A click on a splitter bar is not a drag. Before this fix, a single click on the index rail's divider sometimes left the bar following the pointer until a second click released it. It happened most often right after a reload (Plugins#4621).
The mechanism
Every splitter in the portal is Fluent UI Blazor's FluentMultiSplitter: the index rail beside a
markdown page or a course (SplitterView/SplitterPane) and the shell's side panel
(PortalLayoutBase). It arms a drag in two hops, and each hop can leave the drag armed after the
button is already up:
- The
mousedownis a Blazor event. On Blazor Server it travels to the circuit, which then calls the module'sstartSplitterResizeback in the browser. That call attaches the documentmousemove/mouseuplisteners. A quick click'smouseupcan come before that round trip ends (a circuit still busy after a reload makes this likely), so the listeners wait for a release that has already happened. - Its
mouseUpHandlerdetaches only when both panes'flex-basisis a percentage. That is true only once amousemovehas rewritten them. The rail pane is declared as260px, so a click without a move keeps the listeners attached, every time, until a move turns the bases into percentages and the next release finally detaches.
Both are in Fluent's script (4.14.4, unchanged upstream), which this repository does not ship.
The fix
src/MeshWeaver.Blazor/wwwroot/splitterRelease.js is installed by the MeshWeaver.Blazor JS
initializer. Fluent keeps the armed drag in document.splitterData. The guard:
- ends whatever is still armed after a release. Its window-level
mouseupruns after Fluent's document-level one, so a real drag first reports its new size (OnPaneResizedAsync) and detaches itself as before; - ends a drag armed while nothing is pressed.
document.splitterDatais a proxy whose writes are watched, and an entry written after the release (hop 1 arriving late) is ended once Fluent has finished attaching it.
A drag armed while the button is held is left alone, so dragging is unchanged.
How it is pinned
e2e/splitter-release.spec.ts runs Fluent's own script (a verbatim copy in e2e/fixtures/) and the
shipped guard in Chromium, with no portal. The test plays the circuit hop itself, so both orders are
deterministic. Two cases run without the guard and assert the defect: if Fluent fixes it upstream
they turn red, and the guard can be retired. CI runs the spec in the e2e suite compiles and enumerates job, which a change to the guard or the initializer also triggers.
Related
Index groups keep the reader's toggles covers a different symptom on the same divider, Plugins#4420: a click re-rendered the index and reset its open groups.