@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/README.md
CHANGED
|
@@ -31,7 +31,8 @@ with opt-in feature modules that own everything above the raw terminal:
|
|
|
31
31
|
- **predictive local echo**, **IME / composition** (CJK, dictation,
|
|
32
32
|
autocorrect), and **viewport + keyboard-inset** handling for the iOS soft
|
|
33
33
|
keyboard, rotation, and font-load reflows
|
|
34
|
-
- a connection-status banner
|
|
34
|
+
- a connection-status banner, including a persistent protocol-incompatibility
|
|
35
|
+
state, and a copy toast
|
|
35
36
|
|
|
36
37
|
It is published as TypeScript source (no build step) to npm and JSR, alongside
|
|
37
38
|
the CSS bundle and a reference HTML page. Consumers who want a different UI should
|
|
@@ -43,17 +44,35 @@ depend on the engine directly and skip this package.
|
|
|
43
44
|
npm install @cplieger/web-terminal-ui @cplieger/web-terminal-engine
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
`@cplieger/web-terminal-engine` is a peer dependency — the UI is built on the
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
`@cplieger/web-terminal-engine` is a peer dependency — the UI is built on the
|
|
48
|
+
engine, so the consumer pins the engine version explicitly. Pairing compatibility
|
|
49
|
+
is governed by the engine's
|
|
50
|
+
[directional wire contract](https://github.com/cplieger/web-terminal-engine#wire-protocol),
|
|
51
|
+
not strict package-version equality; the UI surfaces a terminal incompatibility
|
|
52
|
+
through its connection banner.
|
|
49
53
|
|
|
50
54
|
## Usage
|
|
51
55
|
|
|
52
|
-
Serve
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
Serve a CSS bundle matching how you embed the terminal, plus a minimal HTML
|
|
57
|
+
page that has one empty container element, then call
|
|
58
|
+
`createTerminal(root, { features })` from your entry module.
|
|
59
|
+
|
|
60
|
+
**Full-page host** (the terminal IS the page — `web-terminal-server`,
|
|
61
|
+
`web-terminal-kiro`): concatenate `css/MANIFEST` into the `style.css` your page
|
|
62
|
+
links. That reference bundle is `css/page.css` (the page kit: `html/body`
|
|
63
|
+
reset + the terminal web font's `@font-face`, expecting the font files at
|
|
64
|
+
`/vendor/fonts/`) plus the complete component set.
|
|
65
|
+
|
|
66
|
+
**Embedder** (the terminal lives inside your app's layout — a panel, a pane):
|
|
67
|
+
concatenate the per-preset manifest matching your composition instead —
|
|
68
|
+
`css/MANIFEST.single`, `css/MANIFEST.touch`, or `css/MANIFEST.tabbed`. These
|
|
69
|
+
contain ONLY root-scoped component styles: no page reset, no fonts, no
|
|
70
|
+
document-level rules, nothing to quarantine. Pass `layout: "container"` so the
|
|
71
|
+
terminal fills (and positions its chrome against) your container element
|
|
72
|
+
instead of the viewport.
|
|
73
|
+
|
|
74
|
+
Feature bundles (presets) live at the `./presets` sub-path, with per-preset
|
|
75
|
+
entry modules beside it:
|
|
57
76
|
|
|
58
77
|
```html
|
|
59
78
|
<div id="terminal"></div>
|
|
@@ -81,14 +100,22 @@ sub-path:
|
|
|
81
100
|
|
|
82
101
|
`createTerminal(root, opts?)` builds the entire terminal subtree (the kernel
|
|
83
102
|
plus every feature's chrome) inside `root` itself — there is no element-id
|
|
84
|
-
contract for the host page to reproduce
|
|
103
|
+
contract for the host page to reproduce, and every style and CSS custom
|
|
104
|
+
property is scoped to the `wt-root` class it stamps on your element (removed
|
|
105
|
+
again by `destroy()`). Call it exactly once; the engine's
|
|
85
106
|
render/connection/scroll modules are single-instance per page (tabs multiplex
|
|
86
107
|
sessions over the one kernel). `scaffold/index.html` is a complete reference page
|
|
87
108
|
to copy and adapt.
|
|
88
109
|
|
|
89
|
-
Four presets are provided
|
|
90
|
-
|
|
91
|
-
|
|
110
|
+
Four presets are provided; each is a plain feature-array factory, so you can
|
|
111
|
+
spread and edit it. Import the barrel (`@cplieger/web-terminal-ui/presets`) for
|
|
112
|
+
convenience, or a per-preset entry module for the minimal delivered import
|
|
113
|
+
graph — `…/presets/single`, `…/presets/touch`, `…/presets/tabbed`,
|
|
114
|
+
`…/presets/agent-tabbed` (the barrel statically reaches every feature; the
|
|
115
|
+
touch entry, for example, never imports the tabs module). Individual features
|
|
116
|
+
are importable from `…/features/<name>` (`clipboard`, `context-menu`,
|
|
117
|
+
`scroll-to-bottom`, `predictive-echo`, `connection-banner`, `mobile-toolbar`,
|
|
118
|
+
`tabs`, `activity-monitor`, `animations`) for hand-picked compositions:
|
|
92
119
|
|
|
93
120
|
- `presetSingle()` — single-pane desktop UI (context menu, clipboard,
|
|
94
121
|
scroll-to-bottom, predictive echo, connection banner).
|
|
@@ -110,25 +137,31 @@ individual features instead:
|
|
|
110
137
|
|
|
111
138
|
### Options
|
|
112
139
|
|
|
113
|
-
| Option | Default | Purpose
|
|
114
|
-
| ----------- | -------------------------- |
|
|
115
|
-
| `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.
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
140
|
+
| Option | Default | Purpose |
|
|
141
|
+
| ----------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `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. |
|
|
143
|
+
| `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). |
|
|
144
|
+
| `wsPath` | `"/ws"` | WebSocket endpoint path the engine connects to. |
|
|
145
|
+
| `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. |
|
|
146
|
+
| `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. |
|
|
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`. The library ships neutral defaults. |
|
|
120
148
|
|
|
121
149
|
`createTerminal()` returns a handle: `focus()` re-focuses the terminal input
|
|
122
|
-
(and opens the soft keyboard on touch)
|
|
123
|
-
|
|
150
|
+
(and opens the soft keyboard on touch); `send(bytes)` sends bytes to the active
|
|
151
|
+
session through the kernel's sanitizing input funnel (the supported host path
|
|
152
|
+
for a "type this command" affordance); `reset()` drops the local scrollback and
|
|
153
|
+
screen without injecting keystrokes (send a redraw keystroke yourself if you
|
|
154
|
+
want one — e.g. Ctrl+L); and `destroy()` tears every feature down and releases
|
|
155
|
+
the kernel.
|
|
124
156
|
|
|
125
157
|
## What ships
|
|
126
158
|
|
|
127
|
-
| Path
|
|
128
|
-
|
|
|
129
|
-
| `src/**/*.ts`
|
|
130
|
-
| `css/*.css` + `MANIFEST`
|
|
131
|
-
| `
|
|
159
|
+
| Path | Purpose |
|
|
160
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `src/**/*.ts` | The UI modules: the kernel (`kernel/`), opt-in features (`features/`), per-preset entries (`presets/`), IME, predictive echo, viewport. |
|
|
162
|
+
| `css/*.css` + manifests | Root-scoped component styles. `MANIFEST` = the reference full-page bundle (`page.css` + the tabbed set); `MANIFEST.single/touch/tabbed` = component-only per-preset bundles for embedders. Concatenate in manifest order. |
|
|
163
|
+
| `css/page.css` | The page kit (full-page hosts only): `html/body` reset + the terminal web font's `@font-face`. |
|
|
164
|
+
| `scaffold/index.html` | A reference full-page HTML host: `<head>` + one empty root element + importmap. |
|
|
132
165
|
|
|
133
166
|
## Related projects
|
|
134
167
|
|
package/css/00-tokens.css
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
/* ===== Tokens =====
|
|
2
|
-
|
|
1
|
+
/* ===== Tokens =====
|
|
2
|
+
Every token defaults on .wt-root (the class createTerminal stamps on the
|
|
3
|
+
consumer's root element), never on the document :root: the terminal subtree
|
|
4
|
+
inherits them, the host page never does, and a host page's identically-named
|
|
5
|
+
custom properties never reach the terminal (the class definition wins for the
|
|
6
|
+
subtree). The `theme` option overrides them as inline style on the same
|
|
7
|
+
element — the consumer contract is unchanged. */
|
|
8
|
+
.wt-root {
|
|
3
9
|
interpolate-size: allow-keywords;
|
|
4
10
|
|
|
5
11
|
--bg: oklch(0% 0 0deg);
|
|
6
12
|
--surface: oklch(16% 0.005 280deg / 70%);
|
|
7
13
|
--border: oklch(100% 0 0deg / 15%);
|
|
8
14
|
--text: oklch(90% 0.005 280deg);
|
|
15
|
+
|
|
16
|
+
/* Captured copies of the default color pair, resolved HERE (custom
|
|
17
|
+
properties substitute var() at computed-value time, so descendants
|
|
18
|
+
inherit the resolved colors, not the references). The DECSCNM
|
|
19
|
+
reverse-video rule in 02-terminal.css swaps --text/--bg on .term by
|
|
20
|
+
reading these — a direct self-referential swap (--text: var(--bg))
|
|
21
|
+
would be a cycle and invalidate both. Theme overrides (inline custom
|
|
22
|
+
properties on this element, or a consumer rule) are captured too. */
|
|
23
|
+
--text-default: var(--text);
|
|
24
|
+
--bg-default: var(--bg);
|
|
9
25
|
--text-active: color-mix(in oklch, var(--text) 15%, transparent);
|
|
10
26
|
/* ===== Themeable tokens (the consumer-configurable "template") =====
|
|
11
27
|
These default to the neutral built-in look; a consumer recolors the UI by
|
package/css/01-scope.css
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* ===== Component scope + layout modes =====
|
|
2
|
+
Everything here is scoped under .wt-root — the class createTerminal stamps on
|
|
3
|
+
the consumer's root element. The library never styles the host document: page
|
|
4
|
+
concerns (html/body reset, fonts, viewport meta behavior) live in page.css,
|
|
5
|
+
served only by full-page hosts. This file owns the boundary itself: the
|
|
6
|
+
scoped box-sizing + font-smoothing baseline, the two layout modes, and the
|
|
7
|
+
scoped reduced-motion kill-switch. */
|
|
8
|
+
|
|
9
|
+
/* Layout mode: how the root claims space (kernel stamps exactly one).
|
|
10
|
+
wt-viewport — the root IS the page surface: a fixed full-viewport box (the
|
|
11
|
+
full-page product; geometrically identical to the pre-v4 fixed-inset .term).
|
|
12
|
+
wt-container — the root fills its parent, which becomes the boundary: the
|
|
13
|
+
embedded case. overflow: clip keeps absolute chrome (menus, banners, the
|
|
14
|
+
switcher) inside the box, so an embedder needs no containment workarounds. */
|
|
15
|
+
.wt-root.wt-viewport {
|
|
16
|
+
position: fixed;
|
|
17
|
+
inset: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.wt-root.wt-container {
|
|
21
|
+
position: relative;
|
|
22
|
+
inline-size: 100%;
|
|
23
|
+
block-size: 100%;
|
|
24
|
+
overflow: clip;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.wt-root,
|
|
28
|
+
.wt-root *,
|
|
29
|
+
.wt-root *::before,
|
|
30
|
+
.wt-root *::after {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.wt-root {
|
|
35
|
+
-webkit-font-smoothing: antialiased;
|
|
36
|
+
/* The terminal subtree is dark regardless of the host page's scheme, and
|
|
37
|
+
UA-rendered bits inside it (classic scrollbars, selection tints) must
|
|
38
|
+
match — page.css declares this on html for full-page hosts, but an
|
|
39
|
+
embedded root (wt-container) inherits the HOST's scheme without this. */
|
|
40
|
+
color-scheme: dark;
|
|
41
|
+
/* Design-system scrollbar COLOR on every scroller in the subtree (the
|
|
42
|
+
terminal, the tab scroller, the expanded switcher list): border-grey
|
|
43
|
+
thumb, no track — the vibekit/subflux treatment. scrollbar-color
|
|
44
|
+
INHERITS, so the boundary is the one place to set it. Where it is not
|
|
45
|
+
supported the color-scheme above still renders the UA fallback dark.
|
|
46
|
+
Overlay-scrollbar platforms are unaffected. */
|
|
47
|
+
scrollbar-color: var(--border) transparent;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* scrollbar-width does NOT inherit (unlike scrollbar-color), so the thin
|
|
51
|
+
variant is applied to every element in the boundary: a no-op for anything
|
|
52
|
+
that does not scroll, and any scroller a future feature adds is covered
|
|
53
|
+
automatically instead of joining an explicit list nobody remembers. */
|
|
54
|
+
.wt-root * {
|
|
55
|
+
scrollbar-width: thin;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* stylelint-disable declaration-no-important, time-min-milliseconds --
|
|
59
|
+
Canonical prefers-reduced-motion override. !important and near-zero
|
|
60
|
+
durations are required to forcibly defeat any animation/transition rule
|
|
61
|
+
regardless of specificity, which is the whole point of this a11y reset.
|
|
62
|
+
Scoped to the terminal subtree: the host page's own motion policy is the
|
|
63
|
+
host's business (pre-v4 this hit `*` and silently flattened the embedding
|
|
64
|
+
app's animations). */
|
|
65
|
+
@media (prefers-reduced-motion: reduce) {
|
|
66
|
+
.wt-root,
|
|
67
|
+
.wt-root *,
|
|
68
|
+
.wt-root *::before,
|
|
69
|
+
.wt-root *::after {
|
|
70
|
+
animation-duration: 0.01ms !important;
|
|
71
|
+
animation-iteration-count: 1 !important;
|
|
72
|
+
transition-duration: 0.01ms !important;
|
|
73
|
+
scroll-behavior: auto !important;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/* stylelint-enable declaration-no-important, time-min-milliseconds */
|