@cplieger/web-terminal-ui 3.5.0 → 4.1.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 +60 -27
- package/css/00-tokens.css +18 -2
- package/css/01-scope.css +76 -0
- package/css/{02-app.css → 02-terminal.css} +113 -239
- package/css/10-primitives.css +98 -0
- 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} +127 -66
- package/css/{06-mobile.css → 31-switcher.css} +85 -80
- 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 +19 -6
- package/scaffold/index.html +5 -0
- package/src/features/activity-monitor.ts +32 -2
- package/src/features/connection-banner.ts +1 -0
- package/src/features/context-menu.ts +6 -33
- package/src/features/menu-position.ts +50 -0
- package/src/features/mobile-toolbar.ts +5 -4
- package/src/features/{tabs.ts → tabs/index.ts} +310 -721
- package/src/features/tabs/model.ts +342 -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 +16 -6
- package/src/kernel/kernel.ts +170 -59
- package/src/kernel/types.ts +68 -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/03-primitives.css +0 -83
- 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,12 +76,16 @@
|
|
|
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. */
|
|
80
83
|
max-height: var(--wt-list-h, 50dvh);
|
|
81
84
|
overflow-y: auto;
|
|
85
|
+
/* Reaching the list's end must not hand the touch scroll to an embedding
|
|
86
|
+
page (wt-container mode) — the tray sits over the terminal, and a chained
|
|
87
|
+
scroll behind an open tray is disorienting. */
|
|
88
|
+
overscroll-behavior-y: contain;
|
|
82
89
|
/* No top padding: the bar's --sp-2 bottom padding already sits between the
|
|
83
90
|
active tab and the first row, so the seam equals the --sp-2 inter-row gap. */
|
|
84
91
|
padding-top: 0;
|
|
@@ -90,7 +97,7 @@
|
|
|
90
97
|
sibling over the surface's reserved right padding). The wrap is the flex item
|
|
91
98
|
that grows in the bar (and fills the space when the keyboard button collapses
|
|
92
99
|
on expand). */
|
|
93
|
-
.wt-switcher-current-wrap {
|
|
100
|
+
:where(.wt-root) .wt-switcher-current-wrap {
|
|
94
101
|
position: relative;
|
|
95
102
|
flex: 1 1 auto;
|
|
96
103
|
display: flex;
|
|
@@ -101,7 +108,7 @@
|
|
|
101
108
|
overflow: hidden;
|
|
102
109
|
}
|
|
103
110
|
|
|
104
|
-
.wt-switcher-current {
|
|
111
|
+
:where(.wt-root) .wt-switcher-current {
|
|
105
112
|
position: relative;
|
|
106
113
|
flex: 1 1 auto;
|
|
107
114
|
display: flex;
|
|
@@ -130,7 +137,7 @@
|
|
|
130
137
|
padding, mirroring the listed rows' close. Hover fill only on real hover
|
|
131
138
|
pointers so it never sticks white after a touch (a removed tab shifts the
|
|
132
139
|
list under the finger otherwise). */
|
|
133
|
-
.wt-switcher-current-close {
|
|
140
|
+
:where(.wt-root) .wt-switcher-current-close {
|
|
134
141
|
position: absolute;
|
|
135
142
|
/* Centre vertically via top/bottom (not a translateY transform), so transform
|
|
136
143
|
is free for the swipe translate — the close moves with the dot + label. And
|
|
@@ -142,14 +149,14 @@
|
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
@media (hover: hover) {
|
|
145
|
-
.wt-switcher-current-close:hover {
|
|
152
|
+
:where(.wt-root) .wt-switcher-current-close:hover {
|
|
146
153
|
color: var(--text);
|
|
147
154
|
}
|
|
148
155
|
}
|
|
149
156
|
|
|
150
157
|
/* Press feedback: brighten the active fill slightly (a lighter press over an
|
|
151
158
|
already-filled row). */
|
|
152
|
-
.wt-switcher-current:active {
|
|
159
|
+
:where(.wt-root) .wt-switcher-current:active {
|
|
153
160
|
background: color-mix(in oklch, var(--tab-active-bg), var(--tab-active-fg) 12%);
|
|
154
161
|
}
|
|
155
162
|
|
|
@@ -157,7 +164,7 @@
|
|
|
157
164
|
parts out; a horizontal swipe translates it (clipped by the row's
|
|
158
165
|
overflow: hidden) so the active tab area physically slides with the finger,
|
|
159
166
|
and the incoming tab's label slides in from the swipe direction on commit. */
|
|
160
|
-
.wt-switcher-current-inner {
|
|
167
|
+
:where(.wt-root) .wt-switcher-current-inner {
|
|
161
168
|
display: flex;
|
|
162
169
|
align-items: center;
|
|
163
170
|
gap: var(--sp-2);
|
|
@@ -165,7 +172,7 @@
|
|
|
165
172
|
min-width: 0;
|
|
166
173
|
}
|
|
167
174
|
|
|
168
|
-
.wt-switcher-label {
|
|
175
|
+
:where(.wt-root) .wt-switcher-label {
|
|
169
176
|
flex: 1 1 auto;
|
|
170
177
|
overflow: hidden;
|
|
171
178
|
text-overflow: ellipsis;
|
|
@@ -178,7 +185,7 @@
|
|
|
178
185
|
just the wt-btn min-width) makes it animatable: when the tab list expands the
|
|
179
186
|
button collapses to zero width and fades out (the key grid is closed too, in
|
|
180
187
|
JS), and the active row flex-grows to fill the freed space. */
|
|
181
|
-
.wt-switcher-kb {
|
|
188
|
+
:where(.wt-root) .wt-switcher-kb {
|
|
182
189
|
/* Rigid 44px box like the "+" and switch buttons (flex: none), so a long
|
|
183
190
|
active-tab title that overflows the bar is absorbed entirely by the
|
|
184
191
|
ellipsizing active-tab field, not shared with this button. It is otherwise
|
|
@@ -199,11 +206,11 @@
|
|
|
199
206
|
overflow: hidden;
|
|
200
207
|
}
|
|
201
208
|
|
|
202
|
-
.wt-switcher-kb[hidden] {
|
|
209
|
+
:where(.wt-root) .wt-switcher-kb[hidden] {
|
|
203
210
|
display: none;
|
|
204
211
|
}
|
|
205
212
|
|
|
206
|
-
.wt-switcher-kb.wt-active {
|
|
213
|
+
:where(.wt-root) .wt-switcher-kb.wt-active {
|
|
207
214
|
background: var(--tab-hover-bg);
|
|
208
215
|
color: var(--tab-active-fg);
|
|
209
216
|
}
|
|
@@ -212,12 +219,12 @@
|
|
|
212
219
|
Ctrl key uses) while a Ctrl press is pending, so the modifier stays visible
|
|
213
220
|
even with the key grid — and its own Ctrl button — closed. After .wt-active so
|
|
214
221
|
the armed fill wins when the grid is also open. */
|
|
215
|
-
.wt-switcher-kb.wt-armed {
|
|
222
|
+
:where(.wt-root) .wt-switcher-kb.wt-armed {
|
|
216
223
|
background: var(--text);
|
|
217
224
|
color: var(--bg);
|
|
218
225
|
}
|
|
219
226
|
|
|
220
|
-
.wt-switcher-expanded .wt-switcher-kb {
|
|
227
|
+
:where(.wt-root) .wt-switcher-expanded .wt-switcher-kb {
|
|
221
228
|
width: 0;
|
|
222
229
|
/* Absorb the flex gap the collapsed button would otherwise leave behind. */
|
|
223
230
|
margin-inline-start: calc(-1 * var(--sp-1));
|
|
@@ -230,9 +237,9 @@
|
|
|
230
237
|
absolutely placed in the top-right corner. It only earns its place with ≥2
|
|
231
238
|
tabs (a single tab has nothing to switch to), so it is COLLAPSED to zero width
|
|
232
239
|
by default and revealed by .wt-switcher-multi (the tabs feature sets it on tab
|
|
233
|
-
count); the appear/collapse is animated in
|
|
240
|
+
count); the appear/collapse is animated in 40-animations.css and the active
|
|
234
241
|
chip shrinks/grows to fill the freed space in lockstep (flex layout). */
|
|
235
|
-
.wt-switcher-switch {
|
|
242
|
+
:where(.wt-root) .wt-switcher-switch {
|
|
236
243
|
position: relative;
|
|
237
244
|
flex: none;
|
|
238
245
|
width: 0;
|
|
@@ -247,7 +254,7 @@
|
|
|
247
254
|
}
|
|
248
255
|
|
|
249
256
|
/* Revealed once a second tab exists: full 44px box, interactive, no gap eat. */
|
|
250
|
-
.wt-switcher-multi .wt-switcher-switch {
|
|
257
|
+
:where(.wt-root) .wt-switcher-multi .wt-switcher-switch {
|
|
251
258
|
width: var(--touch-target);
|
|
252
259
|
opacity: 1;
|
|
253
260
|
pointer-events: auto;
|
|
@@ -256,10 +263,10 @@
|
|
|
256
263
|
|
|
257
264
|
/* When the tab list expands the switch button collapses too (its job is done:
|
|
258
265
|
the list is open), mirroring the keyboard button's collapse in the same
|
|
259
|
-
animation (
|
|
266
|
+
animation (40-animations.css animates width + margin + opacity). Placed after
|
|
260
267
|
the .wt-switcher-multi reveal so it wins when both apply (expanded, two or
|
|
261
268
|
more tabs). */
|
|
262
|
-
.wt-switcher-expanded .wt-switcher-switch {
|
|
269
|
+
:where(.wt-root) .wt-switcher-expanded .wt-switcher-switch {
|
|
263
270
|
width: 0;
|
|
264
271
|
opacity: 0;
|
|
265
272
|
margin-inline-start: calc(-1 * var(--sp-1));
|
|
@@ -267,17 +274,17 @@
|
|
|
267
274
|
}
|
|
268
275
|
|
|
269
276
|
@media (hover: hover) {
|
|
270
|
-
.wt-switcher-switch:hover {
|
|
277
|
+
:where(.wt-root) .wt-switcher-switch:hover {
|
|
271
278
|
background: var(--tab-hover-bg);
|
|
272
279
|
}
|
|
273
280
|
}
|
|
274
281
|
|
|
275
282
|
/* Latest-wins background-tab notification dot on the switch button — the moved
|
|
276
283
|
attention cue. Amber = a background terminal needs input, green = a background
|
|
277
|
-
turn finished. Reuses the exact .wt-status-dot colours (
|
|
284
|
+
turn finished. Reuses the exact .wt-status-dot colours (30-tabs.css) so it
|
|
278
285
|
matches the per-tab dots. Hidden until the tabs feature sets data-status;
|
|
279
286
|
cleared when the list opens (expandSwitcher). */
|
|
280
|
-
.wt-switcher-switch-dot {
|
|
287
|
+
:where(.wt-root) .wt-switcher-switch-dot {
|
|
281
288
|
position: absolute;
|
|
282
289
|
top: 6px;
|
|
283
290
|
right: 6px;
|
|
@@ -285,10 +292,10 @@
|
|
|
285
292
|
|
|
286
293
|
/* The dot carries the .wt-status-dot class, so its size, shape, and colour come
|
|
287
294
|
straight from the shared .wt-status-dot[data-status="input"|"done"] rules
|
|
288
|
-
(
|
|
295
|
+
(30-tabs.css) — the SAME dot colours every tab already uses, defined once.
|
|
289
296
|
.wt-status-dot is display:none by default; force it visible only while a
|
|
290
297
|
notification is set (data-status present). */
|
|
291
|
-
.wt-switcher-switch-dot[data-status] {
|
|
298
|
+
:where(.wt-root) .wt-switcher-switch-dot[data-status] {
|
|
292
299
|
display: block;
|
|
293
300
|
}
|
|
294
301
|
|
|
@@ -296,72 +303,70 @@
|
|
|
296
303
|
permanent and prominent. Hover fill is gated to real hover pointers: on touch,
|
|
297
304
|
:hover sticks after a tap (no pointer-leave), so the purple would linger — the
|
|
298
305
|
press feedback there comes from :active, which clears on release. */
|
|
299
|
-
.wt-switcher-new {
|
|
306
|
+
:where(.wt-root) .wt-switcher-new {
|
|
300
307
|
flex: none;
|
|
301
308
|
color: var(--accent);
|
|
302
309
|
}
|
|
303
310
|
|
|
304
311
|
@media (hover: hover) {
|
|
305
|
-
.wt-switcher-new:hover {
|
|
312
|
+
:where(.wt-root) .wt-switcher-new:hover {
|
|
306
313
|
background: var(--tab-hover-bg);
|
|
307
314
|
}
|
|
308
315
|
}
|
|
309
316
|
|
|
310
|
-
.wt-switcher-new:active {
|
|
317
|
+
:where(.wt-root) .wt-switcher-new:active {
|
|
311
318
|
background: var(--tab-hover-bg);
|
|
312
319
|
}
|
|
313
320
|
|
|
314
321
|
/* ===== 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
|
-
|
|
322
|
+
In the tabbed presets the mobile toolbar hides its own toggle (a tab-chrome
|
|
323
|
+
keyboard button drives it — the switcher's on a narrow phone, the strip's
|
|
324
|
+
.wt-tab-kb on a wide touchscreen) and the grid docks bottom-right above the
|
|
325
|
+
tab chrome, its RIGHT edge aligned with the keyboard button that opened it,
|
|
326
|
+
so the popup visually hangs off its toggle in every mode (it may overlap the
|
|
327
|
+
scroll-to-bottom button — deliberate: the grid is transient and on top). It
|
|
328
|
+
collapses to fully hidden, since there is no in-grid toggle to keep visible.
|
|
329
|
+
The BASE rules here are the wide-touchscreen (strip) placement — above
|
|
330
|
+
--wt-tabbar-h, right-aligned to the strip's .wt-tab-kb (the bar's last flex
|
|
331
|
+
item, inset --sp-2 by the bar padding); the .wt-narrow overrides below
|
|
332
|
+
re-anchor above the switcher bar (--wt-switcher-h) and shift left past the
|
|
333
|
+
bar controls sitting right of the switcher's kb button: the "+" (always)
|
|
334
|
+
and, once a second tab reveals it, the switch button (.wt-switcher-multi
|
|
335
|
+
from the tabs feature, read here via :has() since the grid is a sibling
|
|
336
|
+
region of the switcher). Everything else (toggle hidden, grid columns,
|
|
337
|
+
collapsed fully hidden) is identical in all modes. */
|
|
338
|
+
@media (pointer: coarse) {
|
|
339
|
+
:where(.wt-root) .wt-toolbar-external .kb-toggle {
|
|
321
340
|
display: none;
|
|
322
341
|
}
|
|
323
342
|
|
|
324
|
-
.key-toolbar.wt-toolbar-external {
|
|
343
|
+
:where(.wt-root) .key-toolbar.wt-toolbar-external {
|
|
325
344
|
top: auto;
|
|
326
|
-
right:
|
|
345
|
+
right: var(--sp-2);
|
|
327
346
|
bottom: calc(
|
|
328
|
-
var(--wt-
|
|
347
|
+
var(--wt-tabbar-h, 44px) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
|
|
329
348
|
);
|
|
330
349
|
grid-template-columns: repeat(4, var(--touch-target));
|
|
331
350
|
}
|
|
332
351
|
|
|
333
|
-
.key-toolbar.wt-toolbar-external.collapsed {
|
|
334
|
-
display: none;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/* Desktop tab strip on a WIDE touchscreen (an iPad): the mobile switcher is
|
|
339
|
-
gone, so the desktop strip carries its OWN keyboard button (.wt-tab-kb, built
|
|
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 {
|
|
352
|
+
:where(.wt-root) .key-toolbar.wt-toolbar-external.collapsed {
|
|
351
353
|
display: none;
|
|
352
354
|
}
|
|
353
355
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
356
|
+
/* Narrow phone (mobile switcher mode): dock above the switcher bar,
|
|
357
|
+
right-aligned to the switcher's kb button — one 44px control (+ gap)
|
|
358
|
+
in from the edge past the "+"... */
|
|
359
|
+
:where(.wt-root.wt-narrow) .key-toolbar.wt-toolbar-external {
|
|
360
|
+
right: calc(var(--sp-2) + var(--touch-target) + var(--sp-1));
|
|
357
361
|
bottom: calc(
|
|
358
|
-
var(--wt-
|
|
362
|
+
var(--wt-switcher-h) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
|
|
359
363
|
);
|
|
360
|
-
grid-template-columns: repeat(4, var(--touch-target));
|
|
361
364
|
}
|
|
362
365
|
|
|
363
|
-
.
|
|
364
|
-
|
|
366
|
+
/* ...and a second control in once the switch button is revealed (>= 2 tabs).
|
|
367
|
+
Ordered after the single-tab rule so it wins when both match. */
|
|
368
|
+
:where(.wt-root.wt-narrow:has(.wt-switcher-multi)) .key-toolbar.wt-toolbar-external {
|
|
369
|
+
right: calc(var(--sp-2) + 2 * (var(--touch-target) + var(--sp-1)));
|
|
365
370
|
}
|
|
366
371
|
}
|
|
367
372
|
|
|
@@ -369,12 +374,12 @@
|
|
|
369
374
|
One row per OTHER tab in the expanded list: a stretched select target (dot +
|
|
370
375
|
label) with the close (x) laid inside at the right, mirroring the desktop
|
|
371
376
|
tab's merged chrome. */
|
|
372
|
-
.wt-switcher-row {
|
|
377
|
+
:where(.wt-root) .wt-switcher-row {
|
|
373
378
|
position: relative;
|
|
374
379
|
display: flex;
|
|
375
380
|
}
|
|
376
381
|
|
|
377
|
-
.wt-switcher-row-select {
|
|
382
|
+
:where(.wt-root) .wt-switcher-row-select {
|
|
378
383
|
flex: 1 1 auto;
|
|
379
384
|
display: flex;
|
|
380
385
|
align-items: center;
|
|
@@ -394,7 +399,7 @@
|
|
|
394
399
|
}
|
|
395
400
|
|
|
396
401
|
@media (hover: hover) {
|
|
397
|
-
.wt-switcher-row-select:hover {
|
|
402
|
+
:where(.wt-root) .wt-switcher-row-select:hover {
|
|
398
403
|
background: color-mix(in oklch, var(--tab-bg), var(--text) 15%);
|
|
399
404
|
}
|
|
400
405
|
}
|
|
@@ -403,11 +408,11 @@
|
|
|
403
408
|
chip (the same 15% --text lift the desktop tabs use), not the translucent
|
|
404
409
|
--tab-hover-bg wash that would erase the chip fill. The active tab is set apart
|
|
405
410
|
by its accent border, so a lit inactive row is never mistaken for it. */
|
|
406
|
-
.wt-switcher-row-select:active {
|
|
411
|
+
:where(.wt-root) .wt-switcher-row-select:active {
|
|
407
412
|
background: color-mix(in oklch, var(--tab-bg), var(--text) 15%);
|
|
408
413
|
}
|
|
409
414
|
|
|
410
|
-
.wt-switcher-row-label {
|
|
415
|
+
:where(.wt-root) .wt-switcher-row-label {
|
|
411
416
|
flex: 1 1 auto;
|
|
412
417
|
overflow: hidden;
|
|
413
418
|
text-overflow: ellipsis;
|
|
@@ -415,7 +420,7 @@
|
|
|
415
420
|
}
|
|
416
421
|
|
|
417
422
|
/* The close sits flush at the row's right edge (no gap), vertically centered. */
|
|
418
|
-
.wt-switcher-row-close {
|
|
423
|
+
:where(.wt-root) .wt-switcher-row-close {
|
|
419
424
|
position: absolute;
|
|
420
425
|
top: 50%;
|
|
421
426
|
right: 0;
|
|
@@ -427,7 +432,7 @@
|
|
|
427
432
|
when closing a tab shifts the list under the finger the newly-under close
|
|
428
433
|
would latch white. Touch feedback comes from the surrounding row's :active. */
|
|
429
434
|
@media (hover: hover) {
|
|
430
|
-
.wt-switcher-row-close:hover {
|
|
435
|
+
:where(.wt-root) .wt-switcher-row-close:hover {
|
|
431
436
|
color: var(--text);
|
|
432
437
|
}
|
|
433
438
|
}
|
|
@@ -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");
|