@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.
Files changed (41) hide show
  1. package/README.md +60 -27
  2. package/css/00-tokens.css +18 -2
  3. package/css/01-scope.css +76 -0
  4. package/css/{02-app.css → 02-terminal.css} +113 -239
  5. package/css/10-primitives.css +98 -0
  6. package/css/20-connection-banner.css +42 -0
  7. package/css/21-scroll-to-bottom.css +65 -0
  8. package/css/22-context-menu.css +45 -0
  9. package/css/23-toolbar.css +181 -0
  10. package/css/{05-tabs.css → 30-tabs.css} +127 -66
  11. package/css/{06-mobile.css → 31-switcher.css} +85 -80
  12. package/css/{07-animations.css → 40-animations.css} +15 -15
  13. package/css/MANIFEST +19 -11
  14. package/css/MANIFEST.single +12 -0
  15. package/css/MANIFEST.tabbed +16 -0
  16. package/css/MANIFEST.touch +12 -0
  17. package/css/{01-reset.css → page.css} +18 -19
  18. package/package.json +19 -6
  19. package/scaffold/index.html +5 -0
  20. package/src/features/activity-monitor.ts +32 -2
  21. package/src/features/connection-banner.ts +1 -0
  22. package/src/features/context-menu.ts +6 -33
  23. package/src/features/menu-position.ts +50 -0
  24. package/src/features/mobile-toolbar.ts +5 -4
  25. package/src/features/{tabs.ts → tabs/index.ts} +310 -721
  26. package/src/features/tabs/model.ts +342 -0
  27. package/src/features/tabs/strip.ts +101 -0
  28. package/src/features/tabs/switcher.ts +129 -0
  29. package/src/index.ts +1 -0
  30. package/src/kernel/a11y.ts +30 -5
  31. package/src/kernel/conn-state.ts +16 -6
  32. package/src/kernel/kernel.ts +170 -59
  33. package/src/kernel/types.ts +68 -15
  34. package/src/presets/agent-tabbed.ts +15 -0
  35. package/src/presets/single.ts +27 -0
  36. package/src/presets/tabbed.ts +42 -0
  37. package/src/presets/touch.ts +13 -0
  38. package/src/presets.ts +12 -76
  39. package/src/viewport.ts +28 -15
  40. package/css/03-primitives.css +0 -83
  41. package/css/04-feedback.css +0 -180
@@ -0,0 +1,65 @@
1
+ /* ===== Scroll-to-bottom (scrollToBottom feature) =====
2
+ A compact floating icon button docked in the bottom-right corner, shown only
3
+ while the user has scrolled up. Same button on both form factors (the old
4
+ desktop bottom-centre text pill was dropped in the design-system switch);
5
+ each form factor just lifts it above its own bottom bar. Size + icon
6
+ centering come from the .wt-btn primitive (a 44px icon button); this rule
7
+ adds only the floating chrome and placement. Absolute against .wt-root, like
8
+ all chrome (identical geometry in wt-viewport mode; see 01-scope.css). */
9
+ :where(.wt-root) .wt-scroll-bottom {
10
+ position: absolute;
11
+ right: var(--sp-3);
12
+ bottom: calc(var(--safe-bottom, 0px) + var(--sp-3) + var(--kb-inset, 0px));
13
+ background: var(--surface);
14
+ backdrop-filter: var(--blur);
15
+ border: 1px solid var(--border);
16
+ /* Match the tab "+" buttons (and every other .wt-btn), which round to
17
+ --radius-inner. Both are 44px boxes, so matching the corner radius makes
18
+ the FAB read as the same-sized button as the new-tab "+" beside/above it
19
+ rather than a rounder, different-looking control. */
20
+ border-radius: var(--radius-inner);
21
+ /* Full --text icon, matching the switcher's keyboard/switch/"+" icons (which
22
+ use .wt-btn's default color). Was a muted 70% mix that read as grey next to
23
+ those white bar icons. */
24
+ color: var(--text);
25
+ z-index: 10;
26
+ display: none;
27
+ }
28
+
29
+ /* The scroll slot gets .scrolled-up toggled by the feature; show the button. */
30
+ :where(.wt-root) .wt-slot-scroll.scrolled-up .wt-scroll-bottom {
31
+ display: inline-flex;
32
+ }
33
+
34
+ /* Hover brightens the border; press fills the background (the .wt-btn :active
35
+ fill would otherwise be masked by this button's own surface fill). The icon
36
+ is already full --text, so hover no longer needs to change it. */
37
+ :where(.wt-root) .wt-scroll-bottom:hover {
38
+ border-color: color-mix(in oklch, var(--text) 35%, transparent);
39
+ }
40
+
41
+ :where(.wt-root) .wt-scroll-bottom:active {
42
+ background: var(--text-active);
43
+ }
44
+
45
+ /* Mobile + tabbed: lift above the switcher bar and the keyboard (WS3), and pull
46
+ the right edge in to --sp-2 so the button sits directly above the switcher's
47
+ "+" (whose right edge is inset by the bar's --sp-2 padding). Both are 44px
48
+ border-box boxes, so matching the right inset lines them up in one vertical
49
+ column (they were 4px out: the FAB's base --sp-3 vs the "+"'s --sp-2). */
50
+ @media (pointer: coarse) {
51
+ :where(.wt-root.wt-tabbed) .wt-scroll-bottom {
52
+ right: var(--sp-2);
53
+ bottom: calc(
54
+ var(--wt-switcher-h) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
55
+ );
56
+ }
57
+ }
58
+
59
+ /* Desktop + tabbed: the tab strip docks at the bottom, so lift the button above
60
+ it with a clear gap. */
61
+ @media (pointer: fine) {
62
+ :where(.wt-root.wt-tabbed) .wt-scroll-bottom {
63
+ bottom: calc(var(--wt-tabbar-h, 44px) + var(--sp-3));
64
+ }
65
+ }
@@ -0,0 +1,45 @@
1
+ /* ===== Context menu (contextMenu feature) =====
2
+ Absolute against .wt-root, like all chrome; the shared point-anchored
3
+ positioner (src/features/menu-position.ts) converts its viewport-clamped
4
+ coordinates into root-relative offsets, so placement is identical in both
5
+ layout modes. */
6
+ :where(.wt-root) .wt-ctx-menu {
7
+ position: absolute;
8
+ display: none;
9
+ flex-direction: column;
10
+ background: var(--surface);
11
+ backdrop-filter: var(--blur);
12
+ border: 1px solid var(--border);
13
+ border-radius: var(--radius);
14
+ padding: var(--sp-1);
15
+ z-index: 30;
16
+ }
17
+
18
+ :where(.wt-root) .wt-ctx-menu.visible {
19
+ display: flex;
20
+ }
21
+
22
+ :where(.wt-root) .wt-ctx-menu button {
23
+ background: transparent;
24
+ color: var(--text);
25
+ border: none;
26
+ border-radius: var(--radius-inner);
27
+ font-family: var(--font-ui);
28
+ font-size: 0.8125rem;
29
+ font-weight: 500;
30
+ padding: var(--sp-1) var(--sp-3);
31
+ cursor: pointer;
32
+ text-align: left;
33
+ }
34
+
35
+ :where(.wt-root) .wt-ctx-menu button:active {
36
+ background: var(--text-active);
37
+ }
38
+
39
+ @media (pointer: coarse) {
40
+ :where(.wt-root) .wt-ctx-menu button {
41
+ min-height: var(--touch-target);
42
+ padding-block: var(--sp-2);
43
+ touch-action: manipulation;
44
+ }
45
+ }
@@ -0,0 +1,181 @@
1
+ /* ===== Key toolbar (mobileToolbar feature; touch devices only) =====
2
+ Uses interpolate-size: allow-keywords (on .wt-root) to animate
3
+ width/height between a fixed collapsed size and auto (intrinsic).
4
+ Toggle is first in DOM so it's at top-left of the grid — visible
5
+ when the container clips to 1-cell size. Container is right-anchored
6
+ so it grows leftward on open. */
7
+ :where(.wt-root) .key-toolbar {
8
+ display: contents;
9
+ }
10
+
11
+ /* Touch-only toolbar children: the on-screen Esc/Tab/arrows/hamburger
12
+ are useless on desktop where the user has a real keyboard. Hidden by
13
+ default; the @media (pointer: coarse) block below restores them. */
14
+ :where(.wt-root) .kb-key,
15
+ :where(.wt-root) .kb-toggle {
16
+ display: none;
17
+ }
18
+
19
+ @media (pointer: coarse) {
20
+ :where(.wt-root) .key-toolbar {
21
+ /* Absolute against .wt-root (identical geometry in wt-viewport mode; see
22
+ 01-scope.css). --vv-top keeps it following iOS's visual-viewport shift. */
23
+ position: absolute;
24
+ top: calc(var(--safe-top) + var(--sp-2) + var(--vv-top, 0px));
25
+ right: var(--sp-2);
26
+ z-index: 10;
27
+ display: grid;
28
+ grid-template-columns: repeat(4, var(--touch-target)) var(--touch-target);
29
+ grid-template-rows: var(--touch-target) var(--touch-target);
30
+ place-content: start end;
31
+ gap: var(--sp-1);
32
+ padding: var(--sp-1);
33
+ background: var(--surface);
34
+ backdrop-filter: var(--blur);
35
+ border: 1px solid var(--border);
36
+ border-radius: var(--radius);
37
+ overflow: hidden;
38
+ width: auto;
39
+ height: auto;
40
+ transition:
41
+ width var(--dur-enter) var(--ease-standard),
42
+ height var(--dur-enter) var(--ease-standard);
43
+ }
44
+
45
+ /* Collapsed: fixed size = 1 button + padding + border. Content is
46
+ right-aligned (justify-content:end) so the toggle (col 5) stays at
47
+ the right edge, visible within the clipped area. --pill-outer
48
+ includes the 2px border (box-sizing:border-box); omitting it left
49
+ the content box 2px short, pinning the button top-right with
50
+ unequal bottom/left gaps. */
51
+ :where(.wt-root) .key-toolbar.collapsed {
52
+ width: var(--pill-outer);
53
+ height: var(--pill-outer);
54
+ }
55
+
56
+ /* Grid placement: toggle at col 5 row 1 (top-right). */
57
+ :where(.wt-root) .kb-toggle {
58
+ grid-area: 1 / 5;
59
+ position: relative;
60
+ }
61
+ :where(.wt-root) .kb-r1c1 {
62
+ grid-area: 1 / 1;
63
+ }
64
+ :where(.wt-root) .kb-r1c2 {
65
+ grid-area: 1 / 2;
66
+ }
67
+ :where(.wt-root) .kb-r1c3 {
68
+ grid-area: 1 / 3;
69
+ }
70
+ :where(.wt-root) .kb-r1c4 {
71
+ grid-area: 1 / 4;
72
+ }
73
+ :where(.wt-root) .kb-r2c1 {
74
+ grid-area: 2 / 1;
75
+ }
76
+ :where(.wt-root) .kb-r2c2 {
77
+ grid-area: 2 / 2;
78
+ }
79
+ :where(.wt-root) .kb-r2c3 {
80
+ grid-area: 2 / 3;
81
+ }
82
+ :where(.wt-root) .kb-r2c4 {
83
+ grid-area: 2 / 4;
84
+ }
85
+
86
+ /* Keys fade in/out during open/close. The container's width/height
87
+ transition handles the background slide; opacity handles the
88
+ individual button appearance. Stagger the fade slightly after
89
+ the container starts growing (delay on open) and before it
90
+ finishes shrinking (no delay on close). */
91
+ :where(.wt-root) .kb-key {
92
+ opacity: 1;
93
+ transition: opacity var(--dur-standard) var(--ease-enter) 0.05s;
94
+ }
95
+
96
+ :where(.wt-root) .key-toolbar.collapsed .kb-key {
97
+ opacity: 0;
98
+ transition: opacity var(--dur-exit) var(--ease-exit);
99
+ }
100
+
101
+ /* Shared button */
102
+ :where(.wt-root) .key-toolbar button {
103
+ width: var(--touch-target);
104
+ height: var(--touch-target);
105
+ background: transparent;
106
+ color: var(--text);
107
+ border: none;
108
+ border-radius: var(--radius-inner);
109
+ font-size: 0.9375rem;
110
+ font-family: var(--font-ui);
111
+ font-weight: 500;
112
+ cursor: pointer;
113
+ touch-action: manipulation;
114
+ user-select: none;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ }
119
+
120
+ :where(.wt-root) .key-toolbar button:active {
121
+ background: var(--text-active);
122
+ }
123
+
124
+ /* Sticky Ctrl, armed: inverted fill so the toggle state reads clearly
125
+ in the monochrome palette (stays filled while pressed). */
126
+ :where(.wt-root) .kb-key.armed,
127
+ :where(.wt-root) .kb-key.armed:active {
128
+ background: var(--text);
129
+ color: var(--bg);
130
+ }
131
+
132
+ :where(.wt-root) .key-toolbar button svg {
133
+ width: var(--icon);
134
+ height: var(--icon);
135
+ stroke: currentcolor;
136
+ fill: none;
137
+ stroke-width: 2;
138
+ stroke-linecap: round;
139
+ stroke-linejoin: round;
140
+ }
141
+
142
+ /* Toggle icon crossfade. Both icons are stacked absolute so they
143
+ can independently fade + rotate during the open<->close
144
+ transition. inset:0 + margin:auto centers them within the
145
+ button (works because the SVGs carry explicit width/height); a
146
+ bare `position: absolute` would pin them at the top-left, giving
147
+ the toggle visibly unequal left/right padding when collapsed. */
148
+ :where(.wt-root) .kb-toggle .icon-hamburger,
149
+ :where(.wt-root) .kb-toggle .icon-close {
150
+ position: absolute;
151
+ inset: 0;
152
+ margin: auto;
153
+ transition:
154
+ opacity var(--dur-standard) linear,
155
+ transform var(--dur-standard) linear;
156
+ }
157
+
158
+ :where(.wt-root) .kb-toggle .icon-close {
159
+ opacity: 1;
160
+ transform: rotate(0deg);
161
+ }
162
+ :where(.wt-root) .kb-toggle .icon-hamburger {
163
+ opacity: 0;
164
+ transform: rotate(-90deg);
165
+ }
166
+ :where(.wt-root) .key-toolbar.collapsed .kb-toggle .icon-close {
167
+ opacity: 0;
168
+ transform: rotate(90deg);
169
+ }
170
+ :where(.wt-root) .key-toolbar.collapsed .kb-toggle .icon-hamburger {
171
+ opacity: 1;
172
+ transform: rotate(0deg);
173
+ }
174
+
175
+ /* No-transition on first render */
176
+ :where(.wt-root) .key-toolbar.no-transition,
177
+ :where(.wt-root) .key-toolbar.no-transition .kb-toggle .icon-hamburger,
178
+ :where(.wt-root) .key-toolbar.no-transition .kb-toggle .icon-close {
179
+ transition: none;
180
+ }
181
+ }