@cplieger/web-terminal-ui 5.4.1 → 5.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/features/tabs/index.ts +96 -152
- package/src/features/tabs/strip.ts +26 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: a composable kernel + feature modules (tabs, activity monitor, mobile toolbar, context menu, IME, predictive echo, viewport handling) and CSS. One createTerminal() call.",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -43,9 +43,9 @@ import {
|
|
|
43
43
|
} from "./model.js";
|
|
44
44
|
import { browserNotifierEnv, createNotifier } from "./notify.js";
|
|
45
45
|
import {
|
|
46
|
-
REORDER_LEAN_PX,
|
|
47
46
|
REORDER_MOVE_EPS_PX,
|
|
48
47
|
REORDER_REST_MS,
|
|
48
|
+
REORDER_STILL_MS,
|
|
49
49
|
REORDER_SETTLE_MS,
|
|
50
50
|
REORDER_SHIFT_TRANS,
|
|
51
51
|
REORDER_SLOT_FADE_MS,
|
|
@@ -667,10 +667,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
667
667
|
let activeId: string | null = null;
|
|
668
668
|
let draggingEl: HTMLElement | null = null;
|
|
669
669
|
// --- Desktop reorder preview state (mechanism below, near dragTargetBefore) ---
|
|
670
|
-
// The chips currently carrying an inline
|
|
671
|
-
//
|
|
672
|
-
//
|
|
673
|
-
// to hand every one of them back to the stylesheet.
|
|
670
|
+
// The chips currently carrying an inline displacement from the commit slide.
|
|
671
|
+
// A set plus one settle function, so whatever a slide wrote can always be handed
|
|
672
|
+
// back to the stylesheet — including when a second drag interrupts the first.
|
|
674
673
|
const shifted = new Set<HTMLElement>();
|
|
675
674
|
let shiftTimer: ReturnType<typeof setTimeout> | null = null;
|
|
676
675
|
// The slot waiting to open and the rest window that will open it.
|
|
@@ -681,6 +680,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
681
680
|
let restTimer: ReturnType<typeof setTimeout> | null = null;
|
|
682
681
|
let restBefore: HTMLElement | null = null;
|
|
683
682
|
let restX: number | null = null;
|
|
683
|
+
// When the pointer last actually MOVED. A stationary dragover this long after it
|
|
684
|
+
// is believed as a stop (see trackRest).
|
|
685
|
+
let restMovedAt = 0;
|
|
684
686
|
// Whether a `drop` fired for the drag in flight. It is the exact signal for
|
|
685
687
|
// "the user released deliberately" as opposed to "the drag was abandoned":
|
|
686
688
|
// Escape and a refused release fire dragend with no drop at all. dragend
|
|
@@ -1833,33 +1835,30 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
1833
1835
|
// in. The chip you were dragging looked like the slot it left, the slot it
|
|
1834
1836
|
// was over, and the slot it would end up in.
|
|
1835
1837
|
//
|
|
1836
|
-
// The preview
|
|
1837
|
-
//
|
|
1838
|
-
//
|
|
1839
|
-
//
|
|
1840
|
-
//
|
|
1841
|
-
// pretending to be the tab):
|
|
1838
|
+
// The preview answers a MOVING pointer and a STOPPED one differently, and the
|
|
1839
|
+
// dragged chip stays in the flow throughout as the slot it will land in
|
|
1840
|
+
// (`.wt-tab-dragging`, which 30-tabs.css renders as an empty dashed outline
|
|
1841
|
+
// rather than a dimmed copy of the tab — the solid copy is the drag image
|
|
1842
|
+
// under the pointer, and one of the two had to stop pretending to be the tab):
|
|
1842
1843
|
//
|
|
1843
|
-
// - SWEEPING across the strip
|
|
1844
|
-
//
|
|
1845
|
-
//
|
|
1846
|
-
//
|
|
1847
|
-
// the
|
|
1848
|
-
// - COMING TO REST opens the slot for real (trackRest -> commitSlot): one
|
|
1849
|
-
// DOM move, the displaced chips slide from their old positions to their
|
|
1850
|
-
// new ones, and the slot fades in at its new home.
|
|
1844
|
+
// - SWEEPING across the strip rearranges NOTHING. Crossing five tabs used to
|
|
1845
|
+
// move all five, which is the whole complaint.
|
|
1846
|
+
// - COMING TO REST opens the slot (trackRest -> commitSlot): one DOM move,
|
|
1847
|
+
// the displaced chips slide from their old positions to their new ones, and
|
|
1848
|
+
// the slot fades in at its new home.
|
|
1851
1849
|
//
|
|
1852
|
-
// Rest is DETECTED, not waited out: REORDER_REST_MS is re-armed by movement,
|
|
1853
|
-
//
|
|
1854
|
-
//
|
|
1855
|
-
//
|
|
1856
|
-
// one case that should be instant
|
|
1857
|
-
//
|
|
1858
|
-
//
|
|
1859
|
-
//
|
|
1850
|
+
// Rest is DETECTED, not waited out: REORDER_REST_MS is re-armed by movement, so
|
|
1851
|
+
// it expires a rest window after the last MOVEMENT rather than a fixed time
|
|
1852
|
+
// after a decision. Two shapes were tried and removed before this one, and both
|
|
1853
|
+
// failures are worth keeping: a fixed one-second HOLD (wrong shape for a direct
|
|
1854
|
+
// manipulation — it delayed the one case that should be instant, and grew a
|
|
1855
|
+
// progress bar to explain the wait, which is the tell that the wait should not
|
|
1856
|
+
// have been there), and a sweep-time LEAN of the chips a commit would displace
|
|
1857
|
+
// (it read as a second, competing preview and its distance from the real move
|
|
1858
|
+
// was too small to tell them apart).
|
|
1860
1859
|
//
|
|
1861
1860
|
// A release never waits either: `drop` commits whatever slot is pending
|
|
1862
|
-
// (flushRest), so dropping mid-sweep lands the tab where
|
|
1861
|
+
// (flushRest), so dropping mid-sweep still lands the tab where it was headed.
|
|
1863
1862
|
//
|
|
1864
1863
|
// dropTargetBefore returns the first tab whose horizontal midpoint is past
|
|
1865
1864
|
// x (the element the dragged tab should sit before), or null to drop at the
|
|
@@ -1869,8 +1868,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
1869
1868
|
//
|
|
1870
1869
|
// It hit-tests LAYOUT geometry (offsetLeft/offsetWidth), never
|
|
1871
1870
|
// getBoundingClientRect. That is not an optimisation, it is what makes an
|
|
1872
|
-
// animated reorder hit-testable at all: a rect read while a chip is mid-
|
|
1873
|
-
//
|
|
1871
|
+
// animated reorder hit-testable at all: a rect read while a chip is mid-slide
|
|
1872
|
+
// returns the INTERPOLATED position, so the preview's own motion feeds
|
|
1874
1873
|
// back into the decision that produced it and the strip oscillates between two
|
|
1875
1874
|
// slots for as long as the pointer sits near a boundary. Layout offsets are the
|
|
1876
1875
|
// chip's position in the FLOW, which a transform does not affect at all.
|
|
@@ -1942,87 +1941,25 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
1942
1941
|
shifted.clear();
|
|
1943
1942
|
}
|
|
1944
1943
|
|
|
1945
|
-
//
|
|
1946
|
-
// destination, for as long as the pointer is still choosing. No settle timer: the
|
|
1947
|
-
// lean is a held state, not an animation with an end, and a timer that expired
|
|
1948
|
-
// under it would snap it away mid-sweep.
|
|
1944
|
+
// trackRest is the whole gate, called on every dragover over the strip.
|
|
1949
1945
|
//
|
|
1950
|
-
//
|
|
1951
|
-
//
|
|
1952
|
-
//
|
|
1946
|
+
// Sweeping and settling are DIFFERENT actions and get different answers. A
|
|
1947
|
+
// travelling pointer rearranges nothing at all; the moment it comes to REST the
|
|
1948
|
+
// slot opens.
|
|
1953
1949
|
//
|
|
1954
|
-
//
|
|
1955
|
-
//
|
|
1956
|
-
// rather than snapping through rest first.
|
|
1957
|
-
function leanToward(before: HTMLElement | null): void {
|
|
1958
|
-
if (!draggingEl) {
|
|
1959
|
-
return;
|
|
1960
|
-
}
|
|
1961
|
-
// The preference is read live, so it can flip mid-gesture. Clearing here rather
|
|
1962
|
-
// than returning bare is what honours a mid-hold opt-in: a bare return would
|
|
1963
|
-
// leave the lean already applied under the previous preference sitting there.
|
|
1964
|
-
if (prefersReduce()) {
|
|
1965
|
-
endShift();
|
|
1966
|
-
return;
|
|
1967
|
-
}
|
|
1968
|
-
const chips = [...scroller.querySelectorAll<HTMLElement>(".wt-tab")];
|
|
1969
|
-
const from = chips.indexOf(draggingEl);
|
|
1970
|
-
const to = before === null ? chips.length : chips.indexOf(before);
|
|
1971
|
-
if (from < 0 || to < 0) {
|
|
1972
|
-
return;
|
|
1973
|
-
}
|
|
1974
|
-
// Moving right past the chips in between shifts them LEFT, and moving left
|
|
1975
|
-
// shifts the chips it displaces RIGHT — the direction the user reads as
|
|
1976
|
-
// "that tab is getting out of the way".
|
|
1977
|
-
const movers = from < to ? chips.slice(from + 1, to) : chips.slice(to, from);
|
|
1978
|
-
const dx = from < to ? -REORDER_LEAN_PX : REORDER_LEAN_PX;
|
|
1979
|
-
const next = new Map<HTMLElement, number>();
|
|
1980
|
-
for (const el of movers) {
|
|
1981
|
-
next.set(el, dx);
|
|
1982
|
-
}
|
|
1983
|
-
for (const el of shifted) {
|
|
1984
|
-
if (!next.has(el)) {
|
|
1985
|
-
next.set(el, 0);
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
applyShift(next, REORDER_SHIFT_TRANS);
|
|
1989
|
-
}
|
|
1990
|
-
// releaseLean eases every leaning chip back to rest and settles it, for when
|
|
1991
|
-
// a candidate is withdrawn (the pointer came back to the committed slot, or left
|
|
1992
|
-
// the strip). endShift would be wrong here: it strips the transition in the same
|
|
1993
|
-
// write, so the lean would snap home.
|
|
1994
|
-
function releaseLean(): void {
|
|
1995
|
-
if (shifted.size === 0) {
|
|
1996
|
-
return;
|
|
1997
|
-
}
|
|
1998
|
-
// ...except under reduced motion, where snapping IS the requested behaviour and
|
|
1999
|
-
// easing back would animate the very motion the preference opted out of.
|
|
2000
|
-
if (prefersReduce()) {
|
|
2001
|
-
endShift();
|
|
2002
|
-
return;
|
|
2003
|
-
}
|
|
2004
|
-
const rest = new Map<HTMLElement, number>();
|
|
2005
|
-
for (const el of shifted) {
|
|
2006
|
-
rest.set(el, 0);
|
|
2007
|
-
}
|
|
2008
|
-
applyShift(rest, REORDER_SHIFT_TRANS);
|
|
2009
|
-
shiftTimer = setTimeout(endShift, REORDER_SETTLE_MS);
|
|
2010
|
-
}
|
|
2011
|
-
// trackRest is the whole gate, called on every dragover over the strip.
|
|
1950
|
+
// Rest is detected from TWO signals, and separating them is what lets the slot
|
|
1951
|
+
// open promptly without a fast sweep committing every slot it crosses:
|
|
2012
1952
|
//
|
|
2013
|
-
//
|
|
2014
|
-
//
|
|
2015
|
-
//
|
|
2016
|
-
//
|
|
2017
|
-
//
|
|
2018
|
-
//
|
|
1953
|
+
// - a `dragover` at an unchanged position is POSITIVE evidence of a stop, so it
|
|
1954
|
+
// commits after a short REORDER_STILL_MS confirmation. This is the signal that
|
|
1955
|
+
// normally decides, and the reason the delay is short.
|
|
1956
|
+
// - the absence of events is only a FALLBACK (armRestNet), for a browser that
|
|
1957
|
+
// stops delivering dragover entirely. That one has to out-wait the drag loop's
|
|
1958
|
+
// 350ms cadence, so it is long — and it is rarely what decides.
|
|
2019
1959
|
//
|
|
2020
|
-
//
|
|
2021
|
-
//
|
|
2022
|
-
//
|
|
2023
|
-
// protect the case that never needed protecting (you are still moving, and the lean
|
|
2024
|
-
// already covers it). The timer here is re-armed by MOVEMENT, so it expires a rest
|
|
2025
|
-
// window after the last movement rather than a fixed time after a decision.
|
|
1960
|
+
// One quiet window had to serve both jobs before this, which is why it could not be
|
|
1961
|
+
// shortened: the timer deciding responsiveness was the same one that had to survive
|
|
1962
|
+
// the cadence.
|
|
2026
1963
|
function trackRest(clientX: number): void {
|
|
2027
1964
|
const dragged = draggingEl;
|
|
2028
1965
|
if (!dragged) {
|
|
@@ -2037,30 +1974,40 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2037
1974
|
cancelRest();
|
|
2038
1975
|
return;
|
|
2039
1976
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
//
|
|
2050
|
-
//
|
|
2051
|
-
//
|
|
2052
|
-
//
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
1977
|
+
restBefore = before;
|
|
1978
|
+
const now = Date.now();
|
|
1979
|
+
if (moved) {
|
|
1980
|
+
// Still travelling. Push the net out and wait.
|
|
1981
|
+
restMovedAt = now;
|
|
1982
|
+
armRestNet();
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
// A dragover at an UNCHANGED position is positive evidence that the pointer has
|
|
1986
|
+
// stopped, which is a far stronger signal than the absence of events — and it is
|
|
1987
|
+
// what lets the slot open promptly instead of out-waiting the drag loop's 350ms
|
|
1988
|
+
// cadence. The elapsed check filters the coincidence where one event of a sweep
|
|
1989
|
+
// lands within REORDER_MOVE_EPS_PX of the previous (a reversal, or a frame whose
|
|
1990
|
+
// motion was almost all vertical); a real stop clears it on the next event.
|
|
1991
|
+
if (now - restMovedAt >= REORDER_STILL_MS) {
|
|
1992
|
+
flushRest();
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
armRestNet(); // too soon to believe; keep the net alive
|
|
1996
|
+
}
|
|
1997
|
+
// armRestNet is the no-events fallback: if dragover stops arriving altogether, no
|
|
1998
|
+
// stationary event will ever confirm the stop, so this commits anyway. Sized to
|
|
1999
|
+
// out-wait the cadence (see REORDER_REST_MS) because it is the one timer that a
|
|
2000
|
+
// fast sweep could otherwise expire between two of its own events.
|
|
2001
|
+
function armRestNet(): void {
|
|
2002
|
+
if (restTimer !== null) {
|
|
2003
|
+
clearTimeout(restTimer);
|
|
2063
2004
|
}
|
|
2005
|
+
restTimer = setTimeout(() => {
|
|
2006
|
+
restTimer = null;
|
|
2007
|
+
const target = restBefore;
|
|
2008
|
+
restBefore = null;
|
|
2009
|
+
commitSlot(target);
|
|
2010
|
+
}, REORDER_REST_MS);
|
|
2064
2011
|
}
|
|
2065
2012
|
function cancelRest(): void {
|
|
2066
2013
|
if (restTimer === null) {
|
|
@@ -2069,11 +2016,10 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2069
2016
|
clearTimeout(restTimer);
|
|
2070
2017
|
restTimer = null;
|
|
2071
2018
|
restBefore = null;
|
|
2072
|
-
releaseLean();
|
|
2073
2019
|
}
|
|
2074
2020
|
// flushRest commits a pending slot at once, because a release is a decision. It is
|
|
2075
2021
|
// what stops the rest window from ever being felt as a floor on the gesture: drop
|
|
2076
|
-
// mid-sweep and the tab lands where
|
|
2022
|
+
// mid-sweep and the tab still lands where it was headed. Only a `drop` calls it;
|
|
2077
2023
|
// an abandoned drag reaches dragend without one and reverts.
|
|
2078
2024
|
function flushRest(): void {
|
|
2079
2025
|
if (restTimer === null) {
|
|
@@ -2103,18 +2049,18 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2103
2049
|
// belongs HERE and not only in the CSS, because these transitions are written
|
|
2104
2050
|
// inline and no stylesheet gate can reach them; the switcher's release reel
|
|
2105
2051
|
// guards its own call site the same way. Read live, so toggling the OS setting
|
|
2106
|
-
// mid-drag takes effect on the next commit
|
|
2052
|
+
// mid-drag takes effect on the very next commit.
|
|
2107
2053
|
if (prefersReduce()) {
|
|
2108
2054
|
endShift();
|
|
2109
2055
|
mutate();
|
|
2110
2056
|
return;
|
|
2111
2057
|
}
|
|
2112
|
-
// FIRST — where each chip is right now,
|
|
2113
|
-
//
|
|
2114
|
-
// release reel captures its live swipe preview the
|
|
2115
|
-
// same reason. Rects, not layout offsets: this is VISUAL
|
|
2116
|
-
// what the animation interpolates (hit-testing wants the
|
|
2117
|
-
// dropTargetBefore).
|
|
2058
|
+
// FIRST — where each chip is right now, MID-SLIDE INCLUDED, so a second commit
|
|
2059
|
+
// continues from wherever the first one got to instead of jumping back through
|
|
2060
|
+
// rest first. The switcher's release reel captures its live swipe preview the
|
|
2061
|
+
// same way, and for the same reason. Rects, not layout offsets: this is VISUAL
|
|
2062
|
+
// position, which is what the animation interpolates (hit-testing wants the
|
|
2063
|
+
// opposite, see dropTargetBefore).
|
|
2118
2064
|
const first = new Map<HTMLElement, number>();
|
|
2119
2065
|
for (const el of scroller.querySelectorAll<HTMLElement>(".wt-tab")) {
|
|
2120
2066
|
if (el !== hold) {
|
|
@@ -2159,14 +2105,14 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2159
2105
|
// an OSC title) renders the committed order rather than flickering through
|
|
2160
2106
|
// whatever the pointer is hovering.
|
|
2161
2107
|
//
|
|
2162
|
-
//
|
|
2163
|
-
//
|
|
2164
|
-
//
|
|
2165
|
-
//
|
|
2108
|
+
// The caller nulls `restTimer` BEFORE calling in (both the rest timer and
|
|
2109
|
+
// flushRest do), so `cancelRest` is already a no-op by the time control arrives
|
|
2110
|
+
// here: an early return must leave nothing behind that only cancelRest would
|
|
2111
|
+
// Nothing exists pre-commit for it to have cleared, and a slide already in flight
|
|
2112
|
+
// is owned by its own settle timer.
|
|
2166
2113
|
function commitSlot(before: HTMLElement | null): void {
|
|
2167
2114
|
const dragged = draggingEl;
|
|
2168
2115
|
if (!dragged?.isConnected) {
|
|
2169
|
-
releaseLean();
|
|
2170
2116
|
return; // the dragged session was closed elsewhere mid-gesture
|
|
2171
2117
|
}
|
|
2172
2118
|
// The pending slot was chosen a rest window ago, and a session closed in another
|
|
@@ -2176,11 +2122,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2176
2122
|
// child, which would abandon the drop mid-way and leave DOM order and tabList
|
|
2177
2123
|
// disagreeing. null stays legal: it means "past the last chip".
|
|
2178
2124
|
if (before !== null && before.parentNode !== scroller) {
|
|
2179
|
-
releaseLean();
|
|
2180
2125
|
return;
|
|
2181
2126
|
}
|
|
2182
2127
|
if (before === dragged || before === dragged.nextElementSibling) {
|
|
2183
|
-
releaseLean();
|
|
2184
2128
|
return; // already in that slot
|
|
2185
2129
|
}
|
|
2186
2130
|
flipTo(() => {
|
|
@@ -2270,8 +2214,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2270
2214
|
}
|
|
2271
2215
|
}
|
|
2272
2216
|
|
|
2273
|
-
// endReorderPreview ends the GESTURE's state: the pending
|
|
2274
|
-
//
|
|
2217
|
+
// endReorderPreview ends the GESTURE's state: the pending rest and the slot's
|
|
2218
|
+
// fade.
|
|
2275
2219
|
//
|
|
2276
2220
|
// It deliberately does NOT stop a slide that is already playing. A slide is an
|
|
2277
2221
|
// animation with its own settle timer, and cutting it here is exactly what
|
|
@@ -2291,9 +2235,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2291
2235
|
// sweep). Two removals matter, for different reasons:
|
|
2292
2236
|
//
|
|
2293
2237
|
// - the pending reference chip: commitSlot would hand a detached node to
|
|
2294
|
-
// insertBefore. It guards that too, but
|
|
2295
|
-
//
|
|
2296
|
-
//
|
|
2238
|
+
// insertBefore. It guards that too, but withdrawing the pending slot here means
|
|
2239
|
+
// a fresh dragover arms against a chip that still exists rather than waiting out
|
|
2240
|
+
// a rest window against one that does not.
|
|
2297
2241
|
// - the DRAGGED chip: the gesture is over and there is no source left to deliver
|
|
2298
2242
|
// a dragend. A browser is not obliged to fire one for a removed source, and
|
|
2299
2243
|
// without this the feature would keep `draggingEl` set to a detached node for
|
|
@@ -3353,7 +3297,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
3353
3297
|
return false;
|
|
3354
3298
|
});
|
|
3355
3299
|
// Drive the reorder preview while dragging a tab over the strip. A sweep only
|
|
3356
|
-
//
|
|
3300
|
+
// rearranges nothing; the slot opens when the pointer comes to rest (trackRest).
|
|
3357
3301
|
bar.addEventListener("dragover", (e) => {
|
|
3358
3302
|
if (!draggingEl) {
|
|
3359
3303
|
return;
|
|
@@ -21,37 +21,41 @@ import { statusPhrase, statusRevealsDot } from "./model.js";
|
|
|
21
21
|
// motion is spelled in — index.ts owns the mechanism, strip.ts owns the values,
|
|
22
22
|
// the same split switcher.ts keeps for the mobile swipe.
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
24
|
+
/** How long (ms) the pointer must have been un-moved before a stationary `dragover` is
|
|
25
|
+
* believed as "stopped".
|
|
26
26
|
*
|
|
27
|
-
* This is
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* This is the number that decides how fast the slot opens, and it is small because a
|
|
28
|
+
* `dragover` at an UNCHANGED position is positive evidence of rest — far better
|
|
29
|
+
* evidence than the absence of events, which is all the fallback below has. The drag
|
|
30
|
+
* loop keeps delivering events while the pointer is held still, so a genuinely stopped
|
|
31
|
+
* pointer announces itself; this only filters the coincidence where one event of a
|
|
32
|
+
* sweep happens to land within REORDER_MOVE_EPS_PX of the previous one (a direction
|
|
33
|
+
* reversal, or a frame where the motion was almost entirely vertical). */
|
|
34
|
+
export const REORDER_STILL_MS = 90;
|
|
35
|
+
|
|
36
|
+
/** Fallback guard (ms): commit the pending slot this long after the last MOVEMENT, for
|
|
37
|
+
* the case where `dragover` stops arriving altogether and no stationary event ever
|
|
38
|
+
* confirms the stop.
|
|
32
39
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* cadence
|
|
36
|
-
*
|
|
40
|
+
* **Its floor is set by the platform, not by taste, and this is the timer that floor
|
|
41
|
+
* applies to.** HTML5 drag-and-drop only guarantees a `dragover` every 350ms — the drag
|
|
42
|
+
* loop runs on that cadence, not per mouse movement — so a guard at or below it can
|
|
43
|
+
* expire BETWEEN two events of a fast sweep, and the strip then commits every slot the
|
|
44
|
+
* pointer crosses. Measured in production: at 120ms a quick pass over five tabs moved
|
|
45
|
+
* all five.
|
|
37
46
|
*
|
|
38
|
-
* A
|
|
39
|
-
|
|
47
|
+
* A pure quiet-window design has to make this ONE number carry both jobs, which is why
|
|
48
|
+
* it could not be shortened — the same timer that decides responsiveness was the one
|
|
49
|
+
* that had to out-wait the cadence, so 500ms was safe and felt slow. Splitting rest
|
|
50
|
+
* DETECTION (the stationary event above) from the no-events NET (here) lets each be
|
|
51
|
+
* sized for its own job. This one is rarely the deciding signal at all. */
|
|
52
|
+
export const REORDER_REST_MS = 450;
|
|
40
53
|
|
|
41
54
|
/** Movement (px) between two dragover events below which the pointer counts as still.
|
|
42
55
|
* A hand resting on a mouse is never perfectly still, and treating a 1px tremor as a
|
|
43
56
|
* sweep would keep pushing the commit out for as long as someone held the tab. */
|
|
44
57
|
export const REORDER_MOVE_EPS_PX = 3;
|
|
45
58
|
|
|
46
|
-
/** How far (px) each chip a commit would displace leans toward its destination while
|
|
47
|
-
* the pointer is still sweeping.
|
|
48
|
-
*
|
|
49
|
-
* Small enough to read as a lean rather than a move, and large enough to be
|
|
50
|
-
* unambiguous at a glance: it is what answers "where will this fall" during the sweep,
|
|
51
|
-
* before anything has actually moved. The same purpose the mobile switcher's
|
|
52
|
-
* PREVIEW_PEEK_MAX serves for its live swipe. */
|
|
53
|
-
export const REORDER_LEAN_PX = 7;
|
|
54
|
-
|
|
55
59
|
/** The one transition both preview stages use (the lean, and the slide that
|
|
56
60
|
* commits it).
|
|
57
61
|
*
|