@cplieger/web-terminal-ui 5.4.5 → 5.4.6
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 +51 -59
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.6",
|
|
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",
|
|
@@ -678,13 +678,11 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
678
678
|
// back to the stylesheet — including when a second drag interrupts the first.
|
|
679
679
|
const shifted = new Set<HTMLElement>();
|
|
680
680
|
let shiftTimer: ReturnType<typeof setTimeout> | null = null;
|
|
681
|
-
// The
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
// stillness (see trackRest).
|
|
681
|
+
// The no-events fallback timer. It carries its own target, so there is
|
|
682
|
+
// deliberately NO pending-slot field here: one was the cause of the reorder
|
|
683
|
+
// dropping commits (see trackRest). restX is the last pointer position seen, which
|
|
684
|
+
// is how movement is told from stillness.
|
|
686
685
|
let restTimer: ReturnType<typeof setTimeout> | null = null;
|
|
687
|
-
let restBefore: HTMLElement | null = null;
|
|
688
686
|
let restX: number | null = null;
|
|
689
687
|
// When the pointer last actually MOVED. A stationary dragover this long after it
|
|
690
688
|
// is believed as a stop (see trackRest).
|
|
@@ -1896,7 +1894,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
1896
1894
|
// was too small to tell them apart).
|
|
1897
1895
|
//
|
|
1898
1896
|
// A release never waits either: `drop` commits whatever slot is pending
|
|
1899
|
-
// (
|
|
1897
|
+
// (the drop handler commits the slot under the pointer), so dropping mid-sweep
|
|
1898
|
+
// still lands the tab exactly where it was headed.
|
|
1900
1899
|
//
|
|
1901
1900
|
// dropTargetBefore returns the first tab whose horizontal midpoint is past
|
|
1902
1901
|
// x (the element the dragged tab should sit before), or null to drop at the
|
|
@@ -2003,21 +2002,30 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2003
2002
|
if (!dragged) {
|
|
2004
2003
|
return;
|
|
2005
2004
|
}
|
|
2005
|
+
const now = Date.now();
|
|
2006
2006
|
const moved = restX === null || Math.abs(clientX - restX) > REORDER_MOVE_EPS_PX;
|
|
2007
2007
|
restX = clientX;
|
|
2008
|
-
const before = dropTargetBefore(clientX);
|
|
2009
|
-
// Already the slot the tab holds, so there is nothing to preview or commit.
|
|
2010
|
-
// Withdraw anything pending: the pointer came back.
|
|
2011
|
-
if (before === dragged || before === dragged.nextElementSibling) {
|
|
2012
|
-
cancelRest();
|
|
2013
|
-
return;
|
|
2014
|
-
}
|
|
2015
|
-
restBefore = before;
|
|
2016
|
-
const now = Date.now();
|
|
2017
2008
|
if (moved) {
|
|
2018
|
-
// Still travelling. Push the net out and wait.
|
|
2019
2009
|
restMovedAt = now;
|
|
2020
|
-
|
|
2010
|
+
}
|
|
2011
|
+
// The candidate is recomputed from THIS event, every time, and the commit below
|
|
2012
|
+
// uses that value rather than anything stored. That is the fix for a real
|
|
2013
|
+
// unreliability, so it must not be refactored back into a pending-target field:
|
|
2014
|
+
//
|
|
2015
|
+
// There used to be a `restBefore` holding the pending slot, and both the
|
|
2016
|
+
// already-there branch and the leave-the-strip paths nulled it. So a drag that
|
|
2017
|
+
// passed through "already there" on its way somewhere else lost the pending
|
|
2018
|
+
// state, and the stillness branch below — which then early-returned because no
|
|
2019
|
+
// timer was armed — committed NOTHING when the pointer stopped. The tab stayed on
|
|
2020
|
+
// its previous slot until the user jiggled the mouse to re-arm, which is exactly
|
|
2021
|
+
// the reported "it does not let go of the old drop spot". It hit leftward drags
|
|
2022
|
+
// hardest, because after each commit the dragged chip sits immediately left of
|
|
2023
|
+
// where the pointer is heading, so `nextElementSibling` matches constantly.
|
|
2024
|
+
const before = dropTargetBefore(clientX);
|
|
2025
|
+
// Already the slot it would land in, so there is nothing to commit. Only the net
|
|
2026
|
+
// is dropped; there is no pending target left to lose.
|
|
2027
|
+
if (before === dragged.nextElementSibling) {
|
|
2028
|
+
endRestNet();
|
|
2021
2029
|
return;
|
|
2022
2030
|
}
|
|
2023
2031
|
// A dragover at an UNCHANGED position is positive evidence that the pointer has
|
|
@@ -2026,48 +2034,31 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2026
2034
|
// cadence. The elapsed check filters the coincidence where one event of a sweep
|
|
2027
2035
|
// lands within REORDER_MOVE_EPS_PX of the previous (a reversal, or a frame whose
|
|
2028
2036
|
// motion was almost all vertical); a real stop clears it on the next event.
|
|
2029
|
-
if (now - restMovedAt >= REORDER_STILL_MS) {
|
|
2030
|
-
|
|
2037
|
+
if (!moved && now - restMovedAt >= REORDER_STILL_MS) {
|
|
2038
|
+
endRestNet();
|
|
2039
|
+
commitSlot(before);
|
|
2031
2040
|
return;
|
|
2032
2041
|
}
|
|
2033
|
-
armRestNet();
|
|
2042
|
+
armRestNet(before);
|
|
2034
2043
|
}
|
|
2035
2044
|
// armRestNet is the no-events fallback: if dragover stops arriving altogether, no
|
|
2036
|
-
// stationary event will ever confirm the stop, so this commits anyway.
|
|
2037
|
-
//
|
|
2038
|
-
//
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2045
|
+
// stationary event will ever confirm the stop, so this commits anyway. It carries
|
|
2046
|
+
// its own target for the same reason trackRest recomputes one — a net whose target
|
|
2047
|
+
// lived in shared mutable state was how the old pending slot got lost. Sized to
|
|
2048
|
+
// out-wait the cadence (see REORDER_REST_MS) because it is the one timer a fast
|
|
2049
|
+
// sweep could otherwise expire between two of its own events.
|
|
2050
|
+
function armRestNet(before: HTMLElement | null): void {
|
|
2051
|
+
endRestNet();
|
|
2043
2052
|
restTimer = setTimeout(() => {
|
|
2044
2053
|
restTimer = null;
|
|
2045
|
-
|
|
2046
|
-
restBefore = null;
|
|
2047
|
-
commitSlot(target);
|
|
2054
|
+
commitSlot(before);
|
|
2048
2055
|
}, REORDER_REST_MS);
|
|
2049
2056
|
}
|
|
2050
|
-
function
|
|
2051
|
-
if (restTimer
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
clearTimeout(restTimer);
|
|
2055
|
-
restTimer = null;
|
|
2056
|
-
restBefore = null;
|
|
2057
|
-
}
|
|
2058
|
-
// flushRest commits a pending slot at once, because a release is a decision. It is
|
|
2059
|
-
// what stops the rest window from ever being felt as a floor on the gesture: drop
|
|
2060
|
-
// mid-sweep and the tab still lands where it was headed. Only a `drop` calls it;
|
|
2061
|
-
// an abandoned drag reaches dragend without one and reverts.
|
|
2062
|
-
function flushRest(): void {
|
|
2063
|
-
if (restTimer === null) {
|
|
2064
|
-
return;
|
|
2057
|
+
function endRestNet(): void {
|
|
2058
|
+
if (restTimer !== null) {
|
|
2059
|
+
clearTimeout(restTimer);
|
|
2060
|
+
restTimer = null;
|
|
2065
2061
|
}
|
|
2066
|
-
const target = restBefore;
|
|
2067
|
-
clearTimeout(restTimer);
|
|
2068
|
-
restTimer = null;
|
|
2069
|
-
restBefore = null;
|
|
2070
|
-
commitSlot(target);
|
|
2071
2062
|
}
|
|
2072
2063
|
|
|
2073
2064
|
// flipTo runs a rearranging mutation and animates its result: every chip that
|
|
@@ -2263,7 +2254,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2263
2254
|
// start from rest, and a torn-down feature may leave no inline style on an
|
|
2264
2255
|
// element it no longer owns.
|
|
2265
2256
|
function endReorderPreview(): void {
|
|
2266
|
-
|
|
2257
|
+
endRestNet();
|
|
2267
2258
|
restX = null;
|
|
2268
2259
|
endSlotFade();
|
|
2269
2260
|
}
|
|
@@ -2283,9 +2274,6 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2283
2274
|
// in progress" and so would preventDefault every unrelated file or text drop on
|
|
2284
2275
|
// the page from then on.
|
|
2285
2276
|
function abortReorderFor(removed: HTMLElement): void {
|
|
2286
|
-
if (removed === restBefore) {
|
|
2287
|
-
cancelRest();
|
|
2288
|
-
}
|
|
2289
2277
|
if (removed !== draggingEl) {
|
|
2290
2278
|
return;
|
|
2291
2279
|
}
|
|
@@ -3359,7 +3347,11 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
3359
3347
|
}
|
|
3360
3348
|
e.preventDefault();
|
|
3361
3349
|
dropped = true;
|
|
3362
|
-
|
|
3350
|
+
// A release decides, and it decides by POSITION: commit the slot the pointer is
|
|
3351
|
+
// actually over rather than whatever a timer had pending. commitSlot no-ops when
|
|
3352
|
+
// the tab already sits there, so a drop with nothing to move costs nothing.
|
|
3353
|
+
endRestNet();
|
|
3354
|
+
commitSlot(dropTargetBefore(e.clientX));
|
|
3363
3355
|
syncOrderFromDom();
|
|
3364
3356
|
announceMoved(moved); // the ONE completed-move announcement per gesture
|
|
3365
3357
|
});
|
|
@@ -3382,7 +3374,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
3382
3374
|
}
|
|
3383
3375
|
const next = e.relatedTarget;
|
|
3384
3376
|
if (next === null || !bar.contains(next as Node)) {
|
|
3385
|
-
|
|
3377
|
+
endRestNet();
|
|
3386
3378
|
}
|
|
3387
3379
|
});
|
|
3388
3380
|
// A tab released anywhere OTHER than the strip — over the terminal, over
|
|
@@ -3412,14 +3404,14 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
3412
3404
|
// So: leave `dropped` false, withdraw anything pending, and let dragend run the
|
|
3413
3405
|
// revert. Nothing is lost silently — the revert animates and announces, so a
|
|
3414
3406
|
// mis-release reads as "that did not take" rather than as a mystery.
|
|
3415
|
-
|
|
3407
|
+
endRestNet();
|
|
3416
3408
|
return;
|
|
3417
3409
|
}
|
|
3418
3410
|
// dragover, off the strip: no candidate slot exists out here, so withdraw a
|
|
3419
3411
|
// pending one rather than let the rest window open a gap for a target the pointer
|
|
3420
3412
|
// has already left. A slot already COMMITTED stays put; the pointer may yet come
|
|
3421
3413
|
// back onto the strip and release there.
|
|
3422
|
-
|
|
3414
|
+
endRestNet();
|
|
3423
3415
|
};
|
|
3424
3416
|
document.addEventListener("dragover", onDocTabDrop);
|
|
3425
3417
|
document.addEventListener("drop", onDocTabDrop);
|