@cplieger/web-terminal-ui 8.3.0 → 8.3.2
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/README.md +65 -612
- package/css/01-scope.css +3 -9
- package/package.json +4 -4
- package/src/composition.ts +1 -1
- package/src/features/connection-banner.ts +2 -2
- package/src/features/context-menu.ts +3 -8
- package/src/features/predictive-echo.ts +0 -1
- package/src/features/tabs/index.ts +53 -111
- package/src/features/tabs/model.ts +3 -10
- package/src/kernel/a11y.ts +10 -5
- package/src/kernel/conn-state.ts +3 -9
- package/src/kernel/loading-status.ts +6 -14
- package/src/kernel/pane.ts +4 -10
- package/src/kernel/scrollback-storage.ts +0 -5
- package/src/kernel/selection.ts +0 -3
- package/src/kernel/shell.ts +2 -27
- package/src/kernel/split-handle.ts +0 -3
- package/src/predict.ts +2 -11
- package/src/viewport.ts +9 -1
package/src/kernel/pane.ts
CHANGED
|
@@ -220,7 +220,8 @@ export interface PaneKernel extends PaneHandle {
|
|
|
220
220
|
* gets when the closing transition ends. */
|
|
221
221
|
setHidden(value: boolean): void;
|
|
222
222
|
/** The shell is about to slide this pane's box: nothing it measures from now
|
|
223
|
-
* until its viewport settles is final, and the settle sends the size.
|
|
223
|
+
* until its viewport settles is final, and the settle sends the size. Live
|
|
224
|
+
* panes only: a failed pane is torn down before the split next opens. */
|
|
224
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,
|
|
@@ -437,15 +438,10 @@ function buildPaneInto(
|
|
|
437
438
|
let hidden = false;
|
|
438
439
|
let rootReleased = false;
|
|
439
440
|
|
|
440
|
-
let ready = false;
|
|
441
441
|
let firstFrameRendered = false;
|
|
442
442
|
let fontsLoaded = false;
|
|
443
443
|
let wsOpen = false;
|
|
444
444
|
function markReady(): void {
|
|
445
|
-
if (ready) {
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
ready = true;
|
|
449
445
|
connState.setLoaded();
|
|
450
446
|
services.loading.firstFrame();
|
|
451
447
|
}
|
|
@@ -981,7 +977,7 @@ function buildPaneInto(
|
|
|
981
977
|
}
|
|
982
978
|
// Nobody owns the keyboard: focus in a shadow root, a dialog or an ARIA
|
|
983
979
|
// widget all report non-body and all bail.
|
|
984
|
-
if (doc.activeElement !==
|
|
980
|
+
if (doc.activeElement !== doc.body) {
|
|
985
981
|
return;
|
|
986
982
|
}
|
|
987
983
|
if (composition.isComposing()) {
|
|
@@ -1514,9 +1510,7 @@ function buildPaneInto(
|
|
|
1514
1510
|
hidden = value;
|
|
1515
1511
|
},
|
|
1516
1512
|
holdGeometry() {
|
|
1517
|
-
|
|
1518
|
-
viewport.beginTransition();
|
|
1519
|
-
}
|
|
1513
|
+
viewport.beginTransition();
|
|
1520
1514
|
},
|
|
1521
1515
|
setTabStop(value) {
|
|
1522
1516
|
input.tabIndex = value ? 0 : -1;
|
|
@@ -214,7 +214,6 @@ export function localScrollbackStorage(
|
|
|
214
214
|
function sweep(): void {
|
|
215
215
|
const now = Date.now();
|
|
216
216
|
const live: { key: string; savedAt: number; chars: number }[] = [];
|
|
217
|
-
let total = 0;
|
|
218
217
|
for (const entry of ownEntries()) {
|
|
219
218
|
// An unreadable timestamp is treated as expired: the kernel would refuse
|
|
220
219
|
// the entry anyway, so keeping it only consumes quota.
|
|
@@ -223,10 +222,6 @@ export function localScrollbackStorage(
|
|
|
223
222
|
continue;
|
|
224
223
|
}
|
|
225
224
|
live.push({ key: entry.key, savedAt: entry.savedAt, chars: entry.chars });
|
|
226
|
-
total += entry.chars;
|
|
227
|
-
}
|
|
228
|
-
if (total <= maxBytes) {
|
|
229
|
-
return;
|
|
230
225
|
}
|
|
231
226
|
// Newest first, then keep entries while they fit and drop the rest: an entry
|
|
232
227
|
// the user last saw a minute ago is worth more than one from this morning.
|
package/src/kernel/selection.ts
CHANGED
|
@@ -12,9 +12,6 @@ export function selectionTextWithin(container: Element): string {
|
|
|
12
12
|
return "";
|
|
13
13
|
}
|
|
14
14
|
const text = sel.toString();
|
|
15
|
-
if (text.length === 0) {
|
|
16
|
-
return "";
|
|
17
|
-
}
|
|
18
15
|
for (let i = 0; i < sel.rangeCount; i++) {
|
|
19
16
|
const range = sel.getRangeAt(i);
|
|
20
17
|
if (!container.contains(range.startContainer) || !container.contains(range.endContainer)) {
|
package/src/kernel/shell.ts
CHANGED
|
@@ -549,9 +549,6 @@ function createShellInto(
|
|
|
549
549
|
applyRatioVar();
|
|
550
550
|
}
|
|
551
551
|
function fireSplitChange(): void {
|
|
552
|
-
if (changeListeners.size === 0) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
552
|
const state = splitState();
|
|
556
553
|
for (const cb of [...changeListeners]) {
|
|
557
554
|
cb(state);
|
|
@@ -608,9 +605,6 @@ function createShellInto(
|
|
|
608
605
|
// may rest on the empty pane the replacement is headed for, so the handle faces
|
|
609
606
|
// where the new tab will land.
|
|
610
607
|
function select(side: PaneSide): boolean {
|
|
611
|
-
if (!isPaneSide(side)) {
|
|
612
|
-
return false;
|
|
613
|
-
}
|
|
614
608
|
const state = paneAt(side)?.state();
|
|
615
609
|
if (state !== "shown" && !(state === "empty" && !anyShown())) {
|
|
616
610
|
return false;
|
|
@@ -1064,7 +1058,7 @@ function createShellInto(
|
|
|
1064
1058
|
return;
|
|
1065
1059
|
}
|
|
1066
1060
|
for (const feature of features) {
|
|
1067
|
-
if (
|
|
1061
|
+
if (kernel.isDestroyed()) {
|
|
1068
1062
|
return;
|
|
1069
1063
|
}
|
|
1070
1064
|
const outcome = await kernel.setupFeature(feature);
|
|
@@ -1072,9 +1066,6 @@ function createShellInto(
|
|
|
1072
1066
|
failPane(slot, { phase: "feature-setup", feature: outcome.feature, cause: outcome.cause });
|
|
1073
1067
|
return;
|
|
1074
1068
|
}
|
|
1075
|
-
if (outcome.status === "aborted") {
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
1069
|
}
|
|
1079
1070
|
}
|
|
1080
1071
|
function buildSecondary(slot: Slot): void {
|
|
@@ -1231,7 +1222,7 @@ function createShellInto(
|
|
|
1231
1222
|
return true;
|
|
1232
1223
|
}
|
|
1233
1224
|
function closeSide(side: PaneSide): boolean {
|
|
1234
|
-
if (!
|
|
1225
|
+
if (!splitOpen || destroyed) {
|
|
1235
1226
|
return false;
|
|
1236
1227
|
}
|
|
1237
1228
|
const squeezed = slotAt(side);
|
|
@@ -1266,9 +1257,6 @@ function createShellInto(
|
|
|
1266
1257
|
);
|
|
1267
1258
|
}
|
|
1268
1259
|
function onShellResize(): void {
|
|
1269
|
-
if (destroyed) {
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
1260
|
paintShellNarrow();
|
|
1273
1261
|
for (const p of builtPanes()) {
|
|
1274
1262
|
p.updateNarrow();
|
|
@@ -1427,9 +1415,6 @@ function createShellInto(
|
|
|
1427
1415
|
|
|
1428
1416
|
let rootReleased = false;
|
|
1429
1417
|
function cleanupRuntime(): void {
|
|
1430
|
-
if (destroyed) {
|
|
1431
|
-
return;
|
|
1432
|
-
}
|
|
1433
1418
|
destroyed = true;
|
|
1434
1419
|
// The tracked stores are about to become unreachable, and the connections
|
|
1435
1420
|
// whose epochs the save is filed under go with the drain.
|
|
@@ -1441,7 +1426,6 @@ function createShellInto(
|
|
|
1441
1426
|
if (destroyed) {
|
|
1442
1427
|
return;
|
|
1443
1428
|
}
|
|
1444
|
-
primaryKernel.markFailed();
|
|
1445
1429
|
cleanupRuntime();
|
|
1446
1430
|
dismissLoadingOverlay();
|
|
1447
1431
|
let handled = false;
|
|
@@ -1517,21 +1501,12 @@ function createShellInto(
|
|
|
1517
1501
|
selectedPane().focus();
|
|
1518
1502
|
},
|
|
1519
1503
|
send(bytes) {
|
|
1520
|
-
if (destroyed) {
|
|
1521
|
-
return;
|
|
1522
|
-
}
|
|
1523
1504
|
selectedPane().send(bytes);
|
|
1524
1505
|
},
|
|
1525
1506
|
reset() {
|
|
1526
|
-
if (destroyed) {
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
1507
|
selectedPane().reset();
|
|
1530
1508
|
},
|
|
1531
1509
|
reattach() {
|
|
1532
|
-
if (destroyed) {
|
|
1533
|
-
return;
|
|
1534
|
-
}
|
|
1535
1510
|
selectedPane().reattach();
|
|
1536
1511
|
},
|
|
1537
1512
|
destroy() {
|
|
@@ -237,9 +237,6 @@ export function createSplitHandle(shell: SplitHandleShell): SplitHandle {
|
|
|
237
237
|
return;
|
|
238
238
|
}
|
|
239
239
|
const state = split.state();
|
|
240
|
-
if (!state.open || state.collapsed) {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
240
|
ev.preventDefault();
|
|
244
241
|
const span = root.clientWidth - SPLIT_GUTTER_PX;
|
|
245
242
|
const { min, max } = bounds();
|
package/src/predict.ts
CHANGED
|
@@ -31,7 +31,6 @@ export function createPredictor(): Predictor {
|
|
|
31
31
|
let predCol = 0;
|
|
32
32
|
let predActive = false;
|
|
33
33
|
let predPendingWrap = false;
|
|
34
|
-
let predFrozen = false;
|
|
35
34
|
let cols = 80;
|
|
36
35
|
let rows = 30;
|
|
37
36
|
let onChange: (() => void) | null = null;
|
|
@@ -40,8 +39,6 @@ export function createPredictor(): Predictor {
|
|
|
40
39
|
predRow = 0;
|
|
41
40
|
predCol = 0;
|
|
42
41
|
predActive = false;
|
|
43
|
-
predPendingWrap = false;
|
|
44
|
-
predFrozen = false;
|
|
45
42
|
onChange?.();
|
|
46
43
|
}
|
|
47
44
|
|
|
@@ -73,23 +70,17 @@ export function createPredictor(): Predictor {
|
|
|
73
70
|
predRow = serverRow;
|
|
74
71
|
predCol = serverCol;
|
|
75
72
|
predPendingWrap = false;
|
|
76
|
-
|
|
77
|
-
predActive = !predFrozen;
|
|
73
|
+
predActive = !(cursorHidden ?? false);
|
|
78
74
|
onChange?.();
|
|
79
75
|
},
|
|
80
76
|
applyInput(bytes) {
|
|
81
|
-
if (!predActive
|
|
77
|
+
if (!predActive) {
|
|
82
78
|
return;
|
|
83
79
|
}
|
|
84
80
|
let i = 0;
|
|
85
81
|
while (i < bytes.length) {
|
|
86
82
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- index checked by loop condition
|
|
87
83
|
const b = bytes[i]!;
|
|
88
|
-
if (b === 0x1b) {
|
|
89
|
-
predActive = false;
|
|
90
|
-
onChange?.();
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
84
|
if (b === 0x08 || b === 0x7f) {
|
|
94
85
|
predPendingWrap = false;
|
|
95
86
|
if (predCol > 0) {
|
package/src/viewport.ts
CHANGED
|
@@ -9,7 +9,15 @@ import { windowOf } from "./kernel/realm.js";
|
|
|
9
9
|
|
|
10
10
|
// Long enough to bridge the iOS keyboard slide (~250ms) with margin for fonts
|
|
11
11
|
// and reflow.
|
|
12
|
-
|
|
12
|
+
let SETTLE_MS = 350;
|
|
13
|
+
|
|
14
|
+
/** @internal Test seam: arm every settle started from now on at `ms`; returns the
|
|
15
|
+
* previous value so the caller can restore it. */
|
|
16
|
+
export function setSettleMs(ms: number): number {
|
|
17
|
+
const previous = SETTLE_MS;
|
|
18
|
+
SETTLE_MS = ms;
|
|
19
|
+
return previous;
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
/** The visual viewport against the layout viewport, in whole CSS px. */
|
|
15
23
|
interface KeyboardGeometry {
|