@cplieger/web-terminal-ui 4.5.2 → 4.7.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 +49 -45
- package/css/02-terminal.css +9 -4
- package/css/30-tabs.css +120 -0
- package/css/31-switcher.css +9 -0
- package/package.json +7 -4
- package/src/features/tabs/index.ts +678 -98
- package/src/features/tabs/model.ts +284 -182
- package/src/kernel/a11y.ts +16 -0
- package/src/kernel/kernel.ts +119 -47
- package/src/kernel/types.ts +41 -11
- package/src/presets/agent-tabbed.ts +4 -6
- package/src/presets/tabbed.ts +8 -3
package/README.md
CHANGED
|
@@ -8,15 +8,14 @@
|
|
|
8
8
|
[](https://scorecard.dev/viewer/?uri=github.com/cplieger/web-terminal-ui)
|
|
9
9
|
|
|
10
10
|
The reference touch-first browser UI for
|
|
11
|
-
[`@cplieger/web-terminal-engine`](https://github.com/cplieger/web-terminal-engine)
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
[`@cplieger/web-terminal-engine`](https://github.com/cplieger/web-terminal-engine).
|
|
12
|
+
It turns the engine's render/scroll/connection/keyboard modules into a usable
|
|
13
|
+
terminal on a phone as well as a desktop.
|
|
14
14
|
|
|
15
15
|
One `createTerminal(root, { features })` call builds the entire terminal UI
|
|
16
16
|
inside a single container element you provide. A small always-present kernel
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
with opt-in feature modules that own everything above the raw terminal:
|
|
17
|
+
composes with opt-in feature modules that own everything above the raw
|
|
18
|
+
terminal:
|
|
20
19
|
|
|
21
20
|
- a **display-only** terminal output (native text selection survives redraws)
|
|
22
21
|
and a hidden `<textarea>` that owns the keyboard and IME (the kernel)
|
|
@@ -44,7 +43,7 @@ depend on the engine directly and skip this package.
|
|
|
44
43
|
npm install @cplieger/web-terminal-ui @cplieger/web-terminal-engine
|
|
45
44
|
```
|
|
46
45
|
|
|
47
|
-
`@cplieger/web-terminal-engine` is a peer dependency
|
|
46
|
+
`@cplieger/web-terminal-engine` is a peer dependency: the UI is built on the
|
|
48
47
|
engine, so the consumer pins the engine version explicitly. Pairing compatibility
|
|
49
48
|
is governed by the engine's
|
|
50
49
|
[directional wire contract](https://github.com/cplieger/web-terminal-engine#wire-protocol),
|
|
@@ -57,14 +56,14 @@ Serve a CSS bundle matching how you embed the terminal, plus a minimal HTML
|
|
|
57
56
|
page that has one empty container element, then call
|
|
58
57
|
`createTerminal(root, { features })` from your entry module.
|
|
59
58
|
|
|
60
|
-
**Full-page host** (the terminal IS the page
|
|
59
|
+
**Full-page host** (the terminal IS the page, as in `web-terminal-server` and
|
|
61
60
|
`web-terminal-kiro`): concatenate `css/MANIFEST` into the `style.css` your page
|
|
62
61
|
links. That reference bundle is `css/page.css` (the page kit: `html/body`
|
|
63
62
|
reset + the terminal web font's `@font-face`, expecting the font files at
|
|
64
63
|
`/vendor/fonts/`) plus the complete component set.
|
|
65
64
|
|
|
66
|
-
**Embedder** (the terminal lives inside your app's layout
|
|
67
|
-
concatenate the per-preset manifest matching your composition instead
|
|
65
|
+
**Embedder** (the terminal lives inside your app's layout, as a panel or pane):
|
|
66
|
+
concatenate the per-preset manifest matching your composition instead:
|
|
68
67
|
`css/MANIFEST.single`, `css/MANIFEST.touch`, or `css/MANIFEST.tabbed`. These
|
|
69
68
|
contain ONLY root-scoped component styles: no page reset, no fonts, no
|
|
70
69
|
document-level rules, nothing to quarantine. Pass `layout: "container"` so the
|
|
@@ -99,7 +98,7 @@ entry modules beside it:
|
|
|
99
98
|
```
|
|
100
99
|
|
|
101
100
|
`createTerminal(root, opts?)` builds the entire terminal subtree (the kernel
|
|
102
|
-
plus every feature's chrome) inside `root` itself
|
|
101
|
+
plus every feature's chrome) inside `root` itself. There is no element-id
|
|
103
102
|
contract for the host page to reproduce, and every style and CSS custom
|
|
104
103
|
property is scoped to the `wt-root` class it stamps on your element (removed
|
|
105
104
|
again by `destroy()`). Call it exactly once; the engine's
|
|
@@ -109,46 +108,43 @@ to copy and adapt.
|
|
|
109
108
|
|
|
110
109
|
Four presets are provided; each is a plain feature-array factory, so you can
|
|
111
110
|
spread and edit it. Import the barrel (`@cplieger/web-terminal-ui/presets`) for
|
|
112
|
-
convenience, or a per-preset entry module
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
convenience, or a per-preset entry module (`…/presets/single`, `…/presets/touch`,
|
|
112
|
+
`…/presets/tabbed`, `…/presets/agent-tabbed`) for the minimal delivered import
|
|
113
|
+
graph; the barrel statically reaches every feature, while the touch entry, for
|
|
114
|
+
example, never imports the tabs module. Individual features
|
|
116
115
|
are importable from `…/features/<name>` (`clipboard`, `context-menu`,
|
|
117
116
|
`scroll-to-bottom`, `predictive-echo`, `connection-banner`, `mobile-toolbar`,
|
|
118
117
|
`tabs`, `activity-monitor`, `animations`) for hand-picked compositions:
|
|
119
118
|
|
|
120
|
-
- `presetSingle()
|
|
119
|
+
- `presetSingle()`: single-pane desktop UI (context menu, clipboard,
|
|
121
120
|
scroll-to-bottom, predictive echo, connection banner).
|
|
122
|
-
- `presetTouch()
|
|
123
|
-
- `presetTabbed()
|
|
124
|
-
activity monitor, and animations. Each tab's title is OSC-first
|
|
121
|
+
- `presetTouch()`: `presetSingle()` plus the mobile key toolbar.
|
|
122
|
+
- `presetTabbed()`: the generic tabbed UI, `presetTouch()` plus tabs, the
|
|
123
|
+
activity monitor, and animations. Each tab's title is OSC-first: it follows
|
|
125
124
|
the process window title (OSC 0/2) when the program sets one and keeps it
|
|
126
125
|
updated, otherwise the last command submitted. The per-tab activity dot
|
|
127
126
|
reveals itself only when a session reports OSC 9;4 progress, so a plain shell
|
|
128
127
|
keeps clean, label-only tabs. Requires a server that speaks the session API
|
|
129
128
|
(`/api/sessions` and `/ws?session=`), such as `web-terminal-server`.
|
|
130
|
-
- `presetAgentTabbed()
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
prove. Its status dots (idle / working / done / needs-input) come from the
|
|
138
|
-
same activity monitor, driven by the server's status SSE and its OSC-9
|
|
139
|
-
classifier.
|
|
129
|
+
- `presetAgentTabbed()`: the same feature set as `presetTabbed()`, tuned for an
|
|
130
|
+
agent shell such as `web-terminal-kiro`. With `preferInputTitle`, each tab's
|
|
131
|
+
label follows the latest submitted line (persisted server-side and recovered
|
|
132
|
+
on reload) and the program's OSC 0/2 title is ignored. With `presumeReports`,
|
|
133
|
+
the idle activity dot shows from tab creation instead of waiting for the
|
|
134
|
+
session's first OSC 9;4 signal. Its status dots (idle / working / done /
|
|
135
|
+
needs-input) come from the same activity monitor.
|
|
140
136
|
|
|
141
137
|
### Options
|
|
142
138
|
|
|
143
|
-
| Option | Default | Purpose
|
|
144
|
-
| -------------- | -------------------------- |
|
|
145
|
-
| `features` | _(none
|
|
146
|
-
| `layout` | `"viewport"` | How the terminal claims space. `"viewport"`: the root becomes a fixed full-viewport box (the full-page product). `"container"`: the root fills your container element, which becomes the styling and positioning boundary (the embedded case).
|
|
147
|
-
| `wsPath` | `"/ws"` | WebSocket endpoint path the engine connects to.
|
|
148
|
-
| `fontReady` | `'14px "MonaspiceNe NFM"'` | CSS font shorthand awaited before the first resize, so the server is sized against the real web font's cell metrics rather than a fallback.
|
|
149
|
-
| `loading` | _(none)_ | A pre-JS loading overlay element (kept in your served HTML so it paints before this module loads); it is faded out and removed once the first frame renders.
|
|
150
|
-
| `onFatalError` | _(built-in recovery)_ | Called after a fatal
|
|
151
|
-
| `theme` | _(none)_ | Theme overrides (CSS custom properties on the terminal root): `--accent`, `--tab-bg`, `--tab-hover-bg`, `--tab-active-bg`, `--tab-active-fg`, `--tab-active-border`, plus the activity-dot palette `--status-working`, `--status-done`, `--status-input
|
|
139
|
+
| Option | Default | Purpose |
|
|
140
|
+
| -------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
|
+
| `features` | _(none; bare kernel)_ | The feature list. Omitted or empty builds only the terminal (no chrome). Use a preset from `./presets` or a hand-picked array. |
|
|
142
|
+
| `layout` | `"viewport"` | How the terminal claims space. `"viewport"`: the root becomes a fixed full-viewport box (the full-page product). `"container"`: the root fills your container element, which becomes the styling and positioning boundary (the embedded case). |
|
|
143
|
+
| `wsPath` | `"/ws"` | WebSocket endpoint path the engine connects to. |
|
|
144
|
+
| `fontReady` | `'14px "MonaspiceNe NFM"'` | CSS font shorthand awaited before the first resize, so the server is sized against the real web font's cell metrics rather than a fallback. |
|
|
145
|
+
| `loading` | _(none)_ | A pre-JS loading overlay element (kept in your served HTML so it paints before this module loads); it is faded out and removed once the first frame renders. |
|
|
146
|
+
| `onFatalError` | _(built-in recovery)_ | Called with a `TerminalStartupFailure` after a fatal startup failure, in either phase (`feature-setup` or `kernel-init`); behavior below. |
|
|
147
|
+
| `theme` | _(none)_ | Theme overrides (CSS custom properties on the terminal root): `--accent`, `--tab-bg`, `--tab-hover-bg`, `--tab-active-bg`, `--tab-active-fg`, `--tab-active-border`, plus the activity-dot palette `--status-working`, `--status-done`, `--status-input`. The library ships neutral defaults. |
|
|
152
148
|
|
|
153
149
|
`createTerminal()` returns a handle: `focus()` re-focuses the terminal input
|
|
154
150
|
(and opens the soft keyboard on touch); `send(bytes)` sends bytes to the active
|
|
@@ -158,11 +154,19 @@ screen without injecting keystrokes (send a redraw keystroke yourself if you
|
|
|
158
154
|
want one, for example Ctrl+L); and `destroy()` tears every feature down and
|
|
159
155
|
releases the kernel.
|
|
160
156
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
Startup can fail in two phases, and both end at the same recovery surface. If a
|
|
158
|
+
feature's setup throws or rejects (`phase: "feature-setup"`), the kernel stops the
|
|
159
|
+
connection, tears down every completed feature and core listener, clears the
|
|
160
|
+
broken subtree, and shows a reload surface. If `createTerminal` itself throws
|
|
161
|
+
(`phase: "kernel-init"` — an invalid feature list, a DOM invariant), it shows the
|
|
162
|
+
same surface, lowers your loading overlay so the surface is visible, and then
|
|
163
|
+
rethrows, so a caller with its own error handling still sees the error. Either
|
|
164
|
+
way the surface is modal when the terminal owns the viewport and non-modal when
|
|
165
|
+
it fills an embedded container.
|
|
166
|
+
|
|
167
|
+
`onFatalError` receives the failure after cleanup. Discriminate on `phase`:
|
|
168
|
+
`feature-setup` names the offending `feature`, `kernel-init` does not, because
|
|
169
|
+
feature composition never began. Return `true` only when the host has rendered
|
|
166
170
|
replacement recovery UI into the terminal root.
|
|
167
171
|
|
|
168
172
|
## What ships
|
|
@@ -178,10 +182,10 @@ replacement recovery UI into the terminal root.
|
|
|
178
182
|
|
|
179
183
|
The web-terminal family:
|
|
180
184
|
|
|
181
|
-
- [`web-terminal-engine`](https://github.com/cplieger/web-terminal-engine)
|
|
185
|
+
- [`web-terminal-engine`](https://github.com/cplieger/web-terminal-engine): the
|
|
182
186
|
Go session engine + TypeScript browser renderer this UI is built on (peer
|
|
183
187
|
dependency).
|
|
184
|
-
- [`web-terminal-server`](https://github.com/cplieger/web-terminal-server)
|
|
188
|
+
- [`web-terminal-server`](https://github.com/cplieger/web-terminal-server): a
|
|
185
189
|
ready-to-run container that serves this UI over HTTP + WebSocket for any
|
|
186
190
|
command.
|
|
187
191
|
|
package/css/02-terminal.css
CHANGED
|
@@ -24,10 +24,15 @@
|
|
|
24
24
|
scrollbar-gutter: stable;
|
|
25
25
|
/* The scroll controller (vterm scroll.ts) is the single owner of
|
|
26
26
|
scrollTop: it pins to the bottom only while the user is following,
|
|
27
|
-
and does nothing while they are scrolled up reading.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
and does nothing while they are scrolled up reading.
|
|
28
|
+
Content ABOVE the reading position DOES change: rows are evicted
|
|
29
|
+
from the top of history at the retention cap, and the trim marker
|
|
30
|
+
is inserted there. Native scroll anchoring compensates for that on
|
|
31
|
+
Chrome and Firefox, so it stays enabled here — but WebKit has never
|
|
32
|
+
implemented it, which is why the renderer also corrects the drift
|
|
33
|
+
itself (render.ts captureReadAnchor / restoreReadAnchor). That
|
|
34
|
+
correction measures residual drift, so it is a no-op wherever this
|
|
35
|
+
declaration already did the job. */
|
|
31
36
|
overflow-anchor: auto;
|
|
32
37
|
/* .term is the scroll container, so the touch-action intersection for a
|
|
33
38
|
touch on the output stops here (the html/body pan-x pan-y does not reach
|
package/css/30-tabs.css
CHANGED
|
@@ -86,6 +86,12 @@
|
|
|
86
86
|
cursor: pointer;
|
|
87
87
|
white-space: nowrap;
|
|
88
88
|
user-select: none;
|
|
89
|
+
/* Kills the 300ms double-tap-to-zoom delay, which is what makes a double-TAP
|
|
90
|
+
usable as the touch rename entry: on iPadOS a long-press on this chip starts
|
|
91
|
+
a reorder drag (it is draggable), so the context menu is unreachable by touch
|
|
92
|
+
and the double-tap is the only path in. Also removes the tap delay for the
|
|
93
|
+
ordinary switch tap. */
|
|
94
|
+
touch-action: manipulation;
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
/* Inactive tabs read as outlined chips with a subtle dark fill (so adjacent tabs
|
|
@@ -104,6 +110,16 @@
|
|
|
104
110
|
color: var(--tab-active-fg);
|
|
105
111
|
}
|
|
106
112
|
|
|
113
|
+
/* Press (mousedown / touch-hold): one rung past the hover lift — same recipe,
|
|
114
|
+
deeper mix — so hover -> press reads as a single dimension. After the hover
|
|
115
|
+
rule so it wins while both apply. Scoped like hover: clicking the active tab
|
|
116
|
+
selects nothing (it is already active), so it presses like the static surface
|
|
117
|
+
it is. */
|
|
118
|
+
:where(.wt-root) .wt-tab:not(.wt-tab-active):active {
|
|
119
|
+
background: color-mix(in oklch, var(--tab-bg), var(--text) 25%);
|
|
120
|
+
color: var(--tab-active-fg);
|
|
121
|
+
}
|
|
122
|
+
|
|
107
123
|
/* The active tab reads clearly above the inactive chips: a stronger fill plus an
|
|
108
124
|
accent-coloured edge (inactive chips carry a faint white --border; the active
|
|
109
125
|
one gets the accent equivalent). Themeable via --accent (web-terminal-kiro -> purple). */
|
|
@@ -166,6 +182,13 @@
|
|
|
166
182
|
color: var(--text);
|
|
167
183
|
}
|
|
168
184
|
|
|
185
|
+
/* Press: deepen the hover box (--text-active is a 15% --text wash; press doubles
|
|
186
|
+
it to 30%) so mousedown visibly registers before the tab disappears. */
|
|
187
|
+
:where(.wt-root) .wt-tab-close:active {
|
|
188
|
+
background: color-mix(in oklch, var(--text) 30%, transparent);
|
|
189
|
+
color: var(--text);
|
|
190
|
+
}
|
|
191
|
+
|
|
169
192
|
/* On an inactive tab the close appears only on hover, kept in the layout via
|
|
170
193
|
visibility so the tab width never jumps; on the active tab it is always shown. */
|
|
171
194
|
:where(.wt-root) .wt-tab:not(.wt-tab-active) .wt-tab-close {
|
|
@@ -221,6 +244,13 @@
|
|
|
221
244
|
background: var(--tab-active-bg);
|
|
222
245
|
}
|
|
223
246
|
|
|
247
|
+
/* Press: brighten the hover fill with the active-tab fg (the
|
|
248
|
+
.wt-switcher-current press recipe — a lighter press over an already-filled
|
|
249
|
+
control), keeping the accent glyph. */
|
|
250
|
+
:where(.wt-root) .wt-tab-new:active {
|
|
251
|
+
background: color-mix(in oklch, var(--tab-active-bg), var(--tab-active-fg) 12%);
|
|
252
|
+
}
|
|
253
|
+
|
|
224
254
|
/* The +/x icons are inline SVG so they center exactly in their flex-centered
|
|
225
255
|
button and stay symmetric regardless of the UI font (fixes the off-center
|
|
226
256
|
glyph and the lopsided "+"). The + is a touch larger and bolder than the x. */
|
|
@@ -270,6 +300,20 @@
|
|
|
270
300
|
margin-left: auto;
|
|
271
301
|
}
|
|
272
302
|
|
|
303
|
+
/* Hover wash, matching every other bar control (.wt-tab-new, the switcher's
|
|
304
|
+
buttons) — without it this was the one control in the chrome that gave no
|
|
305
|
+
pointer feedback at all. Gated to real hover pointers because the button only
|
|
306
|
+
SHOWS on a coarse pointer (the iPad strip): a plain touchscreen has no
|
|
307
|
+
pointer-leave, so :hover would stick after a tap and the wash would linger;
|
|
308
|
+
the feedback there is :active, from .wt-btn. Before .wt-active / .wt-armed so
|
|
309
|
+
those toggled fills win on a hovered button (equal specificity, source order
|
|
310
|
+
decides). */
|
|
311
|
+
@media (hover: hover) {
|
|
312
|
+
:where(.wt-root) .wt-tab-kb:hover {
|
|
313
|
+
background: var(--tab-hover-bg);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
273
317
|
/* Grid open: filled so the toggled state reads at a glance (mirrors the
|
|
274
318
|
switcher keyboard button). */
|
|
275
319
|
:where(.wt-root) .wt-tab-kb.wt-active {
|
|
@@ -298,6 +342,66 @@
|
|
|
298
342
|
opacity: 0.5;
|
|
299
343
|
}
|
|
300
344
|
|
|
345
|
+
/* ===== Inline rename (tabs feature) =====
|
|
346
|
+
The edit surface is the LABEL's own box, so the width the user types into is
|
|
347
|
+
the width the label will have. The field takes the label's flex slot exactly;
|
|
348
|
+
the label is [hidden] and the close button is hidden for the duration (a 44px
|
|
349
|
+
destructive target next to a live text field is a mis-click waiting to happen,
|
|
350
|
+
and the freed width goes to the field). */
|
|
351
|
+
:where(.wt-root) .wt-tab-rename {
|
|
352
|
+
flex: 1 1 auto;
|
|
353
|
+
min-width: 0;
|
|
354
|
+
background: transparent;
|
|
355
|
+
border: none;
|
|
356
|
+
padding: 0;
|
|
357
|
+
color: var(--tab-active-fg);
|
|
358
|
+
font: inherit;
|
|
359
|
+
/* .wt-tab sets user-select: none, and WebKit CASCADES that into descendants
|
|
360
|
+
even though the property is non-inherited per spec (the same deviation the
|
|
361
|
+
touch model documents for .term-output). Without this, caret placement and
|
|
362
|
+
text selection break inside the field in Safari — i.e. on the iPad. */
|
|
363
|
+
user-select: text;
|
|
364
|
+
/* The chip is cursor: pointer; a text field must not inherit that. */
|
|
365
|
+
cursor: text;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* The family focus ring is scoped to button:focus-visible in 10-primitives.css,
|
|
369
|
+
so the field needs its own. It is focused programmatically the instant it
|
|
370
|
+
appears, so :focus (not :focus-visible) is correct here — the ring IS the
|
|
371
|
+
affordance that says "type here". */
|
|
372
|
+
:where(.wt-root) .wt-tab-rename:focus {
|
|
373
|
+
outline: 2px solid var(--accent);
|
|
374
|
+
outline-offset: 1px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* While editing, hide the close button: a 44px destructive target adjacent to a
|
|
378
|
+
live text field is a mis-click waiting to happen, and the freed width goes to
|
|
379
|
+
the field.
|
|
380
|
+
|
|
381
|
+
The chip is NOT widened. An earlier draft gave the edited chip its full 300px
|
|
382
|
+
so a crowded strip (tabs shrink toward a 100px floor) would not offer a 100px
|
|
383
|
+
edit box — but nothing in this design system transitions `width` on a tab, so
|
|
384
|
+
that was an instant ~200px shove of every neighbouring tab plus a scroll jump,
|
|
385
|
+
at the exact moment the user is trying to read what they are typing. A cramped
|
|
386
|
+
field on a crowded strip is the lesser problem. */
|
|
387
|
+
:where(.wt-root) .wt-tab-editing .wt-tab-close {
|
|
388
|
+
display: none;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/* The drag preview clone (setDragGhost): a full-opacity copy of the chip parked
|
|
392
|
+
under .wt-root — OUTSIDE the bar's backdrop-filter, which is what stops WebKit
|
|
393
|
+
from rendering the automatic drag image (webkit.org/b/22787) — and laid exactly
|
|
394
|
+
over the real chip via inline left/top/width/height. It exists for less than a
|
|
395
|
+
frame (the browser snapshots it, then a rAF removes it), so it must not be
|
|
396
|
+
hidden (an invisible element makes no snapshot) and must not intercept the
|
|
397
|
+
pointer. z-index clears the bar so the copy, not the dimmed original, is what
|
|
398
|
+
would show if it ever painted. */
|
|
399
|
+
:where(.wt-root) .wt-tab-ghost {
|
|
400
|
+
position: absolute;
|
|
401
|
+
z-index: 16;
|
|
402
|
+
pointer-events: none;
|
|
403
|
+
}
|
|
404
|
+
|
|
301
405
|
/* Tab context menu (tabs feature, desktop right-click): Close / Close others /
|
|
302
406
|
Close to the right / Close to the left / Close all. Replaces the old bar
|
|
303
407
|
"Close all" button. Mirrors .wt-ctx-menu (a fixed surface popover, clamped
|
|
@@ -340,11 +444,27 @@
|
|
|
340
444
|
background: var(--text-active);
|
|
341
445
|
}
|
|
342
446
|
|
|
447
|
+
/* Press: deepen the hover wash to 30% --text (the .wt-tab-close press rung) so
|
|
448
|
+
the click registers before the menu closes. */
|
|
449
|
+
:where(.wt-root) .wt-tab-menu button:enabled:active {
|
|
450
|
+
background: color-mix(in oklch, var(--text) 30%, transparent);
|
|
451
|
+
}
|
|
452
|
+
|
|
343
453
|
:where(.wt-root) .wt-tab-menu button:disabled {
|
|
344
454
|
color: color-mix(in oklch, var(--text) 35%, transparent);
|
|
345
455
|
cursor: default;
|
|
346
456
|
}
|
|
347
457
|
|
|
458
|
+
/* Group separator: the naming pair (Rename / Use automatic name) sits above the
|
|
459
|
+
move and close groups, so a hairline keeps the non-destructive actions visually
|
|
460
|
+
apart from the destructive ones. The menu is otherwise a flat flex column of
|
|
461
|
+
buttons with no separator vocabulary. */
|
|
462
|
+
:where(.wt-root) .wt-tab-menu-sep {
|
|
463
|
+
height: 1px;
|
|
464
|
+
margin: var(--sp-1) 0;
|
|
465
|
+
background: var(--border);
|
|
466
|
+
}
|
|
467
|
+
|
|
348
468
|
/* Pull the terminal surface up off the docked BOTTOM tab strip so it does not
|
|
349
469
|
overlap the last rows (a mirror of the old top offset). The tabs feature
|
|
350
470
|
marks the surface (.wt-with-tabbar) and keeps the measured strip height in
|
package/css/31-switcher.css
CHANGED
|
@@ -208,6 +208,15 @@
|
|
|
208
208
|
overflow: hidden;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/* Hover wash like the switch and "+" buttons beside it; gated to real hover
|
|
212
|
+
pointers for the same reason they are (a sticky :hover after a tap). Before
|
|
213
|
+
.wt-active / .wt-armed so those fills win on a hovered button. */
|
|
214
|
+
@media (hover: hover) {
|
|
215
|
+
:where(.wt-root) .wt-switcher-kb:hover {
|
|
216
|
+
background: var(--tab-hover-bg);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
211
220
|
:where(.wt-root) .wt-switcher-kb[hidden] {
|
|
212
221
|
display: none;
|
|
213
222
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.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",
|
|
@@ -38,7 +38,10 @@
|
|
|
38
38
|
"verify": "bash scripts/verify.sh"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@cplieger/web-terminal-engine": "^3.
|
|
41
|
+
"@cplieger/web-terminal-engine": "^3.2.0"
|
|
42
|
+
},
|
|
43
|
+
"overrides": {
|
|
44
|
+
"qs": "^6.15.2"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
44
47
|
"@eslint/js": "10.0.1",
|
|
@@ -50,9 +53,9 @@
|
|
|
50
53
|
"@vitest/coverage-v8": "4.1.10",
|
|
51
54
|
"eslint": "10.7.0",
|
|
52
55
|
"fast-check": "4.9.0",
|
|
53
|
-
"happy-dom": "20.11.
|
|
56
|
+
"happy-dom": "20.11.1",
|
|
54
57
|
"html-validate": "11.5.6",
|
|
55
|
-
"knip": "6.
|
|
58
|
+
"knip": "6.29.0",
|
|
56
59
|
"prettier": "3.9.6",
|
|
57
60
|
"stylelint": "17.14.1",
|
|
58
61
|
"stylelint-config-standard": "40.0.0",
|