@cplieger/web-terminal-ui 7.3.2 → 8.0.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/README.md +114 -18
- package/css/30-tabs.css +21 -0
- package/css/31-switcher.css +15 -0
- package/css/32-split.css +176 -0
- package/css/MANIFEST +1 -0
- package/css/MANIFEST.tabbed +1 -0
- package/package.json +13 -12
- package/src/composition.ts +165 -237
- package/src/features/activity-monitor.ts +20 -55
- package/src/features/animations.ts +12 -30
- package/src/features/clipboard.ts +36 -64
- package/src/features/connection-banner.ts +10 -19
- package/src/features/context-menu.ts +70 -145
- package/src/features/dom.ts +44 -54
- package/src/features/menu-position.ts +24 -28
- package/src/features/mobile-toolbar.ts +32 -62
- package/src/features/predictive-echo.ts +15 -49
- package/src/features/scroll-to-bottom.ts +12 -24
- package/src/features/tabs/index.ts +1398 -1806
- package/src/features/tabs/ink-centre.ts +45 -54
- package/src/features/tabs/model.ts +301 -383
- package/src/features/tabs/strip.ts +45 -97
- package/src/features/tabs/switcher.ts +70 -48
- package/src/index.ts +13 -13
- package/src/kernel/a11y.ts +106 -27
- package/src/kernel/attention.ts +157 -0
- package/src/kernel/conn-state.ts +28 -22
- package/src/kernel/fatal.ts +76 -0
- package/src/kernel/feature-host.ts +136 -0
- package/src/kernel/gesture.ts +9 -13
- package/src/kernel/kernel.ts +57 -2139
- package/src/kernel/layout-policy.ts +44 -0
- package/src/kernel/loading-status.ts +30 -88
- package/src/kernel/navigation.ts +11 -17
- package/src/kernel/notify.ts +158 -0
- package/src/kernel/pane.ts +1415 -0
- package/src/kernel/realm.ts +11 -0
- package/src/kernel/regions.ts +8 -11
- package/src/kernel/scrollback.ts +88 -168
- package/src/kernel/selection.ts +25 -0
- package/src/kernel/shell.ts +1434 -0
- package/src/kernel/split-handle.ts +280 -0
- package/src/kernel/status-share.ts +100 -0
- package/src/kernel/types.ts +457 -441
- package/src/predict.ts +144 -170
- package/src/viewport.ts +124 -197
- package/src/features/tabs/attention.ts +0 -250
- package/src/features/tabs/notify.ts +0 -265
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
// Optical centring for the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
// constant, because engines round ascent and descent to whole CSS pixels before
|
|
8
|
-
// building the line box, and round differently per engine and platform.
|
|
1
|
+
// Optical centring for the chip labels, MEASURED in the engine: flex centring
|
|
2
|
+
// centres the LINE BOX, whose font reaches further above the baseline than below,
|
|
3
|
+
// so text sits low, and the correction is the gap from the box's centre to the cap
|
|
4
|
+
// band's. Measured because engines round ascent and descent to whole CSS pixels
|
|
5
|
+
// before building the line box, differently per engine and platform.
|
|
6
|
+
import { windowOf } from "../../kernel/realm.js";
|
|
9
7
|
import { fromHTML } from "../dom.js";
|
|
10
8
|
|
|
11
9
|
/** Ink extents are measured at this size and scaled back to em: WebKit and Blink
|
|
@@ -50,18 +48,25 @@ export function inkShiftPx(m: InkMetrics): number {
|
|
|
50
48
|
return capCentre - m.fontBoxPx / 2;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
// One measuring canvas per document: a canvas resolves font families against
|
|
52
|
+
// its own document's faces, so an outer canvas would not see a face the terminal's
|
|
53
|
+
// document loaded.
|
|
54
|
+
const canvases = new WeakMap<Document, CanvasRenderingContext2D | null>();
|
|
54
55
|
|
|
55
|
-
function canvas2d(): CanvasRenderingContext2D | null {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
function canvas2d(doc: Document): CanvasRenderingContext2D | null {
|
|
57
|
+
let ctx = canvases.get(doc);
|
|
58
|
+
if (ctx === undefined) {
|
|
59
|
+
ctx = doc.createElement("canvas").getContext("2d");
|
|
60
|
+
canvases.set(doc, ctx);
|
|
61
|
+
}
|
|
62
|
+
return ctx;
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
/** The font's own cap ink, in em. Canvas rather than the DOM because only
|
|
61
66
|
* TextMetrics reports where the glyphs' ink actually starts and stops; a DOM
|
|
62
67
|
* rect only ever reports the box around it. */
|
|
63
|
-
function inkExtents(font: string): Pick<InkMetrics, "capInkEm"> | null {
|
|
64
|
-
const ctx = canvas2d();
|
|
68
|
+
function inkExtents(doc: Document, font: string): Pick<InkMetrics, "capInkEm"> | null {
|
|
69
|
+
const ctx = canvas2d(doc);
|
|
65
70
|
if (!ctx) {
|
|
66
71
|
return null;
|
|
67
72
|
}
|
|
@@ -78,12 +83,13 @@ function inkExtents(font: string): Pick<InkMetrics, "capInkEm"> | null {
|
|
|
78
83
|
* sits ON the baseline by definition — the only thing in the DOM that reports
|
|
79
84
|
* a baseline position. */
|
|
80
85
|
function lineBox(label: HTMLElement): Pick<InkMetrics, "fontBoxPx" | "baselinePx"> | null {
|
|
81
|
-
const
|
|
86
|
+
const doc = label.ownerDocument;
|
|
87
|
+
const line = doc.createElement("span");
|
|
82
88
|
line.style.position = "absolute";
|
|
83
89
|
line.style.whiteSpace = "pre";
|
|
84
90
|
line.style.font = "inherit";
|
|
85
91
|
line.textContent = PROBE_TEXT;
|
|
86
|
-
const strut =
|
|
92
|
+
const strut = doc.createElement("span");
|
|
87
93
|
strut.style.display = "inline-block";
|
|
88
94
|
strut.style.width = "0";
|
|
89
95
|
strut.style.height = "0";
|
|
@@ -99,37 +105,32 @@ function lineBox(label: HTMLElement): Pick<InkMetrics, "fontBoxPx" | "baselinePx
|
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
/** Measures one site and returns its shift plus the font signature it belongs
|
|
102
|
-
* to
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* The probe lives only for the duration of this synchronous call, deliberately.
|
|
109
|
-
* Keeping a permanent hidden one would save a DOM mutation per resize, and it
|
|
110
|
-
* would also put a second `.wt-tab` inside the root forever — which the strip's
|
|
111
|
-
* test suite counts as a tab (`root.querySelectorAll(".wt-tab")`). Transient
|
|
112
|
-
* means no other code, and no assertion, can ever observe it. */
|
|
108
|
+
* to; null when the resolved font is unchanged since `cached` or the site
|
|
109
|
+
* cannot be measured (no layout, no canvas, a display: none probe), and callers
|
|
110
|
+
* then leave the CSS default in place. The probe lives only for this
|
|
111
|
+
* synchronous call: a permanent hidden one would be a second `.wt-tab` inside
|
|
112
|
+
* the root that every `querySelectorAll(".wt-tab")` counts. */
|
|
113
113
|
function measureSite(
|
|
114
114
|
varRoot: HTMLElement,
|
|
115
115
|
probeHTML: string,
|
|
116
116
|
cached: string | undefined,
|
|
117
117
|
): { shiftPx: number; signature: string } | null {
|
|
118
|
-
const
|
|
118
|
+
const doc = varRoot.ownerDocument;
|
|
119
|
+
const wrap = doc.createElement("div");
|
|
119
120
|
wrap.setAttribute("aria-hidden", "true");
|
|
120
121
|
wrap.style.position = "absolute";
|
|
121
122
|
wrap.style.top = "0";
|
|
122
123
|
wrap.style.left = "0";
|
|
123
124
|
wrap.style.visibility = "hidden";
|
|
124
125
|
wrap.style.pointerEvents = "none";
|
|
125
|
-
wrap.appendChild(fromHTML(probeHTML));
|
|
126
|
+
wrap.appendChild(fromHTML(doc, probeHTML));
|
|
126
127
|
varRoot.appendChild(wrap);
|
|
127
128
|
try {
|
|
128
129
|
const label = wrap.querySelector<HTMLElement>(`.${PROBE_CLASS}`);
|
|
129
130
|
if (!label) {
|
|
130
131
|
return null;
|
|
131
132
|
}
|
|
132
|
-
const cs = getComputedStyle(label);
|
|
133
|
+
const cs = windowOf(doc).getComputedStyle(label);
|
|
133
134
|
const signature = `${cs.fontStyle} ${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`;
|
|
134
135
|
if (signature === cached) {
|
|
135
136
|
return null;
|
|
@@ -138,7 +139,7 @@ function measureSite(
|
|
|
138
139
|
if (!(fontSizePx > 0)) {
|
|
139
140
|
return null;
|
|
140
141
|
}
|
|
141
|
-
const ink = inkExtents(`${cs.fontStyle} ${cs.fontWeight} ${REF_PX}px ${cs.fontFamily}`);
|
|
142
|
+
const ink = inkExtents(doc, `${cs.fontStyle} ${cs.fontWeight} ${REF_PX}px ${cs.fontFamily}`);
|
|
142
143
|
const box = lineBox(label);
|
|
143
144
|
if (!ink || !box) {
|
|
144
145
|
return null;
|
|
@@ -162,26 +163,14 @@ interface Site {
|
|
|
162
163
|
readonly probeHTML: string;
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* real face can change every number here. These FORCE a re-measure, because
|
|
174
|
-
* the signature below cannot see them: computed style reports the DECLARED
|
|
175
|
-
* font-family list whether or not the webfont ever arrived, so a cached
|
|
176
|
-
* signature would match and pin the fallback's shift permanently. (It did,
|
|
177
|
-
* until scripts/verify-chip-geometry.mjs caught it.)
|
|
178
|
-
* - A resize. rem-relative label sizes move with Safari's per-site page zoom,
|
|
179
|
-
* and a new size means new rounding; but most resizes change neither, so this
|
|
180
|
-
* path keeps the signature check and costs one computed-style read per site
|
|
181
|
-
* when nothing moved.
|
|
182
|
-
*
|
|
183
|
-
* @returns teardown that drops the listeners and the written properties.
|
|
184
|
-
*/
|
|
166
|
+
/** Writes a measured --label-ink-shift onto the tab strip and the mobile
|
|
167
|
+
* switcher, and keeps it current. A font load or failure FORCES a re-measure:
|
|
168
|
+
* the first pass can run inside `font-display: block` on the fallback's line
|
|
169
|
+
* box, and computed style reports the DECLARED font-family list either way, so
|
|
170
|
+
* a cached signature would pin the fallback's shift for good. A resize keeps
|
|
171
|
+
* the signature check (rem sizes move with Safari's per-site zoom, but most
|
|
172
|
+
* resizes change nothing) and costs one computed-style read per site.
|
|
173
|
+
* @returns teardown that drops the listeners and the written properties. */
|
|
185
174
|
export function centreChipLabels(
|
|
186
175
|
varRoot: HTMLElement,
|
|
187
176
|
hosts: { readonly strip: HTMLElement; readonly switcher: HTMLElement },
|
|
@@ -219,20 +208,22 @@ export function centreChipLabels(
|
|
|
219
208
|
};
|
|
220
209
|
|
|
221
210
|
remeasure(false);
|
|
211
|
+
const doc = varRoot.ownerDocument;
|
|
212
|
+
const win = windowOf(doc);
|
|
222
213
|
// The DOM lib types document.fonts as always present; an engine without the CSS
|
|
223
214
|
// Font Loading API ships no FontFaceSet at all, so the honest type is the
|
|
224
215
|
// optional one.
|
|
225
|
-
const fonts =
|
|
216
|
+
const fonts = doc.fonts as FontFaceSet | undefined;
|
|
226
217
|
// `ready` settles the initial load (including a failed one); `loadingdone`
|
|
227
218
|
// covers a face the host page adds afterwards, which `ready` never reports.
|
|
228
219
|
void fonts?.ready.then(onFontsDone);
|
|
229
220
|
fonts?.addEventListener("loadingdone", onFontsDone);
|
|
230
|
-
|
|
221
|
+
win.addEventListener("resize", onResize);
|
|
231
222
|
|
|
232
223
|
return () => {
|
|
233
224
|
disposed = true;
|
|
234
225
|
fonts?.removeEventListener("loadingdone", onFontsDone);
|
|
235
|
-
|
|
226
|
+
win.removeEventListener("resize", onResize);
|
|
236
227
|
for (const site of sites) {
|
|
237
228
|
site.host.style.removeProperty("--label-ink-shift");
|
|
238
229
|
}
|