@cplieger/web-terminal-ui 5.0.0 → 5.0.1
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/css/30-tabs.css +76 -16
- package/css/31-switcher.css +20 -10
- package/package.json +1 -1
- package/src/features/tabs/index.ts +19 -4
package/css/30-tabs.css
CHANGED
|
@@ -75,6 +75,22 @@
|
|
|
75
75
|
width: 300px;
|
|
76
76
|
flex: 0 1 auto;
|
|
77
77
|
min-width: 100px;
|
|
78
|
+
/* ONE height for every control in the strip: the chip, the "+" and the
|
|
79
|
+
keyboard button all resolve to --touch-target, so a chip and a button can
|
|
80
|
+
never disagree about how tall the row is. Before this the chip's height was
|
|
81
|
+
whatever its tallest child happened to be, and the "+" hard-coded a COPY of
|
|
82
|
+
that arithmetic (close button + padding + border) — two sources of truth
|
|
83
|
+
for one row, which drifted the moment a child's box grew: a browser without
|
|
84
|
+
text-box support kept the label's untrimmed line box (31px, past the 24px
|
|
85
|
+
close button), so the chip rendered 49px tall next to a 42px "+" and a 44px
|
|
86
|
+
keyboard button. --touch-target is also the tap-target floor the rest of
|
|
87
|
+
the chrome already respects, including this chip's mobile counterpart
|
|
88
|
+
(.wt-switcher-current in 31-switcher.css, same declaration).
|
|
89
|
+
|
|
90
|
+
min-height, not height: the floor governs today (the tallest child is the
|
|
91
|
+
1.5rem close button, which fits with room to spare), and taller future
|
|
92
|
+
content grows the row rather than overflowing a fixed box. */
|
|
93
|
+
min-height: var(--touch-target);
|
|
78
94
|
padding: var(--sp-2);
|
|
79
95
|
/* A transparent border is reserved on every tab so toggling the inactive
|
|
80
96
|
outline (below) never shifts the strip's layout. */
|
|
@@ -112,9 +128,8 @@
|
|
|
112
128
|
|
|
113
129
|
/* Press (mousedown / touch-hold): one rung past the hover lift — same recipe,
|
|
114
130
|
deeper mix — so hover -> press reads as a single dimension. After the hover
|
|
115
|
-
rule so it wins while both apply.
|
|
116
|
-
|
|
117
|
-
it is. */
|
|
131
|
+
rule so it wins while both apply. The active chip has its own pair (above),
|
|
132
|
+
mixed against the active fill instead of the inactive one. */
|
|
118
133
|
:where(.wt-root) .wt-tab:not(.wt-tab-active):active {
|
|
119
134
|
background: color-mix(in oklch, var(--tab-bg), var(--text) 25%);
|
|
120
135
|
color: var(--tab-active-fg);
|
|
@@ -129,6 +144,34 @@
|
|
|
129
144
|
color: var(--tab-active-fg);
|
|
130
145
|
}
|
|
131
146
|
|
|
147
|
+
/* The active tab answers a press too. Clicking it selects nothing, which is why
|
|
148
|
+
it was originally left inert — but selection is not the only thing a chip does:
|
|
149
|
+
it is a drag handle for reordering, a right-click target for the tab menu, a
|
|
150
|
+
double-tap target for rename, and it carries a close button. A control with
|
|
151
|
+
four affordances and no pointer feedback reads as decoration, and it was the
|
|
152
|
+
only chip in the strip that gave none (the same gap that got the keyboard
|
|
153
|
+
button its hover wash).
|
|
154
|
+
|
|
155
|
+
The lift is mixed with --tab-active-fg rather than --text: this is a lighter
|
|
156
|
+
press over an already-filled control (the .wt-switcher-current / .wt-tab-new
|
|
157
|
+
recipe), not a wash over a transparent one, so it follows the filled-surface
|
|
158
|
+
vocabulary and stays themeable through one token. It is a rung subtler than the
|
|
159
|
+
inactive chip's 15%/25% because it starts from an already-lifted fill.
|
|
160
|
+
|
|
161
|
+
Hover is gated to real hover pointers. Tapping an INACTIVE chip makes it
|
|
162
|
+
active, so a touchscreen's sticky :hover leaves with the class; tapping the
|
|
163
|
+
ACTIVE chip changes nothing, so an ungated hover would stay lit until the next
|
|
164
|
+
tap somewhere else. Touch feedback there is :active, which is correct. */
|
|
165
|
+
@media (hover: hover) {
|
|
166
|
+
:where(.wt-root) .wt-tab-active:hover {
|
|
167
|
+
background: color-mix(in oklch, var(--tab-active-bg), var(--tab-active-fg) 12%);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
:where(.wt-root) .wt-tab-active:active {
|
|
172
|
+
background: color-mix(in oklch, var(--tab-active-bg), var(--tab-active-fg) 22%);
|
|
173
|
+
}
|
|
174
|
+
|
|
132
175
|
/* The label flexes to fill the fixed-width tab and truncates with an ellipsis
|
|
133
176
|
(min-width: 0 lets a flex item shrink below its content width). */
|
|
134
177
|
:where(.wt-root) .wt-tab-label {
|
|
@@ -137,7 +180,9 @@
|
|
|
137
180
|
overflow: hidden;
|
|
138
181
|
white-space: nowrap;
|
|
139
182
|
text-overflow: ellipsis;
|
|
140
|
-
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Center the INK, not the line box: UI fonts carry far more ascent than
|
|
141
186
|
descent (the -apple-system/Noto Sans stack at 13px: 14px over / 4px under
|
|
142
187
|
the baseline), so the flex-centered line box leaves the visible text ~1.5px
|
|
143
188
|
low — reading as baseline-aligned with the activity dot's bottom instead of
|
|
@@ -159,15 +204,26 @@
|
|
|
159
204
|
measured: 3.3px of p/y/g/j tail and 2.3px of an acute accent, i.e. "pygmy
|
|
160
205
|
jaguar Ápple" rendered as "pyqmy iaquar Apple"). 0.5em restores the ink
|
|
161
206
|
overhang the trim removes from the box; being symmetric it does not move
|
|
162
|
-
the centered band, and being em-relative it tracks the font size.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
box
|
|
207
|
+
the centered band, and being em-relative it tracks the font size.
|
|
208
|
+
|
|
209
|
+
The pair is @supports-GATED because the padding is the trim's allowance and
|
|
210
|
+
nothing else: an untrimmed line box already contains that overhang, so
|
|
211
|
+
adding the padding there is not "harmless slack" (this comment used to
|
|
212
|
+
claim it was) — it grew the label's box to 31px, past the 24px close button,
|
|
213
|
+
and a browser without text-box support rendered 49px tab chips beside a
|
|
214
|
+
42px "+" and a 44px keyboard button. The chip's own --touch-target floor
|
|
215
|
+
pins the row height either way; the gate is what keeps the label from
|
|
216
|
+
pushing PAST it. Gating the shorthand (not the longhands) is deliberate: an
|
|
217
|
+
engine that ships text-box-trim without the shorthand does not apply the
|
|
218
|
+
declaration we actually write, so it must take the untrimmed branch too.
|
|
166
219
|
|
|
167
220
|
Shared by all three chip labels (this one + the two switcher labels in
|
|
168
221
|
31-switcher.css). */
|
|
169
|
-
|
|
170
|
-
|
|
222
|
+
@supports (text-box: trim-both ex alphabetic) {
|
|
223
|
+
:where(.wt-root) .wt-tab-label {
|
|
224
|
+
text-box: trim-both ex alphabetic;
|
|
225
|
+
padding-block: 0.5em;
|
|
226
|
+
}
|
|
171
227
|
}
|
|
172
228
|
|
|
173
229
|
/* A little breathing room after the activity dot, before the label. */
|
|
@@ -241,12 +297,16 @@
|
|
|
241
297
|
align-items: center;
|
|
242
298
|
justify-content: center;
|
|
243
299
|
flex: none;
|
|
244
|
-
/*
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
300
|
+
/* A SQUARE box of exactly the strip's control height, so it lines up with the
|
|
301
|
+
tab chips beside it and with the keyboard button after it (the .wt-btn
|
|
302
|
+
primitive's 44px box, same token). Sized explicitly on both axes rather
|
|
303
|
+
than by aspect-ratio: a stretched flex item ignores aspect-ratio against
|
|
304
|
+
its SVG's intrinsic width. It used to restate the chip's height arithmetic
|
|
305
|
+
(close button + padding + border) as a calc(), which is how it ended up
|
|
306
|
+
7px short of a chip whose content grew — see the min-height note on
|
|
307
|
+
.wt-tab. */
|
|
308
|
+
width: var(--touch-target);
|
|
309
|
+
height: var(--touch-target);
|
|
250
310
|
padding: 0;
|
|
251
311
|
background: transparent;
|
|
252
312
|
border: none;
|
package/css/31-switcher.css
CHANGED
|
@@ -178,11 +178,16 @@
|
|
|
178
178
|
text-overflow: ellipsis;
|
|
179
179
|
white-space: nowrap;
|
|
180
180
|
text-align: left;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* Ink-centering trim + its mandatory descender allowance, same as .wt-tab-label
|
|
184
|
+
(see 30-tabs.css for why `ex`, why the padding is not optional, and why the
|
|
185
|
+
pair is @supports-gated rather than left to fall back). */
|
|
186
|
+
@supports (text-box: trim-both ex alphabetic) {
|
|
187
|
+
:where(.wt-root) .wt-switcher-label {
|
|
188
|
+
text-box: trim-both ex alphabetic;
|
|
189
|
+
padding-block: 0.5em;
|
|
190
|
+
}
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
/* Keyboard button: hidden until a keyboardToggle is wired; filled while the key
|
|
@@ -432,11 +437,16 @@
|
|
|
432
437
|
overflow: hidden;
|
|
433
438
|
text-overflow: ellipsis;
|
|
434
439
|
white-space: nowrap;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/* Ink-centering trim + its mandatory descender allowance, same as .wt-tab-label
|
|
443
|
+
(see 30-tabs.css for why `ex`, why the padding is not optional, and why the
|
|
444
|
+
pair is @supports-gated rather than left to fall back). */
|
|
445
|
+
@supports (text-box: trim-both ex alphabetic) {
|
|
446
|
+
:where(.wt-root) .wt-switcher-row-label {
|
|
447
|
+
text-box: trim-both ex alphabetic;
|
|
448
|
+
padding-block: 0.5em;
|
|
449
|
+
}
|
|
440
450
|
}
|
|
441
451
|
|
|
442
452
|
/* The close sits flush at the row's right edge (no gap), vertically centered. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.1",
|
|
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",
|
|
@@ -2445,8 +2445,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2445
2445
|
void close_(activeId);
|
|
2446
2446
|
}
|
|
2447
2447
|
});
|
|
2448
|
-
// Dismiss the tab context menu on an outside click, on Escape, and on
|
|
2449
|
-
// scroll
|
|
2448
|
+
// Dismiss the tab context menu on an outside click, on Escape, and on a
|
|
2449
|
+
// scroll of the strip itself (which moves the chip the menu is anchored to).
|
|
2450
2450
|
const onDocClickMenu = (): void => {
|
|
2451
2451
|
// The trailing click of the long-press that just opened the menu is that
|
|
2452
2452
|
// gesture's own release, not a click-away.
|
|
@@ -2466,8 +2466,23 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
2466
2466
|
}
|
|
2467
2467
|
};
|
|
2468
2468
|
document.addEventListener("contextmenu", onDocContextMenu);
|
|
2469
|
-
|
|
2470
|
-
|
|
2469
|
+
// Only a scroll that moves the menu's ANCHOR dismisses it, which is the
|
|
2470
|
+
// strip's own horizontal scroller and nothing else: the menu is placed
|
|
2471
|
+
// root-relative (menu-position.ts rebases onto the offsetParent), so any
|
|
2472
|
+
// scroll that moves .wt-root moves the menu with it, and the terminal's
|
|
2473
|
+
// scroller does not move the chip at all.
|
|
2474
|
+
//
|
|
2475
|
+
// The target check is load-bearing, not a micro-optimization. This listener
|
|
2476
|
+
// is capture-phase on window, so it sees EVERY scroll in the document —
|
|
2477
|
+
// including the terminal surface auto-scrolling to the bottom on each chunk
|
|
2478
|
+
// of output. Without the check, an agent printing into the TUI closed the
|
|
2479
|
+
// tab menu within a frame of it opening, which is unusable on exactly the
|
|
2480
|
+
// screen the menu exists for (a busy multi-agent strip).
|
|
2481
|
+
const onScrollMenu = (e: Event): void => {
|
|
2482
|
+
const target = e.target;
|
|
2483
|
+
if (target instanceof Element && target.closest(".wt-tab-scroll")) {
|
|
2484
|
+
hideTabMenu();
|
|
2485
|
+
}
|
|
2471
2486
|
};
|
|
2472
2487
|
window.addEventListener("scroll", onScrollMenu, true);
|
|
2473
2488
|
const offMenuKey = ctx.registerKeydown((ev) => {
|