@cplieger/web-terminal-ui 3.5.0 → 4.0.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 +52 -23
- package/css/00-tokens.css +8 -2
- package/css/01-scope.css +56 -0
- package/css/{02-app.css → 02-terminal.css} +64 -239
- package/css/{03-primitives.css → 10-primitives.css} +21 -19
- 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} +96 -57
- package/css/{06-mobile.css → 31-switcher.css} +66 -81
- 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 +18 -5
- package/scaffold/index.html +5 -0
- package/src/features/context-menu.ts +6 -33
- package/src/features/menu-position.ts +50 -0
- package/src/features/{tabs.ts → tabs/index.ts} +204 -655
- package/src/features/tabs/model.ts +334 -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/kernel.ts +149 -59
- package/src/kernel/types.ts +55 -14
- 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/04-feedback.css +0 -180
|
@@ -38,17 +38,15 @@
|
|
|
38
38
|
|
|
39
39
|
import type { TerminalFeature } from "../kernel/types.js";
|
|
40
40
|
import type { ClipboardApi } from "./clipboard.js";
|
|
41
|
+
import { placeMenuAt } from "./menu-position.js";
|
|
41
42
|
|
|
42
43
|
// Slightly longer than the browser's ~500ms native long-press so a native word
|
|
43
44
|
// selection (which we defer to) has registered — and fired selectionchange —
|
|
44
45
|
// before this fires.
|
|
45
46
|
const LONG_PRESS_MS = 550;
|
|
46
47
|
const LONG_PRESS_MOVE_PX = 10;
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
// above the keyboard), it opens above the point. This gap lifts it clear of the
|
|
50
|
-
// fingertip so the touch does not cover the menu items.
|
|
51
|
-
const MENU_GAP = 16;
|
|
48
|
+
// Viewport clamping + the flip-above-the-fingertip gap live in the shared
|
|
49
|
+
// point-anchored positioner (menu-position.ts), shared with the tab menu.
|
|
52
50
|
// After a touch long-press opens the menu, swallow the trailing click/tap that
|
|
53
51
|
// the same gesture emits on finger-release for this window, so the menu does not
|
|
54
52
|
// immediately dismiss itself (the classic contextmenu-then-touchend race).
|
|
@@ -209,35 +207,10 @@ export function contextMenu(opts: ContextMenuOptions = {}): TerminalFeature {
|
|
|
209
207
|
return; // nothing to offer
|
|
210
208
|
}
|
|
211
209
|
// Make visible (so it has measurable size), then place it within the
|
|
212
|
-
//
|
|
213
|
-
// visual viewport
|
|
214
|
-
// keyboard, so clamping to it keeps the menu off the keyboard too.
|
|
210
|
+
// visible viewport via the shared point-anchored positioner (clamp to
|
|
211
|
+
// the visual viewport; flip above the finger near the bottom edge).
|
|
215
212
|
menu.classList.add("visible");
|
|
216
|
-
|
|
217
|
-
const viewLeft = vv ? vv.offsetLeft : 0;
|
|
218
|
-
const viewTop = vv ? vv.offsetTop : 0;
|
|
219
|
-
const viewWidth = vv ? vv.width : window.innerWidth;
|
|
220
|
-
const viewHeight = vv ? vv.height : window.innerHeight;
|
|
221
|
-
const menuW = menu.offsetWidth;
|
|
222
|
-
const menuH = menu.offsetHeight;
|
|
223
|
-
const left = Math.max(
|
|
224
|
-
viewLeft + EDGE_MARGIN,
|
|
225
|
-
Math.min(x, viewLeft + viewWidth - menuW - EDGE_MARGIN),
|
|
226
|
-
);
|
|
227
|
-
// Open just below the point; but if that would overflow the visible
|
|
228
|
-
// bottom (a long-press near the bottom edge, where the user naturally
|
|
229
|
-
// taps), flip above the finger so the menu is neither clipped off-screen
|
|
230
|
-
// nor hidden under the fingertip.
|
|
231
|
-
let top = y;
|
|
232
|
-
if (y + menuH + EDGE_MARGIN > viewTop + viewHeight) {
|
|
233
|
-
top = y - menuH - MENU_GAP;
|
|
234
|
-
}
|
|
235
|
-
top = Math.max(
|
|
236
|
-
viewTop + EDGE_MARGIN,
|
|
237
|
-
Math.min(top, viewTop + viewHeight - menuH - EDGE_MARGIN),
|
|
238
|
-
);
|
|
239
|
-
menu.style.left = `${String(left)}px`;
|
|
240
|
-
menu.style.top = `${String(top)}px`;
|
|
213
|
+
placeMenuAt(menu, x, y);
|
|
241
214
|
// A touch long-press is immediately followed by a synthetic click on
|
|
242
215
|
// release; swallow it so the menu stays open (see SWALLOW_MS).
|
|
243
216
|
if (touch) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// menu-position.ts — shared point-anchored placement for the fixed-position
|
|
2
|
+
// menus (the terminal context menu and the tab-strip menu), which previously
|
|
3
|
+
// carried two hand-synced copies of this exact clamp/flip math.
|
|
4
|
+
//
|
|
5
|
+
// position:fixed means x/y are viewport coordinates; the VISUAL viewport
|
|
6
|
+
// (when present) is the area above the on-screen keyboard, so clamping to it
|
|
7
|
+
// keeps a menu off the keyboard too.
|
|
8
|
+
|
|
9
|
+
/** Clamp margin from the visible viewport edges (px). */
|
|
10
|
+
const EDGE_MARGIN = 8;
|
|
11
|
+
|
|
12
|
+
/** Gap between the pointer and a flipped-above menu (px), so the menu is not
|
|
13
|
+
* hidden under the fingertip that long-pressed near the bottom edge. */
|
|
14
|
+
const FLIP_GAP = 16;
|
|
15
|
+
|
|
16
|
+
/** Place an already-visible `menu` at viewport point (x, y): open just below
|
|
17
|
+
* the point, flip above it when that would overflow the visible bottom, and
|
|
18
|
+
* clamp into the visual viewport on both axes. The menu is absolute-positioned
|
|
19
|
+
* against the terminal root (.wt-root), so the viewport-space result is
|
|
20
|
+
* rebased onto its offsetParent's box — placement is identical whether the
|
|
21
|
+
* root fills the viewport (wt-viewport) or an embedder's panel
|
|
22
|
+
* (wt-container). The menu must be measurable (rendered, not display:none)
|
|
23
|
+
* when called. */
|
|
24
|
+
export function placeMenuAt(menu: HTMLElement, x: number, y: number): void {
|
|
25
|
+
const vv = window.visualViewport;
|
|
26
|
+
const viewLeft = vv ? vv.offsetLeft : 0;
|
|
27
|
+
const viewTop = vv ? vv.offsetTop : 0;
|
|
28
|
+
const viewWidth = vv ? vv.width : window.innerWidth;
|
|
29
|
+
const viewHeight = vv ? vv.height : window.innerHeight;
|
|
30
|
+
const menuW = menu.offsetWidth;
|
|
31
|
+
const menuH = menu.offsetHeight;
|
|
32
|
+
const left = Math.max(
|
|
33
|
+
viewLeft + EDGE_MARGIN,
|
|
34
|
+
Math.min(x, viewLeft + viewWidth - menuW - EDGE_MARGIN),
|
|
35
|
+
);
|
|
36
|
+
let top = y;
|
|
37
|
+
if (y + menuH + EDGE_MARGIN > viewTop + viewHeight) {
|
|
38
|
+
top = y - menuH - FLIP_GAP;
|
|
39
|
+
}
|
|
40
|
+
top = Math.max(viewTop + EDGE_MARGIN, Math.min(top, viewTop + viewHeight - menuH - EDGE_MARGIN));
|
|
41
|
+
// Rebase from viewport coordinates onto the positioned ancestor's box: left/
|
|
42
|
+
// top on an absolute element are offsets from the offsetParent's padding box,
|
|
43
|
+
// which for all terminal chrome is .wt-root (getBoundingClientRect is its
|
|
44
|
+
// viewport-space position; the root has no border, so border/padding offsets
|
|
45
|
+
// do not enter). A missing offsetParent (display:none ancestor — the menu is
|
|
46
|
+
// visible when called, so not in practice) falls back to (0,0) viewport base.
|
|
47
|
+
const base = menu.offsetParent?.getBoundingClientRect() ?? { left: 0, top: 0 };
|
|
48
|
+
menu.style.left = `${String(Math.round(left - base.left))}px`;
|
|
49
|
+
menu.style.top = `${String(Math.round(top - base.top))}px`;
|
|
50
|
+
}
|