@cplieger/web-terminal-ui 3.4.5 → 4.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 +53 -23
- package/css/00-tokens.css +8 -2
- package/css/01-scope.css +56 -0
- package/css/{02-app.css → 02-terminal.css} +64 -239
- package/css/{03-primitives.css → 10-primitives.css} +21 -19
- package/css/20-connection-banner.css +42 -0
- package/css/21-scroll-to-bottom.css +65 -0
- package/css/22-context-menu.css +45 -0
- package/css/23-toolbar.css +181 -0
- package/css/{05-tabs.css → 30-tabs.css} +96 -57
- package/css/{06-mobile.css → 31-switcher.css} +66 -81
- package/css/{07-animations.css → 40-animations.css} +15 -15
- package/css/MANIFEST +19 -11
- package/css/MANIFEST.single +12 -0
- package/css/MANIFEST.tabbed +16 -0
- package/css/MANIFEST.touch +12 -0
- package/css/{01-reset.css → page.css} +18 -19
- package/package.json +21 -5
- package/scaffold/index.html +5 -0
- package/src/features/connection-banner.ts +3 -0
- package/src/features/context-menu.ts +6 -33
- package/src/features/menu-position.ts +50 -0
- package/src/features/{tabs.ts → tabs/index.ts} +207 -636
- package/src/features/tabs/model.ts +334 -0
- package/src/features/tabs/strip.ts +101 -0
- package/src/features/tabs/switcher.ts +129 -0
- package/src/index.ts +1 -0
- package/src/kernel/a11y.ts +30 -5
- package/src/kernel/conn-state.ts +20 -3
- package/src/kernel/kernel.ts +162 -59
- package/src/kernel/types.ts +65 -15
- package/src/presets/agent-tabbed.ts +15 -0
- package/src/presets/single.ts +27 -0
- package/src/presets/tabbed.ts +42 -0
- package/src/presets/touch.ts +13 -0
- package/src/presets.ts +12 -76
- package/src/viewport.ts +28 -15
- package/css/04-feedback.css +0 -180
|
@@ -2,26 +2,29 @@
|
|
|
2
2
|
The bottom bar (switcher) is the mobile tab surface, the externally-driven key
|
|
3
3
|
grid docks above it, and the overview sheet lists every tab. The desktop strip
|
|
4
4
|
is hidden only on a narrow coarse-pointer device (a phone) and these appear
|
|
5
|
-
instead; all motion (bar number fade, sheet slide) lives in
|
|
5
|
+
instead; all motion (bar number fade, sheet slide) lives in 40-animations.css. */
|
|
6
6
|
|
|
7
7
|
/* The switcher is used only on a NARROW coarse-pointer device (a phone), where
|
|
8
8
|
the top strip is hidden so tabs live in the thumb zone. A wide touchscreen (an
|
|
9
9
|
iPad) and any fine-pointer device keep the multi-tab top strip — a single
|
|
10
|
-
giant tab wastes a big screen.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
10
|
+
giant tab wastes a big screen. "Narrow" is the kernel's .wt-narrow root class
|
|
11
|
+
(ROOT width at the single kernel breakpoint — so an embedded terminal in a
|
|
12
|
+
narrow panel counts as narrow); this file pairs it with (pointer: coarse),
|
|
13
|
+
and css/30-tabs.css carries the complementary strip clearance. */
|
|
14
|
+
:where(.wt-root) .wt-switcher {
|
|
14
15
|
display: none;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
@media (pointer: coarse)
|
|
18
|
-
.wt-tab-bar,
|
|
19
|
-
.wt-tab-new {
|
|
18
|
+
@media (pointer: coarse) {
|
|
19
|
+
:where(.wt-root.wt-narrow) .wt-tab-bar,
|
|
20
|
+
:where(.wt-root.wt-narrow) .wt-tab-new {
|
|
20
21
|
display: none;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
.wt-switcher {
|
|
24
|
-
|
|
24
|
+
:where(.wt-root.wt-narrow) .wt-switcher {
|
|
25
|
+
/* Absolute against .wt-root, like all chrome (identical geometry in
|
|
26
|
+
wt-viewport mode; see 01-scope.css). */
|
|
27
|
+
position: absolute;
|
|
25
28
|
left: 0;
|
|
26
29
|
right: 0;
|
|
27
30
|
/* Lift above the soft keyboard rather than hiding, so the switch surface
|
|
@@ -39,7 +42,7 @@
|
|
|
39
42
|
/* The always-visible bar row: active tab + keyboard + "+". The padding gives the
|
|
40
43
|
active-tab chip breathing room above/below within the bar, matching the
|
|
41
44
|
desktop strip (both use --sp-2). */
|
|
42
|
-
.wt-switcher-bar {
|
|
45
|
+
:where(.wt-root) .wt-switcher-bar {
|
|
43
46
|
display: flex;
|
|
44
47
|
align-items: stretch;
|
|
45
48
|
gap: var(--sp-1);
|
|
@@ -48,8 +51,8 @@
|
|
|
48
51
|
|
|
49
52
|
/* The other tabs, revealed below the bar row on expand (the bar slides up).
|
|
50
53
|
Collapsed to zero height; expanded caps the height and scrolls. The max-height
|
|
51
|
-
transition lives in
|
|
52
|
-
.wt-switcher-list {
|
|
54
|
+
transition lives in 40-animations.css. */
|
|
55
|
+
:where(.wt-root) .wt-switcher-list {
|
|
53
56
|
list-style: none;
|
|
54
57
|
margin: 0;
|
|
55
58
|
/* Match the bar's horizontal padding (--sp-2) so a listed row lines up with
|
|
@@ -73,7 +76,7 @@
|
|
|
73
76
|
box-sizing: border-box;
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
.wt-switcher-expanded .wt-switcher-list {
|
|
79
|
+
:where(.wt-root) .wt-switcher-expanded .wt-switcher-list {
|
|
77
80
|
/* Cap against the DYNAMIC viewport so a shown mobile browser toolbar can't push the list
|
|
78
81
|
(and the bar above it) off the top; the switcher's own bottom: kb-inset already lifts it
|
|
79
82
|
clear of the soft keyboard. */
|
|
@@ -90,7 +93,7 @@
|
|
|
90
93
|
sibling over the surface's reserved right padding). The wrap is the flex item
|
|
91
94
|
that grows in the bar (and fills the space when the keyboard button collapses
|
|
92
95
|
on expand). */
|
|
93
|
-
.wt-switcher-current-wrap {
|
|
96
|
+
:where(.wt-root) .wt-switcher-current-wrap {
|
|
94
97
|
position: relative;
|
|
95
98
|
flex: 1 1 auto;
|
|
96
99
|
display: flex;
|
|
@@ -101,7 +104,7 @@
|
|
|
101
104
|
overflow: hidden;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
.wt-switcher-current {
|
|
107
|
+
:where(.wt-root) .wt-switcher-current {
|
|
105
108
|
position: relative;
|
|
106
109
|
flex: 1 1 auto;
|
|
107
110
|
display: flex;
|
|
@@ -130,7 +133,7 @@
|
|
|
130
133
|
padding, mirroring the listed rows' close. Hover fill only on real hover
|
|
131
134
|
pointers so it never sticks white after a touch (a removed tab shifts the
|
|
132
135
|
list under the finger otherwise). */
|
|
133
|
-
.wt-switcher-current-close {
|
|
136
|
+
:where(.wt-root) .wt-switcher-current-close {
|
|
134
137
|
position: absolute;
|
|
135
138
|
/* Centre vertically via top/bottom (not a translateY transform), so transform
|
|
136
139
|
is free for the swipe translate — the close moves with the dot + label. And
|
|
@@ -142,14 +145,14 @@
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
@media (hover: hover) {
|
|
145
|
-
.wt-switcher-current-close:hover {
|
|
148
|
+
:where(.wt-root) .wt-switcher-current-close:hover {
|
|
146
149
|
color: var(--text);
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
/* Press feedback: brighten the active fill slightly (a lighter press over an
|
|
151
154
|
already-filled row). */
|
|
152
|
-
.wt-switcher-current:active {
|
|
155
|
+
:where(.wt-root) .wt-switcher-current:active {
|
|
153
156
|
background: color-mix(in oklch, var(--tab-active-bg), var(--tab-active-fg) 12%);
|
|
154
157
|
}
|
|
155
158
|
|
|
@@ -157,7 +160,7 @@
|
|
|
157
160
|
parts out; a horizontal swipe translates it (clipped by the row's
|
|
158
161
|
overflow: hidden) so the active tab area physically slides with the finger,
|
|
159
162
|
and the incoming tab's label slides in from the swipe direction on commit. */
|
|
160
|
-
.wt-switcher-current-inner {
|
|
163
|
+
:where(.wt-root) .wt-switcher-current-inner {
|
|
161
164
|
display: flex;
|
|
162
165
|
align-items: center;
|
|
163
166
|
gap: var(--sp-2);
|
|
@@ -165,7 +168,7 @@
|
|
|
165
168
|
min-width: 0;
|
|
166
169
|
}
|
|
167
170
|
|
|
168
|
-
.wt-switcher-label {
|
|
171
|
+
:where(.wt-root) .wt-switcher-label {
|
|
169
172
|
flex: 1 1 auto;
|
|
170
173
|
overflow: hidden;
|
|
171
174
|
text-overflow: ellipsis;
|
|
@@ -178,7 +181,7 @@
|
|
|
178
181
|
just the wt-btn min-width) makes it animatable: when the tab list expands the
|
|
179
182
|
button collapses to zero width and fades out (the key grid is closed too, in
|
|
180
183
|
JS), and the active row flex-grows to fill the freed space. */
|
|
181
|
-
.wt-switcher-kb {
|
|
184
|
+
:where(.wt-root) .wt-switcher-kb {
|
|
182
185
|
/* Rigid 44px box like the "+" and switch buttons (flex: none), so a long
|
|
183
186
|
active-tab title that overflows the bar is absorbed entirely by the
|
|
184
187
|
ellipsizing active-tab field, not shared with this button. It is otherwise
|
|
@@ -199,11 +202,11 @@
|
|
|
199
202
|
overflow: hidden;
|
|
200
203
|
}
|
|
201
204
|
|
|
202
|
-
.wt-switcher-kb[hidden] {
|
|
205
|
+
:where(.wt-root) .wt-switcher-kb[hidden] {
|
|
203
206
|
display: none;
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
.wt-switcher-kb.wt-active {
|
|
209
|
+
:where(.wt-root) .wt-switcher-kb.wt-active {
|
|
207
210
|
background: var(--tab-hover-bg);
|
|
208
211
|
color: var(--tab-active-fg);
|
|
209
212
|
}
|
|
@@ -212,12 +215,12 @@
|
|
|
212
215
|
Ctrl key uses) while a Ctrl press is pending, so the modifier stays visible
|
|
213
216
|
even with the key grid — and its own Ctrl button — closed. After .wt-active so
|
|
214
217
|
the armed fill wins when the grid is also open. */
|
|
215
|
-
.wt-switcher-kb.wt-armed {
|
|
218
|
+
:where(.wt-root) .wt-switcher-kb.wt-armed {
|
|
216
219
|
background: var(--text);
|
|
217
220
|
color: var(--bg);
|
|
218
221
|
}
|
|
219
222
|
|
|
220
|
-
.wt-switcher-expanded .wt-switcher-kb {
|
|
223
|
+
:where(.wt-root) .wt-switcher-expanded .wt-switcher-kb {
|
|
221
224
|
width: 0;
|
|
222
225
|
/* Absorb the flex gap the collapsed button would otherwise leave behind. */
|
|
223
226
|
margin-inline-start: calc(-1 * var(--sp-1));
|
|
@@ -230,9 +233,9 @@
|
|
|
230
233
|
absolutely placed in the top-right corner. It only earns its place with ≥2
|
|
231
234
|
tabs (a single tab has nothing to switch to), so it is COLLAPSED to zero width
|
|
232
235
|
by default and revealed by .wt-switcher-multi (the tabs feature sets it on tab
|
|
233
|
-
count); the appear/collapse is animated in
|
|
236
|
+
count); the appear/collapse is animated in 40-animations.css and the active
|
|
234
237
|
chip shrinks/grows to fill the freed space in lockstep (flex layout). */
|
|
235
|
-
.wt-switcher-switch {
|
|
238
|
+
:where(.wt-root) .wt-switcher-switch {
|
|
236
239
|
position: relative;
|
|
237
240
|
flex: none;
|
|
238
241
|
width: 0;
|
|
@@ -247,7 +250,7 @@
|
|
|
247
250
|
}
|
|
248
251
|
|
|
249
252
|
/* Revealed once a second tab exists: full 44px box, interactive, no gap eat. */
|
|
250
|
-
.wt-switcher-multi .wt-switcher-switch {
|
|
253
|
+
:where(.wt-root) .wt-switcher-multi .wt-switcher-switch {
|
|
251
254
|
width: var(--touch-target);
|
|
252
255
|
opacity: 1;
|
|
253
256
|
pointer-events: auto;
|
|
@@ -256,10 +259,10 @@
|
|
|
256
259
|
|
|
257
260
|
/* When the tab list expands the switch button collapses too (its job is done:
|
|
258
261
|
the list is open), mirroring the keyboard button's collapse in the same
|
|
259
|
-
animation (
|
|
262
|
+
animation (40-animations.css animates width + margin + opacity). Placed after
|
|
260
263
|
the .wt-switcher-multi reveal so it wins when both apply (expanded, two or
|
|
261
264
|
more tabs). */
|
|
262
|
-
.wt-switcher-expanded .wt-switcher-switch {
|
|
265
|
+
:where(.wt-root) .wt-switcher-expanded .wt-switcher-switch {
|
|
263
266
|
width: 0;
|
|
264
267
|
opacity: 0;
|
|
265
268
|
margin-inline-start: calc(-1 * var(--sp-1));
|
|
@@ -267,17 +270,17 @@
|
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
@media (hover: hover) {
|
|
270
|
-
.wt-switcher-switch:hover {
|
|
273
|
+
:where(.wt-root) .wt-switcher-switch:hover {
|
|
271
274
|
background: var(--tab-hover-bg);
|
|
272
275
|
}
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
/* Latest-wins background-tab notification dot on the switch button — the moved
|
|
276
279
|
attention cue. Amber = a background terminal needs input, green = a background
|
|
277
|
-
turn finished. Reuses the exact .wt-status-dot colours (
|
|
280
|
+
turn finished. Reuses the exact .wt-status-dot colours (30-tabs.css) so it
|
|
278
281
|
matches the per-tab dots. Hidden until the tabs feature sets data-status;
|
|
279
282
|
cleared when the list opens (expandSwitcher). */
|
|
280
|
-
.wt-switcher-switch-dot {
|
|
283
|
+
:where(.wt-root) .wt-switcher-switch-dot {
|
|
281
284
|
position: absolute;
|
|
282
285
|
top: 6px;
|
|
283
286
|
right: 6px;
|
|
@@ -285,10 +288,10 @@
|
|
|
285
288
|
|
|
286
289
|
/* The dot carries the .wt-status-dot class, so its size, shape, and colour come
|
|
287
290
|
straight from the shared .wt-status-dot[data-status="input"|"done"] rules
|
|
288
|
-
(
|
|
291
|
+
(30-tabs.css) — the SAME dot colours every tab already uses, defined once.
|
|
289
292
|
.wt-status-dot is display:none by default; force it visible only while a
|
|
290
293
|
notification is set (data-status present). */
|
|
291
|
-
.wt-switcher-switch-dot[data-status] {
|
|
294
|
+
:where(.wt-root) .wt-switcher-switch-dot[data-status] {
|
|
292
295
|
display: block;
|
|
293
296
|
}
|
|
294
297
|
|
|
@@ -296,72 +299,54 @@
|
|
|
296
299
|
permanent and prominent. Hover fill is gated to real hover pointers: on touch,
|
|
297
300
|
:hover sticks after a tap (no pointer-leave), so the purple would linger — the
|
|
298
301
|
press feedback there comes from :active, which clears on release. */
|
|
299
|
-
.wt-switcher-new {
|
|
302
|
+
:where(.wt-root) .wt-switcher-new {
|
|
300
303
|
flex: none;
|
|
301
304
|
color: var(--accent);
|
|
302
305
|
}
|
|
303
306
|
|
|
304
307
|
@media (hover: hover) {
|
|
305
|
-
.wt-switcher-new:hover {
|
|
308
|
+
:where(.wt-root) .wt-switcher-new:hover {
|
|
306
309
|
background: var(--tab-hover-bg);
|
|
307
310
|
}
|
|
308
311
|
}
|
|
309
312
|
|
|
310
|
-
.wt-switcher-new:active {
|
|
313
|
+
:where(.wt-root) .wt-switcher-new:active {
|
|
311
314
|
background: var(--tab-hover-bg);
|
|
312
315
|
}
|
|
313
316
|
|
|
314
317
|
/* ===== Externally-driven key grid (tabbed presets, WS3) =====
|
|
315
|
-
In the tabbed presets the mobile toolbar hides its own toggle (
|
|
316
|
-
keyboard button drives it
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
In the tabbed presets the mobile toolbar hides its own toggle (a tab-chrome
|
|
319
|
+
keyboard button drives it — the switcher's on a narrow phone, the strip's
|
|
320
|
+
.wt-tab-kb on a wide touchscreen) and the grid docks bottom-right above the
|
|
321
|
+
tab chrome, to the left of where scroll-to-bottom sits. It collapses to
|
|
322
|
+
fully hidden, since there is no in-grid toggle to keep visible. The BASE
|
|
323
|
+
rules here are the wide-touchscreen (strip) placement — above --wt-tabbar-h;
|
|
324
|
+
the .wt-narrow override below re-anchors above the switcher bar instead
|
|
325
|
+
(--wt-switcher-h). Everything else (toggle hidden, right offset, grid
|
|
326
|
+
columns, collapsed fully hidden) is identical in both modes. */
|
|
327
|
+
@media (pointer: coarse) {
|
|
328
|
+
:where(.wt-root) .wt-toolbar-external .kb-toggle {
|
|
321
329
|
display: none;
|
|
322
330
|
}
|
|
323
331
|
|
|
324
|
-
.key-toolbar.wt-toolbar-external {
|
|
332
|
+
:where(.wt-root) .key-toolbar.wt-toolbar-external {
|
|
325
333
|
top: auto;
|
|
326
334
|
right: calc(var(--sp-3) + var(--touch-target) + var(--sp-2));
|
|
327
335
|
bottom: calc(
|
|
328
|
-
var(--wt-
|
|
336
|
+
var(--wt-tabbar-h, 44px) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
|
|
329
337
|
);
|
|
330
338
|
grid-template-columns: repeat(4, var(--touch-target));
|
|
331
339
|
}
|
|
332
340
|
|
|
333
|
-
.key-toolbar.wt-toolbar-external.collapsed {
|
|
341
|
+
:where(.wt-root) .key-toolbar.wt-toolbar-external.collapsed {
|
|
334
342
|
display: none;
|
|
335
343
|
}
|
|
336
|
-
}
|
|
337
344
|
|
|
338
|
-
/*
|
|
339
|
-
|
|
340
|
-
by the same factory as the switcher's — see css/05-tabs.css + tabs.ts) to open
|
|
341
|
-
the key grid. So, exactly like switcher mode, the toolbar's own toggle is
|
|
342
|
-
hidden and the collapsed grid fully hidden — the tab-strip keyboard button is
|
|
343
|
-
the sole control (this replaces the old CSS-only workaround that un-hid the
|
|
344
|
-
toolbar's own toggle here). The grid still docks bottom-right ABOVE the strip:
|
|
345
|
-
above the tabs (never overlapping them, even when many are open and the strip
|
|
346
|
-
scrolls) and to the left of the scroll-to-bottom FAB (same right offset as
|
|
347
|
-
switcher mode). Complements the strip-shown breakpoint in css/05-tabs.css;
|
|
348
|
-
keep in lockstep with SWITCHER_MEDIA in src/features/tabs.ts. */
|
|
349
|
-
@media (pointer: coarse) and (width >= 601px) {
|
|
350
|
-
.wt-toolbar-external .kb-toggle {
|
|
351
|
-
display: none;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
.key-toolbar.wt-toolbar-external {
|
|
355
|
-
top: auto;
|
|
356
|
-
right: calc(var(--sp-3) + var(--touch-target) + var(--sp-2));
|
|
345
|
+
/* Narrow phone (mobile switcher mode): dock above the switcher bar. */
|
|
346
|
+
:where(.wt-root.wt-narrow) .key-toolbar.wt-toolbar-external {
|
|
357
347
|
bottom: calc(
|
|
358
|
-
var(--wt-
|
|
348
|
+
var(--wt-switcher-h) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
|
|
359
349
|
);
|
|
360
|
-
grid-template-columns: repeat(4, var(--touch-target));
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
.key-toolbar.wt-toolbar-external.collapsed {
|
|
364
|
-
display: none;
|
|
365
350
|
}
|
|
366
351
|
}
|
|
367
352
|
|
|
@@ -369,12 +354,12 @@
|
|
|
369
354
|
One row per OTHER tab in the expanded list: a stretched select target (dot +
|
|
370
355
|
label) with the close (x) laid inside at the right, mirroring the desktop
|
|
371
356
|
tab's merged chrome. */
|
|
372
|
-
.wt-switcher-row {
|
|
357
|
+
:where(.wt-root) .wt-switcher-row {
|
|
373
358
|
position: relative;
|
|
374
359
|
display: flex;
|
|
375
360
|
}
|
|
376
361
|
|
|
377
|
-
.wt-switcher-row-select {
|
|
362
|
+
:where(.wt-root) .wt-switcher-row-select {
|
|
378
363
|
flex: 1 1 auto;
|
|
379
364
|
display: flex;
|
|
380
365
|
align-items: center;
|
|
@@ -394,7 +379,7 @@
|
|
|
394
379
|
}
|
|
395
380
|
|
|
396
381
|
@media (hover: hover) {
|
|
397
|
-
.wt-switcher-row-select:hover {
|
|
382
|
+
:where(.wt-root) .wt-switcher-row-select:hover {
|
|
398
383
|
background: color-mix(in oklch, var(--tab-bg), var(--text) 15%);
|
|
399
384
|
}
|
|
400
385
|
}
|
|
@@ -403,11 +388,11 @@
|
|
|
403
388
|
chip (the same 15% --text lift the desktop tabs use), not the translucent
|
|
404
389
|
--tab-hover-bg wash that would erase the chip fill. The active tab is set apart
|
|
405
390
|
by its accent border, so a lit inactive row is never mistaken for it. */
|
|
406
|
-
.wt-switcher-row-select:active {
|
|
391
|
+
:where(.wt-root) .wt-switcher-row-select:active {
|
|
407
392
|
background: color-mix(in oklch, var(--tab-bg), var(--text) 15%);
|
|
408
393
|
}
|
|
409
394
|
|
|
410
|
-
.wt-switcher-row-label {
|
|
395
|
+
:where(.wt-root) .wt-switcher-row-label {
|
|
411
396
|
flex: 1 1 auto;
|
|
412
397
|
overflow: hidden;
|
|
413
398
|
text-overflow: ellipsis;
|
|
@@ -415,7 +400,7 @@
|
|
|
415
400
|
}
|
|
416
401
|
|
|
417
402
|
/* The close sits flush at the row's right edge (no gap), vertically centered. */
|
|
418
|
-
.wt-switcher-row-close {
|
|
403
|
+
:where(.wt-root) .wt-switcher-row-close {
|
|
419
404
|
position: absolute;
|
|
420
405
|
top: 50%;
|
|
421
406
|
right: 0;
|
|
@@ -427,7 +412,7 @@
|
|
|
427
412
|
when closing a tab shifts the list under the finger the newly-under close
|
|
428
413
|
would latch white. Touch feedback comes from the surrounding row's :active. */
|
|
429
414
|
@media (hover: hover) {
|
|
430
|
-
.wt-switcher-row-close:hover {
|
|
415
|
+
:where(.wt-root) .wt-switcher-row-close:hover {
|
|
431
416
|
color: var(--text);
|
|
432
417
|
}
|
|
433
418
|
}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
base rules in 03-06 carry the resting state, and these rules add the
|
|
5
5
|
transitions and keyframes on top. A few pieces of motion are intentionally NOT
|
|
6
6
|
gated on .wt-animate and animate unconditionally: terminal-core motion (cursor
|
|
7
|
-
and SGR blink) and the mobile key toolbar open/close in
|
|
8
|
-
working-status-dot pulse in
|
|
9
|
-
WCAG 1.4.1). prefers-reduced-motion still defeats all of it via the
|
|
10
|
-
01-
|
|
7
|
+
and SGR blink) and the mobile key toolbar open/close in 23-toolbar.css, plus the
|
|
8
|
+
working-status-dot pulse in 30-tabs.css (motion is its only non-hue cue, per
|
|
9
|
+
WCAG 1.4.1). prefers-reduced-motion still defeats all of it via the scoped
|
|
10
|
+
kill-switch in 01-scope.css. Keeping the chrome-surface motion in one file is the
|
|
11
11
|
design-system seam for "does this chrome animate, and how". */
|
|
12
12
|
|
|
13
13
|
/* Fade the toast OUT on hide (display allow-discrete holds display:block for
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
shared opacity transition would be inert while the `display allow-discrete`
|
|
22
22
|
half held them fully visible for --dur-standard before popping out on hide.
|
|
23
23
|
They show and hide instantly. */
|
|
24
|
-
.wt-animate .wt-toast {
|
|
24
|
+
:where(.wt-root.wt-animate) .wt-toast {
|
|
25
25
|
transition:
|
|
26
26
|
opacity var(--dur-standard) var(--ease-standard),
|
|
27
27
|
display var(--dur-standard) allow-discrete;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/* Tab chrome micro-transitions (hover/active color shifts). */
|
|
31
|
-
.wt-animate .wt-tab,
|
|
32
|
-
.wt-animate .wt-tab-close,
|
|
33
|
-
.wt-animate .wt-tab-new {
|
|
31
|
+
:where(.wt-root.wt-animate) .wt-tab,
|
|
32
|
+
:where(.wt-root.wt-animate) .wt-tab-close,
|
|
33
|
+
:where(.wt-root.wt-animate) .wt-tab-new {
|
|
34
34
|
transition:
|
|
35
35
|
background-color var(--dur-micro) var(--ease-standard),
|
|
36
36
|
color var(--dur-micro) var(--ease-standard);
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
immediately with no exit animation, so the strip reflows in one frame (a
|
|
42
42
|
delayed exit made the "+" teleport and put a last-tab replacement in the wrong
|
|
43
43
|
slot). scale() is a transform, so the enter never shifts the "+". */
|
|
44
|
-
.wt-animate .wt-tab-enter {
|
|
44
|
+
:where(.wt-root.wt-animate) .wt-tab-enter {
|
|
45
45
|
animation: wt-tab-in var(--dur-enter) var(--ease-enter);
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
— animating only max-height let the padding snap away instantly at the start
|
|
59
59
|
of a collapse (a choppy, two-step close). The tabs feature clears the rows
|
|
60
60
|
after the collapse. */
|
|
61
|
-
.wt-animate .wt-switcher-list {
|
|
61
|
+
:where(.wt-root.wt-animate) .wt-switcher-list {
|
|
62
62
|
transition:
|
|
63
63
|
max-height var(--dur-enter) var(--ease-standard),
|
|
64
64
|
padding var(--dur-enter) var(--ease-standard);
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
/* When the tab list expands, the keyboard button collapses to zero width and
|
|
68
68
|
fades (the key grid is closed in JS); the active row flex-grows into the freed
|
|
69
69
|
space in lockstep as the button's width animates. */
|
|
70
|
-
.wt-animate .wt-switcher-kb {
|
|
70
|
+
:where(.wt-root.wt-animate) .wt-switcher-kb {
|
|
71
71
|
transition:
|
|
72
72
|
width var(--dur-enter) var(--ease-standard),
|
|
73
73
|
margin var(--dur-enter) var(--ease-standard),
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
at one tab (.wt-switcher-multi, tabs feature). Animate its width + margin +
|
|
79
79
|
opacity so it slides in from zero width — the active-tab chip shrinks to make
|
|
80
80
|
room in lockstep (flex) — rather than popping. Mirrors the keyboard button. */
|
|
81
|
-
.wt-animate .wt-switcher-switch {
|
|
81
|
+
:where(.wt-root.wt-animate) .wt-switcher-switch {
|
|
82
82
|
transition:
|
|
83
83
|
width var(--dur-enter) var(--ease-standard),
|
|
84
84
|
margin var(--dur-enter) var(--ease-standard),
|
|
@@ -89,15 +89,15 @@
|
|
|
89
89
|
reads as a change rather than a flicker. A swipe additionally slides the
|
|
90
90
|
content in from the swipe's direction (next from the right, prev from the
|
|
91
91
|
left, WS5). */
|
|
92
|
-
.wt-animate .term.wt-switching .term-output {
|
|
92
|
+
:where(.wt-root.wt-animate) .term.wt-switching .term-output {
|
|
93
93
|
animation: wt-switch-in var(--dur-standard) var(--ease-standard);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
.wt-animate .term.wt-switching-next .term-output {
|
|
96
|
+
:where(.wt-root.wt-animate) .term.wt-switching-next .term-output {
|
|
97
97
|
animation: wt-switch-next var(--dur-standard) var(--ease-standard);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
.wt-animate .term.wt-switching-prev .term-output {
|
|
100
|
+
:where(.wt-root.wt-animate) .term.wt-switching-prev .term-output {
|
|
101
101
|
animation: wt-switch-prev var(--dur-standard) var(--ease-standard);
|
|
102
102
|
}
|
|
103
103
|
|
package/css/MANIFEST
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
# web-terminal-ui CSS bundle
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
1
|
+
# web-terminal-ui CSS — the REFERENCE FULL-PAGE bundle.
|
|
2
|
+
# What the full-page product serves (web-terminal-server, web-terminal-kiro,
|
|
3
|
+
# scaffold/index.html): the page kit (page.css — html/body reset + @font-face;
|
|
4
|
+
# full-page hosts ONLY) plus the complete tabbed component set. An EMBEDDING
|
|
5
|
+
# consumer must NOT serve this file's bundle — it concatenates the per-preset
|
|
6
|
+
# manifest matching its composition instead (MANIFEST.single / MANIFEST.touch /
|
|
7
|
+
# MANIFEST.tabbed), which contains no page-level rules.
|
|
8
|
+
# Files concatenate in declared order; the order IS the cascade.
|
|
9
|
+
page.css
|
|
5
10
|
00-tokens.css
|
|
6
|
-
01-
|
|
7
|
-
02-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
01-scope.css
|
|
12
|
+
02-terminal.css
|
|
13
|
+
10-primitives.css
|
|
14
|
+
20-connection-banner.css
|
|
15
|
+
21-scroll-to-bottom.css
|
|
16
|
+
22-context-menu.css
|
|
17
|
+
23-toolbar.css
|
|
18
|
+
30-tabs.css
|
|
19
|
+
31-switcher.css
|
|
20
|
+
40-animations.css
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Component bundle for presetSingle() compositions (no toolbar, no tabs, no
|
|
2
|
+
# animations feature). Library styles only — root-scoped, no page-level rules;
|
|
3
|
+
# the host page owns its own reset/fonts (full-page hosts use the reference
|
|
4
|
+
# MANIFEST instead, whose page.css carries those).
|
|
5
|
+
# Files concatenate in declared order; the order IS the cascade.
|
|
6
|
+
00-tokens.css
|
|
7
|
+
01-scope.css
|
|
8
|
+
02-terminal.css
|
|
9
|
+
10-primitives.css
|
|
10
|
+
20-connection-banner.css
|
|
11
|
+
21-scroll-to-bottom.css
|
|
12
|
+
22-context-menu.css
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Component bundle for presetTabbed() / presetAgentTabbed() compositions: the
|
|
2
|
+
# touch set plus tabs (strip + mobile switcher) and the animations feature's
|
|
3
|
+
# motion. Library styles only — root-scoped, no page-level rules. A full-page
|
|
4
|
+
# host serves the reference MANIFEST instead (this set + page.css).
|
|
5
|
+
# Files concatenate in declared order; the order IS the cascade.
|
|
6
|
+
00-tokens.css
|
|
7
|
+
01-scope.css
|
|
8
|
+
02-terminal.css
|
|
9
|
+
10-primitives.css
|
|
10
|
+
20-connection-banner.css
|
|
11
|
+
21-scroll-to-bottom.css
|
|
12
|
+
22-context-menu.css
|
|
13
|
+
23-toolbar.css
|
|
14
|
+
30-tabs.css
|
|
15
|
+
31-switcher.css
|
|
16
|
+
40-animations.css
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Component bundle for presetTouch() compositions (single + the mobile key
|
|
2
|
+
# toolbar). Library styles only — root-scoped, no page-level rules; the host
|
|
3
|
+
# page owns its own reset/fonts (vibekit's embedded panel serves exactly this).
|
|
4
|
+
# Files concatenate in declared order; the order IS the cascade.
|
|
5
|
+
00-tokens.css
|
|
6
|
+
01-scope.css
|
|
7
|
+
02-terminal.css
|
|
8
|
+
10-primitives.css
|
|
9
|
+
20-connection-banner.css
|
|
10
|
+
21-scroll-to-bottom.css
|
|
11
|
+
22-context-menu.css
|
|
12
|
+
23-toolbar.css
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
/* =====
|
|
1
|
+
/* ===== Page kit (full-page hosts ONLY — never ship this to an embedder) =====
|
|
2
|
+
The page-level half of the full-page terminal product: what the HOST PAGE
|
|
3
|
+
owns when the terminal IS the page (web-terminal-server, web-terminal-kiro,
|
|
4
|
+
scaffold/index.html). The component bundle (00-10) never touches the host
|
|
5
|
+
document; everything here is deliberately page-scoped. An embedding consumer
|
|
6
|
+
(vibekit) serves a per-preset manifest instead and owns its own page.
|
|
7
|
+
Concatenated FIRST by the reference MANIFEST. */
|
|
8
|
+
|
|
2
9
|
*,
|
|
3
10
|
*::before,
|
|
4
11
|
*::after {
|
|
@@ -11,7 +18,10 @@ body {
|
|
|
11
18
|
padding: 0;
|
|
12
19
|
height: 100%;
|
|
13
20
|
overflow: hidden;
|
|
14
|
-
|
|
21
|
+
/* The pre-JS backdrop (the loading state paints over it). Matches the
|
|
22
|
+
component's --bg default (css/00-tokens.css, oklch(0% 0 0deg)); a themed
|
|
23
|
+
full-page host overrides both together. */
|
|
24
|
+
background: #000;
|
|
15
25
|
color-scheme: dark;
|
|
16
26
|
-webkit-font-smoothing: antialiased;
|
|
17
27
|
/* Disable the browser's landscape text autosizing ("text inflation"): without
|
|
@@ -29,23 +39,12 @@ body {
|
|
|
29
39
|
touch-action: pan-x pan-y;
|
|
30
40
|
}
|
|
31
41
|
|
|
32
|
-
/*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@
|
|
37
|
-
|
|
38
|
-
*::before,
|
|
39
|
-
*::after {
|
|
40
|
-
animation-duration: 0.01ms !important;
|
|
41
|
-
animation-iteration-count: 1 !important;
|
|
42
|
-
transition-duration: 0.01ms !important;
|
|
43
|
-
scroll-behavior: auto !important;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/* stylelint-enable declaration-no-important, time-min-milliseconds */
|
|
47
|
-
|
|
48
|
-
/* ===== Fonts ===== */
|
|
42
|
+
/* ===== Fonts =====
|
|
43
|
+
The terminal web font, served by the full-page host at /vendor/fonts (both
|
|
44
|
+
web-terminal-server and web-terminal-kiro ship these files there; see their
|
|
45
|
+
Dockerfiles). An embedder does not serve this file, so it never inherits
|
|
46
|
+
these URLs — it either ships its own @font-face or lets --font-mono fall back
|
|
47
|
+
to the platform monospace stack. */
|
|
49
48
|
@font-face {
|
|
50
49
|
font-family: "MonaspiceNe NFM";
|
|
51
50
|
src: url("/vendor/fonts/MonaspiceNeNerdFontMono-Regular.otf") format("opentype");
|
package/package.json
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.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": "GPL-3.0-or-later",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts",
|
|
9
|
-
"./presets": "./src/presets.ts"
|
|
9
|
+
"./presets": "./src/presets.ts",
|
|
10
|
+
"./presets/single": "./src/presets/single.ts",
|
|
11
|
+
"./presets/touch": "./src/presets/touch.ts",
|
|
12
|
+
"./presets/tabbed": "./src/presets/tabbed.ts",
|
|
13
|
+
"./presets/agent-tabbed": "./src/presets/agent-tabbed.ts",
|
|
14
|
+
"./features/clipboard": "./src/features/clipboard.ts",
|
|
15
|
+
"./features/context-menu": "./src/features/context-menu.ts",
|
|
16
|
+
"./features/scroll-to-bottom": "./src/features/scroll-to-bottom.ts",
|
|
17
|
+
"./features/predictive-echo": "./src/features/predictive-echo.ts",
|
|
18
|
+
"./features/connection-banner": "./src/features/connection-banner.ts",
|
|
19
|
+
"./features/mobile-toolbar": "./src/features/mobile-toolbar.ts",
|
|
20
|
+
"./features/tabs": "./src/features/tabs/index.ts",
|
|
21
|
+
"./features/activity-monitor": "./src/features/activity-monitor.ts",
|
|
22
|
+
"./features/animations": "./src/features/animations.ts"
|
|
10
23
|
},
|
|
11
24
|
"sideEffects": [
|
|
12
25
|
"**/*.css"
|
|
@@ -25,23 +38,26 @@
|
|
|
25
38
|
"verify": "bash scripts/verify.sh"
|
|
26
39
|
},
|
|
27
40
|
"peerDependencies": {
|
|
28
|
-
"@cplieger/web-terminal-engine": "
|
|
41
|
+
"@cplieger/web-terminal-engine": "^2.8.0"
|
|
29
42
|
},
|
|
30
43
|
"devDependencies": {
|
|
31
44
|
"@eslint/js": "10.0.1",
|
|
45
|
+
"@stryker-mutator/core": "9.6.1",
|
|
46
|
+
"@stryker-mutator/typescript-checker": "9.6.1",
|
|
47
|
+
"@stryker-mutator/vitest-runner": "9.6.1",
|
|
32
48
|
"@types/node": "25.9.5",
|
|
33
49
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
34
50
|
"@vitest/coverage-v8": "4.1.10",
|
|
35
51
|
"eslint": "10.7.0",
|
|
36
52
|
"fast-check": "4.9.0",
|
|
37
53
|
"happy-dom": "20.10.6",
|
|
38
|
-
"html-validate": "11.5.
|
|
54
|
+
"html-validate": "11.5.6",
|
|
39
55
|
"knip": "6.26.0",
|
|
40
56
|
"prettier": "3.9.5",
|
|
41
57
|
"stylelint": "17.14.0",
|
|
42
58
|
"stylelint-config-standard": "40.0.0",
|
|
43
59
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
44
|
-
"typescript-eslint": "8.
|
|
60
|
+
"typescript-eslint": "8.64.0",
|
|
45
61
|
"vitest": "4.1.10"
|
|
46
62
|
},
|
|
47
63
|
"publishConfig": {
|