@cplieger/web-terminal-ui 8.4.0 → 8.5.0
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/kernel/pane.ts +5 -4
- package/src/kernel/shell.ts +38 -4
- package/src/viewport.ts +26 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.0",
|
|
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",
|
package/src/kernel/pane.ts
CHANGED
|
@@ -219,9 +219,10 @@ export interface PaneKernel extends PaneHandle {
|
|
|
219
219
|
* gets when the closing transition ends. */
|
|
220
220
|
setHidden(value: boolean): void;
|
|
221
221
|
/** The shell is about to slide this pane's box: nothing it measures from now
|
|
222
|
-
* until its viewport settles is final, and the
|
|
223
|
-
* panes only: a failed pane is torn down before
|
|
224
|
-
|
|
222
|
+
* until its viewport settles after the returned release is final, and the
|
|
223
|
+
* settle sends the size. Live panes only: a failed pane is torn down before
|
|
224
|
+
* the split next opens. */
|
|
225
|
+
holdGeometry(): () => void;
|
|
225
226
|
/** Whether this pane's input is a stop in the document's Tab order. On while
|
|
226
227
|
* the split is open and the pane shows a tab, so Tab runs left input, divider,
|
|
227
228
|
* right input, chrome; off otherwise, where typing or a click enters the one
|
|
@@ -1508,7 +1509,7 @@ function buildPaneInto(
|
|
|
1508
1509
|
hidden = value;
|
|
1509
1510
|
},
|
|
1510
1511
|
holdGeometry() {
|
|
1511
|
-
viewport.
|
|
1512
|
+
return viewport.hold();
|
|
1512
1513
|
},
|
|
1513
1514
|
setTabStop(value) {
|
|
1514
1515
|
input.tabIndex = value ? 0 : -1;
|
package/src/kernel/shell.ts
CHANGED
|
@@ -508,7 +508,9 @@ function createShellInto(
|
|
|
508
508
|
return cssTimeMs(win.getComputedStyle(shellRoot).getPropertyValue("--dur-standard"));
|
|
509
509
|
}
|
|
510
510
|
function slideFor(durationMs: number, tail: () => void): Slide {
|
|
511
|
+
let live = true;
|
|
511
512
|
const stop = (): void => {
|
|
513
|
+
live = false;
|
|
512
514
|
win.clearTimeout(timer);
|
|
513
515
|
shellRoot.removeEventListener("transitionend", onEnd);
|
|
514
516
|
};
|
|
@@ -518,12 +520,39 @@ function createShellInto(
|
|
|
518
520
|
};
|
|
519
521
|
const onEnd = (ev: TransitionEvent): void => {
|
|
520
522
|
if (ev.target === shellRoot && ev.propertyName === "grid-template-columns") {
|
|
523
|
+
settleWhenStill();
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
// Removing the slide class does not stop a running transition (the fallback
|
|
527
|
+
// `transition-property: all` still matches it), so a slide whose first frame
|
|
528
|
+
// came late would finish while its columns still move, announcing mid-slide
|
|
529
|
+
// sizes. Nor is every transitionend this slide's: the previous slide's can
|
|
530
|
+
// land after its finish. So the timer and transitionend settle only once no
|
|
531
|
+
// columns transition runs; a direct `settle()` still ends the slide at once.
|
|
532
|
+
const settleWhenStill = (): void => {
|
|
533
|
+
if (!live) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
// A completed transition can stay listed until the next style change event
|
|
537
|
+
// (https://www.w3.org/TR/css-transitions-1/#complete); waiting on its settled
|
|
538
|
+
// `finished` would rerun this check in microtasks forever.
|
|
539
|
+
const running = shellRoot
|
|
540
|
+
.getAnimations()
|
|
541
|
+
.filter(
|
|
542
|
+
(a) =>
|
|
543
|
+
a.playState === "running" &&
|
|
544
|
+
"transitionProperty" in a &&
|
|
545
|
+
a.transitionProperty === "grid-template-columns",
|
|
546
|
+
);
|
|
547
|
+
if (running.length === 0) {
|
|
521
548
|
settle();
|
|
549
|
+
return;
|
|
522
550
|
}
|
|
551
|
+
void Promise.allSettled(running.map((a) => a.finished)).then(settleWhenStill);
|
|
523
552
|
};
|
|
524
553
|
shellRoot.addEventListener("transitionend", onEnd);
|
|
525
554
|
// An engine that applies a track-list change at once fires no transitionend.
|
|
526
|
-
const timer = win.setTimeout(
|
|
555
|
+
const timer = win.setTimeout(settleWhenStill, durationMs);
|
|
527
556
|
return { settle, abort: stop };
|
|
528
557
|
}
|
|
529
558
|
function beginClosing(toward: 0 | 1, durationMs: number, tail: () => void): void {
|
|
@@ -546,9 +575,12 @@ function createShellInto(
|
|
|
546
575
|
* start shows the new pane 0 px wide, so the panes hold their geometry until
|
|
547
576
|
* it is over, or a socket opening meanwhile would size its PTY to that. */
|
|
548
577
|
function beginOpening(durationMs: number): void {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
578
|
+
const holds = builtPanes().map((p) => p.holdGeometry());
|
|
579
|
+
const release = (): void => {
|
|
580
|
+
for (const r of holds) {
|
|
581
|
+
r();
|
|
582
|
+
}
|
|
583
|
+
};
|
|
552
584
|
shellRoot.style.setProperty("--wt-split-ratio", "1");
|
|
553
585
|
// The read commits the start as a style of its own; without it the slide
|
|
554
586
|
// would start from the closed view's single column.
|
|
@@ -560,6 +592,7 @@ function createShellInto(
|
|
|
560
592
|
for (const p of builtPanes()) {
|
|
561
593
|
p.announceSize();
|
|
562
594
|
}
|
|
595
|
+
release();
|
|
563
596
|
});
|
|
564
597
|
opening = {
|
|
565
598
|
settle: slide.settle,
|
|
@@ -567,6 +600,7 @@ function createShellInto(
|
|
|
567
600
|
slide.abort();
|
|
568
601
|
opening = null;
|
|
569
602
|
shellRoot.classList.remove("wt-split-opening");
|
|
603
|
+
release();
|
|
570
604
|
},
|
|
571
605
|
};
|
|
572
606
|
applyRatioVar();
|
package/src/viewport.ts
CHANGED
|
@@ -79,8 +79,11 @@ export interface Viewport {
|
|
|
79
79
|
/** Whether a transition is in flight, so geometry measured now is provisional. */
|
|
80
80
|
isInTransition(): boolean;
|
|
81
81
|
/** Start a transition as a resize would, for a box about to move that has not
|
|
82
|
-
* moved yet
|
|
83
|
-
|
|
82
|
+
* moved yet, and keep it from settling until the returned release runs (which
|
|
83
|
+
* is idempotent). It settles SETTLE_MS after the last hold or resize, as any
|
|
84
|
+
* transition does; a settle that came due while held runs SETTLE_MS after the
|
|
85
|
+
* last release instead. */
|
|
86
|
+
hold(): () => void;
|
|
84
87
|
/** Release every listener and observer and stop the settle timer. */
|
|
85
88
|
teardown(): void;
|
|
86
89
|
}
|
|
@@ -185,6 +188,7 @@ export function createViewport(opts: ViewportOptions): Viewport {
|
|
|
185
188
|
// page's.
|
|
186
189
|
const win = windowOf(termWrap.ownerDocument);
|
|
187
190
|
let inTransition = false;
|
|
191
|
+
let holds = 0;
|
|
188
192
|
let settleTimer: number | null = null;
|
|
189
193
|
const cleanup: (() => void)[] = [];
|
|
190
194
|
function release(): void {
|
|
@@ -203,6 +207,9 @@ export function createViewport(opts: ViewportOptions): Viewport {
|
|
|
203
207
|
}
|
|
204
208
|
settleTimer = win.setTimeout(() => {
|
|
205
209
|
settleTimer = null;
|
|
210
|
+
if (holds > 0) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
206
213
|
inTransition = false;
|
|
207
214
|
// Read at SETTLE, not at the start of the burst: every further event
|
|
208
215
|
// re-arms the timer without re-reading, and on iOS a wake emits a stream
|
|
@@ -215,6 +222,22 @@ export function createViewport(opts: ViewportOptions): Viewport {
|
|
|
215
222
|
opts.onSettled(stillFollowing);
|
|
216
223
|
}, SETTLE_MS);
|
|
217
224
|
}
|
|
225
|
+
function hold(): () => void {
|
|
226
|
+
holds++;
|
|
227
|
+
startTransition();
|
|
228
|
+
let held = true;
|
|
229
|
+
return () => {
|
|
230
|
+
if (!held) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
held = false;
|
|
234
|
+
holds--;
|
|
235
|
+
// A settle that came due while held was dropped: start it again.
|
|
236
|
+
if (holds === 0 && inTransition && settleTimer === null) {
|
|
237
|
+
startTransition();
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
218
241
|
|
|
219
242
|
// iOS shrinks (and can offset) the visual viewport without resizing the layout
|
|
220
243
|
// viewport, so a `position: fixed; inset: 0` box keeps the full-screen height
|
|
@@ -267,7 +290,7 @@ export function createViewport(opts: ViewportOptions): Viewport {
|
|
|
267
290
|
|
|
268
291
|
return {
|
|
269
292
|
isInTransition: () => inTransition,
|
|
270
|
-
|
|
293
|
+
hold,
|
|
271
294
|
teardown: release,
|
|
272
295
|
};
|
|
273
296
|
}
|