@cplieger/web-terminal-ui 8.6.0-dev.2 → 8.6.0-dev.4
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
CHANGED
|
@@ -474,55 +474,6 @@
|
|
|
474
474
|
color: var(--tab-active-fg);
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
-
/* The split glyph (both split buttons) is filled, never stroked: a stroke is
|
|
478
|
-
centred on its outline, so its edges sit half a stroke off the shape's.
|
|
479
|
-
20px is a whole number of device px at 1, 1.25, 1.5 and 2 dppx, and at 1.25
|
|
480
|
-
and 1.5 the shapes move to that DPR's device grid, written as device px over
|
|
481
|
-
the ratio (25 and 30 across, both mirrored about the centre). */
|
|
482
|
-
:where(.wt-root) .wt-btn > .wt-split-icon {
|
|
483
|
-
width: 20px;
|
|
484
|
-
height: 20px;
|
|
485
|
-
fill: currentcolor;
|
|
486
|
-
stroke: none;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
@media (resolution: 1.25dppx) {
|
|
490
|
-
:where(.wt-root) .wt-split-icon-left {
|
|
491
|
-
x: calc(1px / 1.25);
|
|
492
|
-
width: calc(8px / 1.25);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
:where(.wt-root) .wt-split-icon-divider {
|
|
496
|
-
x: calc(11px / 1.25);
|
|
497
|
-
y: calc(2px / 1.25);
|
|
498
|
-
width: calc(3px / 1.25);
|
|
499
|
-
height: calc(21px / 1.25);
|
|
500
|
-
rx: calc(1.5px / 1.25);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
:where(.wt-root) .wt-split-icon-right {
|
|
504
|
-
x: calc(16px / 1.25);
|
|
505
|
-
width: calc(8px / 1.25);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
@media (resolution: 1.5dppx) {
|
|
510
|
-
:where(.wt-root) .wt-split-icon-left {
|
|
511
|
-
x: calc(2px / 1.5);
|
|
512
|
-
width: calc(9px / 1.5);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
:where(.wt-root) .wt-split-icon-divider {
|
|
516
|
-
x: calc(14px / 1.5);
|
|
517
|
-
width: calc(2px / 1.5);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
:where(.wt-root) .wt-split-icon-right {
|
|
521
|
-
x: calc(19px / 1.5);
|
|
522
|
-
width: calc(9px / 1.5);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
477
|
/* ===== Reorder drop slot (tabs feature, desktop drag) =====
|
|
527
478
|
The dragged chip stays in the strip's flow for the whole gesture and renders as
|
|
528
479
|
the SLOT it will land in: an empty dashed outline in the accent over a faint
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "8.6.0-dev.
|
|
3
|
+
"version": "8.6.0-dev.4",
|
|
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": "MPL-2.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"verify": "bash scripts/verify.sh"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@cplieger/web-terminal-engine": "^6.0.0 || ^6.2.0-dev"
|
|
47
|
+
"@cplieger/web-terminal-engine": "^6.0.0 || ^6.2.0-dev || ^6.4.0-dev"
|
|
48
48
|
},
|
|
49
49
|
"overrides": {
|
|
50
50
|
"qs": "^6.15.2"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@stryker-mutator/core": "10.0.0",
|
|
55
55
|
"@stryker-mutator/typescript-checker": "10.0.0",
|
|
56
56
|
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
57
|
-
"@types/node": "26.6.
|
|
57
|
+
"@types/node": "26.6.5",
|
|
58
58
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
59
59
|
"@vitest/browser": "5.0.4",
|
|
60
60
|
"@vitest/browser-playwright": "5.0.4",
|
|
@@ -16,7 +16,6 @@ import type { ActivityMonitorApi } from "../activity-monitor.js";
|
|
|
16
16
|
import type { MobileToolbarApi } from "../mobile-toolbar.js";
|
|
17
17
|
import { fromHTML, holdFocusOnPress } from "../dom.js";
|
|
18
18
|
import { createClickSwallow, placeMenuAt } from "../menu-position.js";
|
|
19
|
-
import { snapToDevicePixels } from "./device-snap.js";
|
|
20
19
|
import { centreChipLabels } from "./ink-centre.js";
|
|
21
20
|
import { SWITCH_ANIMATIONS, SWITCH_CLASSES } from "./switch-anim.js";
|
|
22
21
|
import { createAddressBar, type AddressBar, type RouteOrigin } from "./address-bar.js";
|
|
@@ -833,12 +832,6 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
833
832
|
// engine produced for THIS font at THIS size, rather than the em constant
|
|
834
833
|
// in 00-tokens.css that can only be right at one size (see ink-centre.ts).
|
|
835
834
|
ctx.defer(centreChipLabels(varRoot, { strip: bar, switcher }));
|
|
836
|
-
for (const b of splitButtons) {
|
|
837
|
-
const icon = b.querySelector("svg");
|
|
838
|
-
if (icon) {
|
|
839
|
-
ctx.defer(snapToDevicePixels(icon, ctx.shell.root));
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
835
|
|
|
843
836
|
// Mark the root so the CSS lifts the bottom-anchored chrome (banner, toast,
|
|
844
837
|
// scroll-to-bottom, key grid) above the switcher bar on a coarse pointer.
|
|
@@ -149,14 +149,10 @@ const KB_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" wi
|
|
|
149
149
|
// background-tab notification dot rides on it (see switchButtonHTML). Same
|
|
150
150
|
// viewBox + stroke=currentcolor treatment as the others.
|
|
151
151
|
const SWITCH_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="8" width="13" height="13" rx="2"/><path d="M8 8V6a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-3"/></svg>`;
|
|
152
|
-
// The
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
const SPLIT_SVG =
|
|
156
|
-
`<svg class="wt-split-icon" viewBox="0 0 20 20" aria-hidden="true">` +
|
|
157
|
-
`<rect class="wt-split-icon-left" x="1" y="4" width="6" height="12" rx="1.5"/>` +
|
|
158
|
-
`<rect class="wt-split-icon-divider" x="9" y="2" width="2" height="16" rx="1"/>` +
|
|
159
|
-
`<rect class="wt-split-icon-right" x="13" y="4" width="6" height="12" rx="1.5"/></svg>`;
|
|
152
|
+
// The keyboard glyph's rounded square split down the middle, so the two buttons
|
|
153
|
+
// beside each other read as one size and weight. The state is the button's
|
|
154
|
+
// aria-expanded, not its icon.
|
|
155
|
+
const SPLIT_SVG = `<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="3.5"/><path d="M12 4v16"/></svg>`;
|
|
160
156
|
|
|
161
157
|
/** The ONE builder for a tab chip's content (status dot, label, progress bar,
|
|
162
158
|
* close), shared by the strip chip, the mobile active row and each mobile list
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// A box that starts between device pixels draws every edge of a filled shape
|
|
2
|
-
// as a partial pixel. Layout decides where the box starts, and CSS cannot round
|
|
3
|
-
// a position to the device grid, so the correction is measured and written.
|
|
4
|
-
|
|
5
|
-
import { windowOf } from "../../kernel/realm.js";
|
|
6
|
-
|
|
7
|
-
/** CSS px that move `v` onto the nearest whole device pixel. `v` is read to
|
|
8
|
-
* 1/1000 of a device pixel first: subtracting an applied offset leaves float
|
|
9
|
-
* residue, which would tip a half-pixel position either way. */
|
|
10
|
-
function toWholeDevicePx(v: number, dpr: number): number {
|
|
11
|
-
const device = Math.round(v * dpr * 1000) / 1000;
|
|
12
|
-
return (Math.round(device) - device) / dpr;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Keeps `el`'s box on whole device pixels, moved by at most half of one
|
|
16
|
-
* through its `translate`, which it owns from then on. Re-measured when `el`
|
|
17
|
-
* or an ancestor up to `root` changes size and when the device pixel ratio
|
|
18
|
-
* changes; a move that resizes nothing on that chain is not followed.
|
|
19
|
-
* @returns the release, which stops following and keeps the last offset. */
|
|
20
|
-
export function snapToDevicePixels(el: SVGElement, root: HTMLElement): () => void {
|
|
21
|
-
const win = windowOf(el.ownerDocument);
|
|
22
|
-
let applied = { x: 0, y: 0 };
|
|
23
|
-
const snap = (): void => {
|
|
24
|
-
const box = el.getBoundingClientRect();
|
|
25
|
-
const dpr = win.devicePixelRatio;
|
|
26
|
-
const x = toWholeDevicePx(box.left - applied.x, dpr);
|
|
27
|
-
const y = toWholeDevicePx(box.top - applied.y, dpr);
|
|
28
|
-
applied = { x, y };
|
|
29
|
-
el.style.translate = x === 0 && y === 0 ? "" : `${String(x)}px ${String(y)}px`;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const resize = new win.ResizeObserver(snap);
|
|
33
|
-
for (let node: Element | null = el; node !== null; node = node.parentElement) {
|
|
34
|
-
resize.observe(node);
|
|
35
|
-
if (node === root) {
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// A ratio change resizes nothing in CSS px, and each query matches one ratio.
|
|
40
|
-
let ratio: MediaQueryList | undefined;
|
|
41
|
-
const onRatio = (): void => {
|
|
42
|
-
watchRatio();
|
|
43
|
-
snap();
|
|
44
|
-
};
|
|
45
|
-
function watchRatio(): void {
|
|
46
|
-
ratio?.removeEventListener("change", onRatio);
|
|
47
|
-
ratio = win.matchMedia(`(resolution: ${String(win.devicePixelRatio)}dppx)`);
|
|
48
|
-
ratio.addEventListener("change", onRatio);
|
|
49
|
-
}
|
|
50
|
-
watchRatio();
|
|
51
|
-
|
|
52
|
-
return () => {
|
|
53
|
-
resize.disconnect();
|
|
54
|
-
ratio?.removeEventListener("change", onRatio);
|
|
55
|
-
};
|
|
56
|
-
}
|