@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
package/src/presets.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
// Feature presets
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Feature presets: the barrel. Presets are plain feature-array factories, so
|
|
2
|
+
// they are spreadable and editable: a consumer can drop or add a feature with a
|
|
3
|
+
// filter/spread. This barrel statically imports EVERY preset (and therefore
|
|
4
|
+
// every feature); a consumer that wants the minimal delivered import graph for
|
|
5
|
+
// its composition imports the per-preset entry module instead —
|
|
6
|
+
// "@cplieger/web-terminal-ui/presets/single" | "/presets/touch" |
|
|
7
|
+
// "/presets/tabbed" | "/presets/agent-tabbed" — or hand-picks individual
|
|
8
|
+
// features from "@cplieger/web-terminal-ui/features/<name>".
|
|
6
9
|
//
|
|
7
10
|
// The tabbed presets (presetTabbed / presetAgentTabbed) share the same feature
|
|
8
11
|
// set (both include the activity monitor, whose per-tab dot reveals itself only
|
|
@@ -13,74 +16,7 @@
|
|
|
13
16
|
// server-side — an agent server sets a classifier mapping OSC 9 notifications to
|
|
14
17
|
// done/needs-input.
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import { predictiveEcho } from "./features/predictive-echo.js";
|
|
21
|
-
import { connectionBanner } from "./features/connection-banner.js";
|
|
22
|
-
import { mobileToolbar } from "./features/mobile-toolbar.js";
|
|
23
|
-
import { tabs } from "./features/tabs.js";
|
|
24
|
-
import { activityMonitor } from "./features/activity-monitor.js";
|
|
25
|
-
import { animations } from "./features/animations.js";
|
|
26
|
-
|
|
27
|
-
/** Single-pane desktop UI: context menu, clipboard, scroll-to-bottom, predictive
|
|
28
|
-
* echo, and the connection banner. No mobile toolbar, no tabs. */
|
|
29
|
-
export function presetSingle(): TerminalFeature<unknown>[] {
|
|
30
|
-
// contextMenu offers Copy/Paste through the clipboard feature (ctx.use), so it
|
|
31
|
-
// holds the same clipboard value the array includes.
|
|
32
|
-
const clip = clipboard();
|
|
33
|
-
return [
|
|
34
|
-
contextMenu({ clipboard: clip }),
|
|
35
|
-
clip,
|
|
36
|
-
scrollToBottom(),
|
|
37
|
-
predictiveEcho(),
|
|
38
|
-
connectionBanner(),
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Touch-first UI: presetSingle plus the on-screen key toolbar. */
|
|
43
|
-
export function presetTouch(): TerminalFeature<unknown>[] {
|
|
44
|
-
return [...presetSingle(), mobileToolbar()];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// buildTabbed composes the tabbed UI: the single-pane touch features, an
|
|
48
|
-
// externally-driven mobile toolbar (its grid is opened from a keyboard button in
|
|
49
|
-
// the tab bar, not its own toggle), the activity monitor (the status-SSE data
|
|
50
|
-
// source), tabs wired to both, and animations. The activity dot reveals itself
|
|
51
|
-
// per tab only when a session reports activity (OSC 9;4), so the monitor is
|
|
52
|
-
// always included — a plain shell just never reveals a dot. The toolbar and
|
|
53
|
-
// monitor are ordered before tabs because tabs reads their APIs via ctx.use.
|
|
54
|
-
function buildTabbed(preferInputTitle: boolean): TerminalFeature<unknown>[] {
|
|
55
|
-
const kb = mobileToolbar({ externalToggle: true });
|
|
56
|
-
const monitor = activityMonitor();
|
|
57
|
-
return [
|
|
58
|
-
...presetSingle(),
|
|
59
|
-
kb,
|
|
60
|
-
monitor,
|
|
61
|
-
tabs({ keyboardToggle: kb, activityMonitor: monitor, preferInputTitle }),
|
|
62
|
-
animations(),
|
|
63
|
-
];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Tabbed UI: the touch features, tabs, the mobile keyboard bar, the activity
|
|
67
|
-
* monitor, and animations. Requires a server that speaks the session API
|
|
68
|
-
* (`/api/sessions`, `/ws?session=`, and the status SSE `/api/sessions/events`),
|
|
69
|
-
* such as `web-terminal-server` or `web-terminal-kiro`. Each tab's title follows the
|
|
70
|
-
* process OSC 0/2 window title when the program sets one, else the last
|
|
71
|
-
* submitted line; each tab's activity dot stays hidden until its session
|
|
72
|
-
* reports activity via OSC 9;4 progress (kiro-cli, Claude Code, …), so a plain
|
|
73
|
-
* bash/sh keeps clean, label-only tabs. */
|
|
74
|
-
export function presetTabbed(): TerminalFeature<unknown>[] {
|
|
75
|
-
return buildTabbed(false);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Tabbed UI for an agent shell (web-terminal-kiro). Same features as presetTabbed, but
|
|
79
|
-
* with `preferInputTitle`: the agent's program (kiro-cli) emits a non-empty but
|
|
80
|
-
* useless OSC 0/2 title, so each tab's label follows the latest submitted line
|
|
81
|
-
* (persisted server-side and recovered on reload) and the OSC title is ignored.
|
|
82
|
-
* The agent-vs-generic status distinction remains server-side (a status
|
|
83
|
-
* classifier mapping OSC 9 notifications to done/needs-input). */
|
|
84
|
-
export function presetAgentTabbed(): TerminalFeature<unknown>[] {
|
|
85
|
-
return buildTabbed(true);
|
|
86
|
-
}
|
|
19
|
+
export { presetSingle } from "./presets/single.js";
|
|
20
|
+
export { presetTouch } from "./presets/touch.js";
|
|
21
|
+
export { presetTabbed } from "./presets/tabbed.js";
|
|
22
|
+
export { presetAgentTabbed } from "./presets/agent-tabbed.js";
|
package/src/viewport.ts
CHANGED
|
@@ -20,6 +20,11 @@ import { scroll } from "@cplieger/web-terminal-engine";
|
|
|
20
20
|
const SETTLE_MS = 350;
|
|
21
21
|
|
|
22
22
|
let termWrap: HTMLElement;
|
|
23
|
+
// The terminal root (.wt-root): the geometry CSS vars (--kb-inset, --vv-top)
|
|
24
|
+
// are published here so the terminal subtree — and nothing else on the host
|
|
25
|
+
// page — inherits them. Falls back to termWrap if init was somehow given no
|
|
26
|
+
// root (never in practice; the kernel always passes it).
|
|
27
|
+
let varTarget: HTMLElement;
|
|
23
28
|
let onSettled: ((wasAtBottom: boolean) => void) | null = null;
|
|
24
29
|
let inTransition = false;
|
|
25
30
|
let wasAtBottomAtStart = true;
|
|
@@ -59,6 +64,10 @@ function startTransition(): void {
|
|
|
59
64
|
|
|
60
65
|
export function init(opts: {
|
|
61
66
|
termWrap: HTMLElement;
|
|
67
|
+
/** The terminal root (.wt-root): receives the geometry CSS vars the sibling
|
|
68
|
+
* chrome reads (--kb-inset, --vv-top), so they scope to the terminal
|
|
69
|
+
* subtree instead of leaking onto the host document. */
|
|
70
|
+
root?: HTMLElement;
|
|
62
71
|
onSettled: (wasAtBottom: boolean) => void;
|
|
63
72
|
/** Ignore the visualViewport keyboard geometry (a hardware-keyboard device
|
|
64
73
|
* has no soft keyboard to accommodate); only reserved bottom chrome insets
|
|
@@ -66,6 +75,7 @@ export function init(opts: {
|
|
|
66
75
|
suppressKeyboardInset?: () => boolean;
|
|
67
76
|
}): void {
|
|
68
77
|
termWrap = opts.termWrap;
|
|
78
|
+
varTarget = opts.root ?? opts.termWrap;
|
|
69
79
|
onSettled = opts.onSettled;
|
|
70
80
|
suppressKeyboardInset = opts.suppressKeyboardInset ?? (() => false);
|
|
71
81
|
|
|
@@ -105,12 +115,13 @@ export function init(opts: {
|
|
|
105
115
|
// folds the reserve into the bottom offset here. The reserve excludes the
|
|
106
116
|
// keyboard (it is measured with the keyboard closed), so adding it to
|
|
107
117
|
// bottomInset does not double-count.
|
|
118
|
+
// Read the reserve off the terminal itself: tabs publishes it on the
|
|
119
|
+
// root (.wt-root), and termWrap inherits it — nothing terminal-scoped
|
|
120
|
+
// lives on the document root anymore.
|
|
108
121
|
const rawReserve = Math.max(
|
|
109
122
|
0,
|
|
110
123
|
Math.round(
|
|
111
|
-
parseFloat(
|
|
112
|
-
getComputedStyle(document.documentElement).getPropertyValue("--wt-reserve-bottom"),
|
|
113
|
-
) || 0,
|
|
124
|
+
parseFloat(getComputedStyle(termWrap).getPropertyValue("--wt-reserve-bottom")) || 0,
|
|
114
125
|
),
|
|
115
126
|
);
|
|
116
127
|
// Sanity cap: the reserve is bottom chrome (a tab bar), tens of px. A value
|
|
@@ -122,13 +133,13 @@ export function init(opts: {
|
|
|
122
133
|
const bottom = bottomInset + reserve;
|
|
123
134
|
termWrap.style.top = offsetTop > 0 ? `${offsetTop}px` : "";
|
|
124
135
|
termWrap.style.bottom = bottom > 0 ? `${bottom}px` : "";
|
|
125
|
-
// Expose the same geometry as CSS vars for the sibling
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
// Expose the same geometry as CSS vars for the sibling chrome, on the
|
|
137
|
+
// terminal ROOT (scoped: the host page never sees them): --kb-inset
|
|
138
|
+
// lifts the bottom banner above the keyboard; --vv-top lets the top key
|
|
139
|
+
// toolbar follow the visual viewport's offset (otherwise it scrolls off
|
|
140
|
+
// the top when iOS shifts the layout up).
|
|
141
|
+
varTarget.style.setProperty("--kb-inset", `${bottomInset}px`);
|
|
142
|
+
varTarget.style.setProperty("--vv-top", `${offsetTop}px`);
|
|
132
143
|
startTransition();
|
|
133
144
|
};
|
|
134
145
|
vv.addEventListener("resize", onChange);
|
|
@@ -192,11 +203,13 @@ export function teardown(): void {
|
|
|
192
203
|
clearTimeout(settleTimer);
|
|
193
204
|
settleTimer = null;
|
|
194
205
|
}
|
|
195
|
-
// Clear the
|
|
196
|
-
// a remount (which would recompute them) leaves no stale inset
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
206
|
+
// Clear the CSS vars onChange published on the terminal root, so a destroy
|
|
207
|
+
// without a remount (which would recompute them) leaves no stale inset.
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- teardown may run before any init in tests
|
|
209
|
+
if (varTarget) {
|
|
210
|
+
varTarget.style.removeProperty("--kb-inset");
|
|
211
|
+
varTarget.style.removeProperty("--vv-top");
|
|
212
|
+
}
|
|
200
213
|
inTransition = false;
|
|
201
214
|
onSettled = null;
|
|
202
215
|
suppressKeyboardInset = () => false;
|
package/css/03-primitives.css
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/* ===== web-terminal-ui design system: primitives =====
|
|
2
|
-
The v3 kernel builds named layout regions (wt-region-*) as siblings of the
|
|
3
|
-
terminal surface (.term, styled in 02-app.css); features mount chrome into
|
|
4
|
-
region slots. The regions are transparent groupings (display: contents) so
|
|
5
|
-
each feature's element floats over the fixed-inset terminal, and an absent
|
|
6
|
-
feature contributes nothing.
|
|
7
|
-
|
|
8
|
-
The stylesheet is layered by concern and concatenated in css/MANIFEST order:
|
|
9
|
-
tokens (00) -> reset (01) -> terminal core + legacy key toolbar (02) -> these
|
|
10
|
-
shared primitives (03) -> per-feature chrome (04 feedback, 05 tabs, 06 mobile)
|
|
11
|
-
-> animations (07, where every .wt-animate-gated transition/keyframe lives, so
|
|
12
|
-
motion is opt-in from one place). Cascade order is the file order here.
|
|
13
|
-
|
|
14
|
-
Primitives are the building blocks every feature reuses: the transparent
|
|
15
|
-
layout regions, the 44px touch button, and the toast surface. */
|
|
16
|
-
|
|
17
|
-
.wt-region,
|
|
18
|
-
.wt-slot {
|
|
19
|
-
display: contents;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/* ===== Shared button primitive (44px touch target) ===== */
|
|
23
|
-
.wt-btn {
|
|
24
|
-
display: inline-flex;
|
|
25
|
-
align-items: center;
|
|
26
|
-
justify-content: center;
|
|
27
|
-
min-width: var(--touch-target);
|
|
28
|
-
min-height: var(--touch-target);
|
|
29
|
-
background: transparent;
|
|
30
|
-
color: var(--text);
|
|
31
|
-
border: none;
|
|
32
|
-
border-radius: var(--radius-inner);
|
|
33
|
-
font: inherit;
|
|
34
|
-
cursor: pointer;
|
|
35
|
-
touch-action: manipulation;
|
|
36
|
-
user-select: none;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.wt-btn:active {
|
|
40
|
-
background: var(--text-active);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.wt-btn svg {
|
|
44
|
-
width: var(--icon);
|
|
45
|
-
height: var(--icon);
|
|
46
|
-
stroke: currentcolor;
|
|
47
|
-
fill: none;
|
|
48
|
-
stroke-width: 2;
|
|
49
|
-
stroke-linecap: round;
|
|
50
|
-
stroke-linejoin: round;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/* ===== Toast (kernel shared primitive) =====
|
|
54
|
-
Top-centre on every form factor (both desktop and the tabbed mobile UI dock a
|
|
55
|
-
bar at the bottom, so a bottom toast would collide with it; top-centre is
|
|
56
|
-
clear and the same everywhere). The vv-top/safe-top offset keeps it below the
|
|
57
|
-
notch and follows iOS's visual-viewport shift. */
|
|
58
|
-
.wt-toast {
|
|
59
|
-
position: fixed;
|
|
60
|
-
top: calc(var(--safe-top, 0px) + var(--vv-top, 0px) + var(--sp-3));
|
|
61
|
-
left: 50%;
|
|
62
|
-
transform: translateX(-50%);
|
|
63
|
-
max-width: calc(100% - 2 * var(--sp-3));
|
|
64
|
-
padding: var(--sp-1) var(--sp-3);
|
|
65
|
-
background: var(--surface);
|
|
66
|
-
backdrop-filter: var(--blur);
|
|
67
|
-
border: 1px solid oklch(70% 0.1 160deg / 30%);
|
|
68
|
-
border-radius: var(--radius);
|
|
69
|
-
color: var(--text);
|
|
70
|
-
font-family: var(--font-ui);
|
|
71
|
-
font-size: 0.8125rem;
|
|
72
|
-
font-weight: 500;
|
|
73
|
-
text-align: center;
|
|
74
|
-
z-index: 40;
|
|
75
|
-
pointer-events: none; /* passive status surface: never steal taps from the terminal beneath */
|
|
76
|
-
display: none;
|
|
77
|
-
opacity: 0;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.wt-toast.visible {
|
|
81
|
-
display: block;
|
|
82
|
-
opacity: 1;
|
|
83
|
-
}
|
package/css/04-feedback.css
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/* ===== Feedback + overlay chrome =====
|
|
2
|
-
Transient, non-tab feature surfaces: the connection banner, the
|
|
3
|
-
scroll-to-bottom control, the right-click/long-press context menu, and the
|
|
4
|
-
tab catching-up cue. Grouped because they are all fixed, floating, and
|
|
5
|
-
short-lived; their motion lives in 07-animations.css. */
|
|
6
|
-
|
|
7
|
-
/* ===== Connection banner (connectionBanner feature) =====
|
|
8
|
-
Top-centre on every form factor (same rationale as the toast: a bottom bar is
|
|
9
|
-
always docked now). */
|
|
10
|
-
.wt-conn-banner {
|
|
11
|
-
position: fixed;
|
|
12
|
-
top: calc(var(--safe-top, 0px) + var(--vv-top, 0px) + var(--sp-3));
|
|
13
|
-
left: 50%;
|
|
14
|
-
transform: translateX(-50%);
|
|
15
|
-
max-width: calc(100% - 2 * var(--sp-3));
|
|
16
|
-
padding: var(--sp-1) var(--sp-3);
|
|
17
|
-
background: var(--surface);
|
|
18
|
-
backdrop-filter: var(--blur);
|
|
19
|
-
border: 1px solid var(--border);
|
|
20
|
-
border-radius: var(--radius);
|
|
21
|
-
color: var(--text);
|
|
22
|
-
font-family: var(--font-ui);
|
|
23
|
-
font-size: 0.8125rem;
|
|
24
|
-
font-weight: 500;
|
|
25
|
-
text-align: center;
|
|
26
|
-
z-index: 20;
|
|
27
|
-
pointer-events: none; /* passive status surface: never steal taps from the terminal beneath */
|
|
28
|
-
display: none;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.wt-conn-banner.visible {
|
|
32
|
-
display: block;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.wt-conn-banner[data-state="reconnecting"] {
|
|
36
|
-
border-color: oklch(70% 0.15 60deg / 40%);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.wt-conn-banner[data-state="offline"] {
|
|
40
|
-
color: #f55;
|
|
41
|
-
border-color: oklch(60% 0.2 25deg / 50%);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.wt-conn-banner[data-state="restarted"] {
|
|
45
|
-
color: #fa5;
|
|
46
|
-
border-color: oklch(70% 0.15 60deg / 40%);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/* ===== Scroll-to-bottom (scrollToBottom feature) =====
|
|
50
|
-
A compact floating icon button docked in the bottom-right corner, shown only
|
|
51
|
-
while the user has scrolled up. Same button on both form factors (the old
|
|
52
|
-
desktop bottom-centre text pill was dropped in the design-system switch, and
|
|
53
|
-
its dead CSS removed from 02-app.css); each form factor just lifts it above
|
|
54
|
-
its own bottom bar. Size + icon centering come from the .wt-btn primitive (a
|
|
55
|
-
44px icon button); this rule adds only the floating chrome and placement. */
|
|
56
|
-
.wt-scroll-bottom {
|
|
57
|
-
position: fixed;
|
|
58
|
-
right: var(--sp-3);
|
|
59
|
-
bottom: calc(var(--safe-bottom, 0px) + var(--sp-3) + var(--kb-inset, 0px));
|
|
60
|
-
background: var(--surface);
|
|
61
|
-
backdrop-filter: var(--blur);
|
|
62
|
-
border: 1px solid var(--border);
|
|
63
|
-
/* Match the tab "+" buttons (and every other .wt-btn), which round to
|
|
64
|
-
--radius-inner. Both are 44px boxes, so matching the corner radius makes
|
|
65
|
-
the FAB read as the same-sized button as the new-tab "+" beside/above it
|
|
66
|
-
rather than a rounder, different-looking control. */
|
|
67
|
-
border-radius: var(--radius-inner);
|
|
68
|
-
/* Full --text icon, matching the switcher's keyboard/switch/"+" icons (which
|
|
69
|
-
use .wt-btn's default color). Was a muted 70% mix that read as grey next to
|
|
70
|
-
those white bar icons. */
|
|
71
|
-
color: var(--text);
|
|
72
|
-
z-index: 10;
|
|
73
|
-
display: none;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/* The scroll slot gets .scrolled-up toggled by the feature; show the button. */
|
|
77
|
-
.wt-slot-scroll.scrolled-up .wt-scroll-bottom {
|
|
78
|
-
display: inline-flex;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/* Hover brightens the border; press fills the background (the .wt-btn :active
|
|
82
|
-
fill would otherwise be masked by this button's own surface fill). The icon
|
|
83
|
-
is already full --text, so hover no longer needs to change it. */
|
|
84
|
-
.wt-scroll-bottom:hover {
|
|
85
|
-
border-color: color-mix(in oklch, var(--text) 35%, transparent);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.wt-scroll-bottom:active {
|
|
89
|
-
background: var(--text-active);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/* Mobile + tabbed: lift above the switcher bar and the keyboard (WS3), and pull
|
|
93
|
-
the right edge in to --sp-2 so the button sits directly above the switcher's
|
|
94
|
-
"+" (whose right edge is inset by the bar's --sp-2 padding). Both are 44px
|
|
95
|
-
border-box boxes, so matching the right inset lines them up in one vertical
|
|
96
|
-
column (they were 4px out: the FAB's base --sp-3 vs the "+"'s --sp-2). */
|
|
97
|
-
@media (pointer: coarse) {
|
|
98
|
-
.wt-tabbed .wt-scroll-bottom {
|
|
99
|
-
right: var(--sp-2);
|
|
100
|
-
bottom: calc(
|
|
101
|
-
var(--wt-switcher-h) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* Desktop + tabbed: the tab strip docks at the bottom, so lift the button above
|
|
107
|
-
it with a clear gap. */
|
|
108
|
-
@media (pointer: fine) {
|
|
109
|
-
.wt-tabbed .wt-scroll-bottom {
|
|
110
|
-
bottom: calc(var(--wt-tabbar-h, 44px) + var(--sp-3));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/* ===== Context menu (contextMenu feature) ===== */
|
|
115
|
-
.wt-ctx-menu {
|
|
116
|
-
position: fixed;
|
|
117
|
-
display: none;
|
|
118
|
-
flex-direction: column;
|
|
119
|
-
background: var(--surface);
|
|
120
|
-
backdrop-filter: var(--blur);
|
|
121
|
-
border: 1px solid var(--border);
|
|
122
|
-
border-radius: var(--radius);
|
|
123
|
-
padding: var(--sp-1);
|
|
124
|
-
z-index: 30;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.wt-ctx-menu.visible {
|
|
128
|
-
display: flex;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.wt-ctx-menu button {
|
|
132
|
-
background: transparent;
|
|
133
|
-
color: var(--text);
|
|
134
|
-
border: none;
|
|
135
|
-
border-radius: var(--radius-inner);
|
|
136
|
-
font-family: var(--font-ui);
|
|
137
|
-
font-size: 0.8125rem;
|
|
138
|
-
font-weight: 500;
|
|
139
|
-
padding: var(--sp-1) var(--sp-3);
|
|
140
|
-
cursor: pointer;
|
|
141
|
-
text-align: left;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.wt-ctx-menu button:active {
|
|
145
|
-
background: var(--text-active);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@media (pointer: coarse) {
|
|
149
|
-
.wt-ctx-menu button {
|
|
150
|
-
min-height: var(--touch-target);
|
|
151
|
-
padding-block: var(--sp-2);
|
|
152
|
-
touch-action: manipulation;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/* ===== Catching-up cue (tabs feature, sections 12/13) =====
|
|
157
|
-
A switched-into tab's cached screen is stale until its resume delta lands, so
|
|
158
|
-
it must not read as live. This unobtrusive cue shows only if the delta has not
|
|
159
|
-
arrived shortly after the switch, and clears on the first screen frame. */
|
|
160
|
-
.wt-catchup {
|
|
161
|
-
position: fixed;
|
|
162
|
-
top: calc(var(--safe-top, 0px) + var(--vv-top, 0px) + 44px);
|
|
163
|
-
left: 50%;
|
|
164
|
-
transform: translateX(-50%);
|
|
165
|
-
padding: 2px var(--sp-2);
|
|
166
|
-
background: var(--surface);
|
|
167
|
-
backdrop-filter: var(--blur);
|
|
168
|
-
border: 1px solid var(--border);
|
|
169
|
-
border-radius: var(--radius);
|
|
170
|
-
color: color-mix(in oklch, var(--text) 75%, transparent);
|
|
171
|
-
font-family: var(--font-ui);
|
|
172
|
-
font-size: 0.75rem;
|
|
173
|
-
z-index: 24;
|
|
174
|
-
pointer-events: none; /* passive status surface: never steal taps from the terminal beneath */
|
|
175
|
-
display: none;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.wt-catchup.visible {
|
|
179
|
-
display: block;
|
|
180
|
-
}
|