vintage-frames 0.19.0 → 0.20.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 +1 -18
- package/custom-elements.json +247 -59
- package/dist/components/vf-menu-bar.d.ts +6 -4
- package/dist/components/vf-menu-bar.js +26 -56
- package/dist/components/vf-menu-item.d.ts +33 -5
- package/dist/components/vf-menu-item.js +128 -61
- package/dist/components/vf-menu.d.ts +35 -0
- package/dist/components/vf-menu.js +171 -111
- package/dist/glyphs.d.ts +6 -0
- package/dist/glyphs.js +18 -14
- package/dist/menu-navigation.d.ts +34 -0
- package/dist/menu-navigation.js +53 -0
- package/dist/menu-press.d.ts +25 -7
- package/dist/menu-press.js +78 -44
- package/dist/motion.d.ts +10 -0
- package/dist/text-control.d.ts +3 -3
- package/docs/SPEC.md +13 -9
- package/editor/vscode.html-custom-data.json +13 -8
- package/editor/web-types.json +51 -15
- package/package.json +4 -34
- package/dist/build/app-file.d.ts +0 -21
- package/dist/build/app-file.js +0 -42
- package/dist/build/box.d.ts +0 -57
- package/dist/build/box.js +0 -93
- package/dist/build/client.d.ts +0 -12
- package/dist/build/frame-picture.d.ts +0 -6
- package/dist/build/frame-picture.js +0 -4
- package/dist/build/frame.d.ts +0 -11
- package/dist/build/frame.js +0 -44
- package/dist/build/index.d.ts +0 -14
- package/dist/build/index.js +0 -4
- package/dist/build/plugins.d.ts +0 -41
- package/dist/build/plugins.js +0 -134
- package/dist/build/png.d.ts +0 -24
- package/dist/build/png.js +0 -116
- package/dist/shell/alert.d.ts +0 -40
- package/dist/shell/alert.js +0 -41
- package/dist/shell/app-file.d.ts +0 -115
- package/dist/shell/app-file.js +0 -189
- package/dist/shell/catalog.d.ts +0 -212
- package/dist/shell/catalog.js +0 -317
- package/dist/shell/clock.d.ts +0 -16
- package/dist/shell/clock.js +0 -36
- package/dist/shell/filing.d.ts +0 -39
- package/dist/shell/filing.js +0 -68
- package/dist/shell/finder.d.ts +0 -179
- package/dist/shell/finder.js +0 -662
- package/dist/shell/geometry.d.ts +0 -203
- package/dist/shell/geometry.js +0 -257
- package/dist/shell/index.d.ts +0 -30
- package/dist/shell/index.js +0 -12
- package/dist/shell/pure.d.ts +0 -13
- package/dist/shell/pure.js +0 -6
- package/dist/shell/shell.d.ts +0 -177
- package/dist/shell/shell.js +0 -272
- package/dist/shell/state.d.ts +0 -88
- package/dist/shell/state.js +0 -103
- package/dist/shell/storage.d.ts +0 -14
- package/dist/shell/storage.js +0 -41
- package/dist/shell/version.d.ts +0 -2
- package/dist/shell/version.js +0 -4
- package/dist/shell/windows.d.ts +0 -278
- package/dist/shell/windows.js +0 -447
- package/dist/styles/body-strike.d.ts +0 -12
- package/dist/styles/body-strike.js +0 -3961
- package/dist/styles/display-strike.d.ts +0 -12
- package/dist/styles/display-strike.js +0 -4343
- package/dist/styles/strike.d.ts +0 -30
package/dist/menu-press.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { DocumentListenersController as e } from "./document-listeners.js";
|
|
2
2
|
import "./motion.js";
|
|
3
|
+
import { openPath as t } from "./menu-navigation.js";
|
|
3
4
|
//#region src/menu-press.ts
|
|
4
|
-
var
|
|
5
|
+
var n = class {
|
|
5
6
|
#e;
|
|
6
7
|
#t;
|
|
7
8
|
#n;
|
|
@@ -9,89 +10,122 @@ var t = class {
|
|
|
9
10
|
#i;
|
|
10
11
|
#a;
|
|
11
12
|
#o;
|
|
13
|
+
#s;
|
|
14
|
+
#c;
|
|
15
|
+
#l;
|
|
12
16
|
constructor(t, n) {
|
|
13
|
-
this.target = n, this.#e = !1, this.#t = !1, this.#n = !1, this.#r = null, this.#i = 0, this.#a = null, this.onPointerDown = (e) => {
|
|
17
|
+
this.target = n, this.#e = !1, this.#t = !1, this.#n = !1, this.#r = null, this.#i = 0, this.#a = !1, this.#o = !1, this.#s = null, this.onPointerDown = (e) => {
|
|
14
18
|
if (e.button > 0 || !e.isPrimary) return;
|
|
15
|
-
let t = this.#
|
|
16
|
-
if (t) {
|
|
17
|
-
if (e.preventDefault(), this.#e = !0, this.#n = !1, this.#r = t.item ?? t.menu, this.#i = e.timeStamp, this.#t = !t.menu.open, this.#
|
|
18
|
-
this.#
|
|
19
|
+
let t = this.#_(e.clientX, e.clientY);
|
|
20
|
+
if (!(!t || !t.title && !t.item)) {
|
|
21
|
+
if (e.preventDefault(), this.#e = !0, this.#n = !1, this.#r = t.item ?? t.menu, this.#i = e.timeStamp, this.#t = !t.menu.open, this.#l.attach(), t.item) {
|
|
22
|
+
this.#h(t);
|
|
19
23
|
return;
|
|
20
24
|
}
|
|
21
25
|
t.menu.focus(), this.#t && this.target.open(t.menu);
|
|
22
26
|
}
|
|
23
|
-
}, this.#
|
|
24
|
-
if (
|
|
25
|
-
e.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
}, this.#u = (e) => {
|
|
28
|
+
if (this.#o) return;
|
|
29
|
+
let t = this.#_(e.clientX, e.clientY);
|
|
30
|
+
if (this.#e) {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
let n = t ? t.item ?? (t.title ? t.menu : null) : null;
|
|
33
|
+
!this.#n && n !== this.#r && (this.#n = !0), t?.title && !t.menu.open && this.target.open(t.menu);
|
|
34
|
+
}
|
|
35
|
+
this.#h(t);
|
|
36
|
+
}, this.#d = (e) => {
|
|
29
37
|
if (!this.#e) return;
|
|
30
|
-
let t = this.#
|
|
31
|
-
this.#
|
|
32
|
-
}, this.#
|
|
33
|
-
this.#
|
|
34
|
-
}, t.addController(this), this.#
|
|
38
|
+
let t = this.#_(e.clientX, e.clientY), n = this.#t, r = !this.#n, i = e.timeStamp - this.#i < 200;
|
|
39
|
+
this.#p(), !(n && r && i) && this.#m(t && !t.title ? t : null);
|
|
40
|
+
}, this.#f = () => {
|
|
41
|
+
this.#p(), this.#h(null);
|
|
42
|
+
}, t.addController(this), this.#l = new e(t, () => [
|
|
35
43
|
[
|
|
36
44
|
document,
|
|
37
45
|
"pointermove",
|
|
38
|
-
this.#
|
|
46
|
+
this.#u,
|
|
39
47
|
!0
|
|
40
48
|
],
|
|
41
49
|
[
|
|
42
50
|
document,
|
|
43
51
|
"pointerup",
|
|
44
|
-
this.#
|
|
52
|
+
this.#d,
|
|
45
53
|
!0
|
|
46
54
|
],
|
|
47
55
|
[
|
|
48
56
|
document,
|
|
49
57
|
"pointercancel",
|
|
50
|
-
this.#
|
|
58
|
+
this.#f,
|
|
51
59
|
!0
|
|
52
60
|
]
|
|
53
61
|
]);
|
|
54
62
|
}
|
|
55
63
|
hostDisconnected() {
|
|
56
|
-
this.#
|
|
64
|
+
this.#p(), this.track(!1);
|
|
57
65
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
track(e) {
|
|
67
|
+
if (this.#a = e, e) {
|
|
68
|
+
this.#l.attach();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.#o = !1, this.#s = null, this.#g(), this.#e || this.#l.detach();
|
|
63
72
|
}
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
#u;
|
|
74
|
+
#d;
|
|
75
|
+
#f;
|
|
76
|
+
#p() {
|
|
77
|
+
this.#e && (this.#e = !1, this.#r = null, this.#a || this.#l.detach());
|
|
78
|
+
}
|
|
79
|
+
#m(e) {
|
|
80
|
+
let t = e?.item;
|
|
81
|
+
if (!e || !t || t.disabled) {
|
|
82
|
+
this.target.close();
|
|
67
83
|
return;
|
|
68
84
|
}
|
|
69
|
-
this.#
|
|
85
|
+
if (this.#g(), t.submenu) {
|
|
86
|
+
e.menu.expand(t);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.#h(e), this.#o = !0, t.activate();
|
|
90
|
+
}
|
|
91
|
+
#h(e) {
|
|
92
|
+
let n = e && !e.title ? {
|
|
93
|
+
menu: e.menu,
|
|
94
|
+
row: e.item && !e.item.disabled ? e.item : null
|
|
95
|
+
} : null, r = this.#s;
|
|
96
|
+
if (r?.menu === n?.menu && r?.row === n?.row) return;
|
|
97
|
+
this.#s = n;
|
|
98
|
+
for (let e of this.target.menus()) if (e.open) for (let r of t(e)) r.pointerOver(r === n?.menu ? n.row : void 0);
|
|
99
|
+
if (this.#g(), !n) return;
|
|
100
|
+
let { menu: i, row: a } = n, o = i.expandedItem;
|
|
101
|
+
(a === null || a !== o) && (o ? this.#c = window.setTimeout(() => i.expand(a?.submenu ? a : null), 300) : a?.submenu && (this.#c = window.setTimeout(() => i.expand(a), 150)));
|
|
70
102
|
}
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
t !== this.#a && (this.#a && (this.#a.active = !1), this.#a = t, t && (t.active = !0));
|
|
103
|
+
#g() {
|
|
104
|
+
this.#c !== void 0 && window.clearTimeout(this.#c), this.#c = void 0;
|
|
74
105
|
}
|
|
75
|
-
#
|
|
76
|
-
let
|
|
77
|
-
if (i) {
|
|
78
|
-
|
|
106
|
+
#_(e, n) {
|
|
107
|
+
let i = this.target.menus(), a = i.find((e) => e.open);
|
|
108
|
+
if (a) for (let i of t(a).reverse()) {
|
|
109
|
+
let t = i.panelRect;
|
|
110
|
+
if (!(!t || !r(t, e, n))) return {
|
|
79
111
|
menu: i,
|
|
80
|
-
|
|
112
|
+
title: !1,
|
|
113
|
+
item: i.allItems.find((t) => r(t.getBoundingClientRect(), e, n)) ?? null
|
|
81
114
|
};
|
|
82
115
|
}
|
|
83
|
-
for (let
|
|
84
|
-
let
|
|
85
|
-
if (
|
|
86
|
-
menu:
|
|
116
|
+
for (let t of i) {
|
|
117
|
+
let i = t.labelRect;
|
|
118
|
+
if (i && r(i, e, n)) return {
|
|
119
|
+
menu: t,
|
|
120
|
+
title: !0,
|
|
87
121
|
item: null
|
|
88
122
|
};
|
|
89
123
|
}
|
|
90
124
|
return null;
|
|
91
125
|
}
|
|
92
126
|
};
|
|
93
|
-
function
|
|
127
|
+
function r(e, t, n) {
|
|
94
128
|
return t >= e.left && t < e.right && n >= e.top && n < e.bottom;
|
|
95
129
|
}
|
|
96
130
|
//#endregion
|
|
97
|
-
export {
|
|
131
|
+
export { n as MenuPressController };
|
package/dist/motion.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ export declare function prefersReducedMotion(): boolean;
|
|
|
19
19
|
* drag-pick regardless of duration.
|
|
20
20
|
*/
|
|
21
21
|
export declare const PRESS_HOLD_MS = 200;
|
|
22
|
+
/**
|
|
23
|
+
* How long (ms) the pointer rests on a menu item before its submenu opens,
|
|
24
|
+
* and how long it may stray onto another row of the same menu before an open
|
|
25
|
+
* submenu closes. The second is the longer, so a diagonal slide from an item
|
|
26
|
+
* into its submenu can cross the rows between. Not gated on
|
|
27
|
+
* `prefers-reduced-motion`: they are the interaction, not decoration, the
|
|
28
|
+
* reasoning {@link MENU_SCROLL_INTERVAL_MS} gives. Provisional, tuned by hand.
|
|
29
|
+
*/
|
|
30
|
+
export declare const SUBMENU_OPEN_MS = 150;
|
|
31
|
+
export declare const SUBMENU_CLOSE_MS = 300;
|
|
22
32
|
/**
|
|
23
33
|
* How long (ms) a press on a selected icon's name waits before it opens the
|
|
24
34
|
* rename box — long enough that a double-click has already declared itself.
|
package/dist/text-control.d.ts
CHANGED
|
@@ -33,9 +33,9 @@ export declare class VfTextControlBase extends VfShadowRoleControl {
|
|
|
33
33
|
/**
|
|
34
34
|
* Opens with the whole text selected, so typing replaces it. Implies
|
|
35
35
|
* `autofocus`: the first control carrying either takes the opening focus of
|
|
36
|
-
* its `vf-dialog`, or of a window
|
|
37
|
-
* selects the text on every open, a window on its first open only.
|
|
38
|
-
* still places the caret.
|
|
36
|
+
* its `vf-dialog`, or of a window system-online-sdk's window manager opens.
|
|
37
|
+
* A dialog selects the text on every open, a window on its first open only.
|
|
38
|
+
* A click still places the caret.
|
|
39
39
|
*/
|
|
40
40
|
autoselect: boolean;
|
|
41
41
|
/** Form field name used when submitting the associated form. */
|
package/docs/SPEC.md
CHANGED
|
@@ -196,7 +196,7 @@ Every recipe also declares its size — `width`, and `height` where the window i
|
|
|
196
196
|
|
|
197
197
|
#### `vf-desktop` (`VfDesktop`, vf-desktop.ts)
|
|
198
198
|
Full-bleed classic desktop container.
|
|
199
|
-
- **Attributes/props:** `width`, `height: number` (system px, defaults 512×342 — the compact Mac's raster) — the screen's own size, the way a WIND resource declared a window's. The host box renders at exactly `width + 2·bezel` per axis (a live `sysLength` written as host inline size on every update), always a whole number of system px. **Pure CSS sizing is not supported**: the inline size wins over any stylesheet, so the page sets the numbers — directly or via `fitWithin` — and positions the explicitly sized desktop with its own CSS, keeping any sub-system-pixel slack in the page's own layout. `bezel: number` (system px, default 0) — the black screen surround, the CRT's unlit margin between raster and case, added onto the declared screen on every side. The screen owns flow, an absolute window's containing block and the drag clip, so a dragged window crops at the raster's edge. Its four corners wear the `SCREEN_CORNER` mask (the bottom pair mirrored vertically — `steppedCornerClip`'s `edge`), painted above everything like the hardware mask — the top pair lands over a slotted menu bar's corners, so the bar needs no `rounded` of its own inside a bezeled desktop. The bezel width is written onto the host as `--vf-desktop-bezel` (self-set geometry, like `--vf-scale` — component-owned, not a theming token). `pattern: string` (default `'gray-50'`, reflected) — the desktop pattern, System 7's General Controls setting: a library name (docs/PATTERNS.md) or sixteen hex digits stating a custom 8×8; an unrecognized value warns once and keeps the dither. A `--vf-desktop-pattern` token override wins over it. It reflects so a page can observe a change on the attribute;
|
|
199
|
+
- **Attributes/props:** `width`, `height: number` (system px, defaults 512×342 — the compact Mac's raster) — the screen's own size, the way a WIND resource declared a window's. The host box renders at exactly `width + 2·bezel` per axis (a live `sysLength` written as host inline size on every update), always a whole number of system px. **Pure CSS sizing is not supported**: the inline size wins over any stylesheet, so the page sets the numbers — directly or via `fitWithin` — and positions the explicitly sized desktop with its own CSS, keeping any sub-system-pixel slack in the page's own layout. `bezel: number` (system px, default 0) — the black screen surround, the CRT's unlit margin between raster and case, added onto the declared screen on every side. The screen owns flow, an absolute window's containing block and the drag clip, so a dragged window crops at the raster's edge. Its four corners wear the `SCREEN_CORNER` mask (the bottom pair mirrored vertically — `steppedCornerClip`'s `edge`), painted above everything like the hardware mask — the top pair lands over a slotted menu bar's corners, so the bar needs no `rounded` of its own inside a bezeled desktop. The bezel width is written onto the host as `--vf-desktop-bezel` (self-set geometry, like `--vf-scale` — component-owned, not a theming token). `pattern: string` (default `'gray-50'`, reflected) — the desktop pattern, System 7's General Controls setting: a library name (docs/PATTERNS.md) or sixteen hex digits stating a custom 8×8; an unrecognized value warns once and keeps the dither. A `--vf-desktop-pattern` token override wins over it. It reflects so a page can observe a change on the attribute; system-online-sdk's shell saves it that way. `window-top: number` (`windowTop`, system px from the screen's top, unset by default) — the least `top` a window may take, for a page that keeps a strip of its own under the menu bar, over the windows: a window dragged on the desktop stops there and `windowArea` starts there; icons still stop at the menu bar; below the bar's bottom it changes nothing.
|
|
200
200
|
- **Methods:** `fitWithin(maxWidth, maxHeight)` (CSS px) → sets `width`/ `height` to the largest whole-system-px raster whose host box — bezel included — fits the bound, per the current effective scale, and returns `{ width, height }`. The page's half of the sizing contract: call it on `resize` and `onScaleChange` (zoom and density moves change what a system px costs in CSS px), as a full-screen faux desktop does. `clearActive()` → deactivate the whole document tier (see *Deactivation* under Behavior). `activeWindow: HTMLElement | null` (read-only) → the active document-tier window, or `null` while the tier is deactivated or has no shown window; utility windows and hidden windows are never the holder. `stackingOrder: HTMLElement[]` (read-only) → the slotted windows in stacking order, bottom-most first — the document tier, then the floating tier, each by its last raise — hidden windows included where they stand; a fresh array per read. The light-DOM order catches up with it only after a gesture, and a raise by keyboard focus moves no node, so a page that saves window depths or picks the window behind another reads this rather than the DOM. `workArea` (read-only) → `{ left, top, width, height }` in system px from the screen's corner, bezel excluded: the screen less a slotted, shown `vf-menu-bar` along its top edge (measured at the call; the whole screen without one). `windowArea` (read-only) → the work area deepened to `window-top` where that is lower — the area windows are dragged in. `placementAt(clientX, clientY, child?)` → a viewport point as a placement on the screen, `{ left, top }` in whole system px on the placement lattice, measured from the raster's corner with the bezel excluded — the pair a child dropped onto the desktop is written with; with `child`, the pair to write to that element, its `origin` offset folded in (`placementIn`, src/position.ts; the anchor sits in shadow, which is why it is a method).
|
|
201
201
|
- **Visual:** `display: block; position: relative;` — the paint lives on an inner screen surface (part `desktop`, `overflow: hidden` — the whole-system-px raster, inset by `bezel` when one is set). Screen surface = the `pattern` (the classic 50% dither, `gray-50`, by default), painted as the screen's own background by the pattern fill (*Patterns* under *Tiled fills*): black ink on the fill's opaque `--vf-white` paper, a repeating tile — the authentic black-on-white dither. A consumer `--vf-desktop-pattern` renders instead as a placed tile grid on the token's 30-system-px tile with no paper, so `var(--vf-desktop, #808080)` beneath shows only under a custom tile with transparent cells (or `none`). Forced colors: flat Canvas.
|
|
202
202
|
- **Slots:** default (menu bar, windows, anything).
|
|
@@ -234,7 +234,7 @@ The desktop-window shell: the classic document window (see DragThing screenshot)
|
|
|
234
234
|
#### `vf-dialog` (`VfDialog`, vf-dialog.ts)
|
|
235
235
|
The modal-dialog shell: movable modal by default (the dBoxProc double frame with the striped title bar set into it — traced from a 2× System 7 capture of a movable modal), the modal dialog box with `frame="plain"` (the bare double frame; see the Group A recipe table).
|
|
236
236
|
- **Attributes/props:** `open: boolean` (reflect), `heading: string`, `top: number` / `left: number` (whole system px, in **viewport** coordinates — see §1 Explicit placement; unset means centered), `width: number` / `height: number` (**declare them both** — whole system px, the same fixed box `vf-window` is. A native `<dialog>` is `width: fit-content` measured against the space left beside its own offsets, and stating an offset is how the movable modal is positioned, so an undeclared dialog squeezes itself and reflows as it is dragged toward an edge. The two fall back differently — width to 260 system px, height to the content — and it names whichever are missing, once, on the open that first shows it), `label: string` (accessible name for a dialog with no `heading`), `closable: boolean` (default **false** — the bare movable-modal bar; the close box is opt-in because the HIG's Chapter 6 text denies a movable modal one while its Figure 5-1 grants it — the parameter enables either reading), `light-dismiss: boolean` (`lightDismiss`; default **false** — a click outside the frame closes the dialog with `vf-close` reason `'outside'`. Off by default because the classic modal ignored an outside click, and a dialog that asks a question should keep ignoring it; opt in for the About box and the splash, which the classic Mac did dismiss on a click. Reflected, and read at the release, so it can be toggled on an open dialog), `frame?: 'plain'`, `returnValue: string` (property only, as on `<dialog>`: the dialog's answer, cleared on each open).
|
|
237
|
-
- **Implementation:** wraps a native `<dialog>` (for top-layer + focus trap). `show()` → `showModal()`; `close()` closes. Keep `open` attr in sync both directions. Drag the title bar to move it (shared `DragController` with `vf-window`), stating `top`/`left` in system px; drags starting on the close widget are ignored (same composedPath guard as `vf-window`). Escape → close + `vf-close` detail `{ reason: 'escape' }`; close box/programmatic/close() → `{ reason: 'close' }`; a click outside under `light-dismiss` → `{ reason: 'outside' }`. **Answers** (`VfModalDialog`): `close(value)` closes with `value` as `returnValue`, and a `<form method="dialog">` inside the dialog closes it on submit with the submitter's `value`, a submitter's `formmethod="dialog"` too. The platform's own rule looks for a `<dialog>` around the form, and this one is in the shadow root, so the base listens for `submit` on the host and closes at the end of the submit's path (`deferActivation`), not at all if anything cancelled it. `vf-button`'s submit proxy carries its `value` with or without a `name`. `vf-close`'s detail carries `returnValue`: the value that close carried, or null (Escape, a click outside, the close box, `close()` alone, removal). Return in a text field runs the form's implicit submission through the ringed `variant="default"` submit button when the form has one, ahead of the first in tree order, so a Cancel/OK row answers OK (`requestImplicitSubmit`). `npm run verify:dialog`, ANSWERS. No backdrop dimming: `::backdrop { background: transparent; }`. **Light dismiss is target identity, not geometry** (`VfModalDialog`): the `::backdrop` hit-tests as its `<dialog>`, and the frame fills the dialog's box, so the dialog is a pointer event's *target* exactly when the pointer was outside the frame — the listeners sit on the `<dialog>` itself, attached while open. It takes both halves of the click: a `pointerdown` on the backdrop arms (any other press disarms, so a right-click macOS never releases can't leave a stale arm), and a `pointerup` on the backdrop with the same `pointerId` closes — the platform's own `closedby="any"` two-step, so a press that starts on a control and slides off, or a title-bar drag released past the frame, leaves the dialog open. Deliberately not the `click` event: UI Events dispatches a press-drag-release click at the common ancestor of the two targets, which for a press on the frame released outside is the dialog itself. The platform's `closedby` attribute is not used because its dismissal arrives as `cancel`, which the base already reads as Escape. The press is consumed either way — a modal's backdrop lets nothing beneath it see the click — which is what lets a page dismiss its About box without also selecting the icon under the pointer. `npm run verify:dialog`. **Keyboard: the Dialog Manager's two rules** (`VfModalDialog`). *Initial focus:* right after `showModal()`'s own focusing steps, focus is handed to `initialFocusTarget` — the first slotted control carrying `autofocus` or `autoselect`, else the first enabled text-entry control (`vf-text-field`, `vf-number-field`, `vf-text-area`, or a native text input/textarea), else the default button (`defaultButton`: the first enabled `vf-button[variant="default"]`, looked for among the slotted content and then the shadow tree), else nothing, leaving the browser's pick. A field marked `autoselect` has its whole text selected on every open, right after `vf-show` and once the field has rendered, so a value set before `show()` or in a `vf-show` handler is the text selected. Otherwise the modal selects no text: whether a field opens selected is the page's call. The classic dialog put the insertion point in its first text item and gave nothing else focus; the web needs a focused control, and the default button is the one that makes Return and Space work without a Tab. Left alone, `showModal()` focuses the first focusable thing in flat-tree order — Cancel in a Cancel/OK row, or a link in the body — so Return pressed the wrong thing. Both getters are `protected`, for a consumer's own modal to override. *Return/Enter:* a `keydown` listener on the `<dialog>`, bubble phase, attached while open with the light-dismiss trio. An uncancelled, unmodified Enter activates `defaultButton` (its `click()`, so a `type="submit"` default runs its proxy path) and cancels the keydown, which is what keeps a focused Cancel from firing itself — Space presses the focused control, as on the classic Mac. Anything that took Enter for itself cancelled the keydown first and is left alone: `vf-select`'s list, `vf-list`, an icon's rename, and a text field whose form ran its implicit submission (`requestImplicitSubmit` now reports whether a form owner took the press, and the fields cancel on true — so the submission runs once, not once per layer). A link keeps its own Enter. A multi-line editor (`<textarea>`, contenteditable) keeps Return for the newline; only `code === 'NumpadEnter'` fires the button there — the split a TextEdit item drew between the two keys. With no enabled default button the press is left native. Escape is unchanged (the `cancel` path above). **Removal while open is a close path** (`VfModalDialog.disconnectedCallback`): HTML's dialog removing steps skip the close algorithm entirely, which is exactly what a framework unmount does — the teardown routes through the same native-`close` funnel (`vf-close` fires on the removed element; nothing bubbles, it left the tree), `open` and the written origin reconcile so a re-append mounts closed, and focus returns to the element focused at open time. **Opened again before a close's `close` event comes** — the native event is queued, so a `close()` and a `show()` in one turn do it — the open runs the funnel for that close first: its `vf-close` comes before the open's `vf-show`, and the late event finds the modal open and changes nothing, where it used to close the dialog that had just opened. That `vf-close` fires inside `show()`, so a one-time listener for the open's own answer goes on after `show()` (
|
|
237
|
+
- **Implementation:** wraps a native `<dialog>` (for top-layer + focus trap). `show()` → `showModal()`; `close()` closes. Keep `open` attr in sync both directions. Drag the title bar to move it (shared `DragController` with `vf-window`), stating `top`/`left` in system px; drags starting on the close widget are ignored (same composedPath guard as `vf-window`). Escape → close + `vf-close` detail `{ reason: 'escape' }`; close box/programmatic/close() → `{ reason: 'close' }`; a click outside under `light-dismiss` → `{ reason: 'outside' }`. **Answers** (`VfModalDialog`): `close(value)` closes with `value` as `returnValue`, and a `<form method="dialog">` inside the dialog closes it on submit with the submitter's `value`, a submitter's `formmethod="dialog"` too. The platform's own rule looks for a `<dialog>` around the form, and this one is in the shadow root, so the base listens for `submit` on the host and closes at the end of the submit's path (`deferActivation`), not at all if anything cancelled it. `vf-button`'s submit proxy carries its `value` with or without a `name`. `vf-close`'s detail carries `returnValue`: the value that close carried, or null (Escape, a click outside, the close box, `close()` alone, removal). Return in a text field runs the form's implicit submission through the ringed `variant="default"` submit button when the form has one, ahead of the first in tree order, so a Cancel/OK row answers OK (`requestImplicitSubmit`). `npm run verify:dialog`, ANSWERS. No backdrop dimming: `::backdrop { background: transparent; }`. **Light dismiss is target identity, not geometry** (`VfModalDialog`): the `::backdrop` hit-tests as its `<dialog>`, and the frame fills the dialog's box, so the dialog is a pointer event's *target* exactly when the pointer was outside the frame — the listeners sit on the `<dialog>` itself, attached while open. It takes both halves of the click: a `pointerdown` on the backdrop arms (any other press disarms, so a right-click macOS never releases can't leave a stale arm), and a `pointerup` on the backdrop with the same `pointerId` closes — the platform's own `closedby="any"` two-step, so a press that starts on a control and slides off, or a title-bar drag released past the frame, leaves the dialog open. Deliberately not the `click` event: UI Events dispatches a press-drag-release click at the common ancestor of the two targets, which for a press on the frame released outside is the dialog itself. The platform's `closedby` attribute is not used because its dismissal arrives as `cancel`, which the base already reads as Escape. The press is consumed either way — a modal's backdrop lets nothing beneath it see the click — which is what lets a page dismiss its About box without also selecting the icon under the pointer. `npm run verify:dialog`. **Keyboard: the Dialog Manager's two rules** (`VfModalDialog`). *Initial focus:* right after `showModal()`'s own focusing steps, focus is handed to `initialFocusTarget` — the first slotted control carrying `autofocus` or `autoselect`, else the first enabled text-entry control (`vf-text-field`, `vf-number-field`, `vf-text-area`, or a native text input/textarea), else the default button (`defaultButton`: the first enabled `vf-button[variant="default"]`, looked for among the slotted content and then the shadow tree), else nothing, leaving the browser's pick. A field marked `autoselect` has its whole text selected on every open, right after `vf-show` and once the field has rendered, so a value set before `show()` or in a `vf-show` handler is the text selected. Otherwise the modal selects no text: whether a field opens selected is the page's call. The classic dialog put the insertion point in its first text item and gave nothing else focus; the web needs a focused control, and the default button is the one that makes Return and Space work without a Tab. Left alone, `showModal()` focuses the first focusable thing in flat-tree order — Cancel in a Cancel/OK row, or a link in the body — so Return pressed the wrong thing. Both getters are `protected`, for a consumer's own modal to override. *Return/Enter:* a `keydown` listener on the `<dialog>`, bubble phase, attached while open with the light-dismiss trio. An uncancelled, unmodified Enter activates `defaultButton` (its `click()`, so a `type="submit"` default runs its proxy path) and cancels the keydown, which is what keeps a focused Cancel from firing itself — Space presses the focused control, as on the classic Mac. Anything that took Enter for itself cancelled the keydown first and is left alone: `vf-select`'s list, `vf-list`, an icon's rename, and a text field whose form ran its implicit submission (`requestImplicitSubmit` now reports whether a form owner took the press, and the fields cancel on true — so the submission runs once, not once per layer). A link keeps its own Enter. A multi-line editor (`<textarea>`, contenteditable) keeps Return for the newline; only `code === 'NumpadEnter'` fires the button there — the split a TextEdit item drew between the two keys. With no enabled default button the press is left native. Escape is unchanged (the `cancel` path above). **Removal while open is a close path** (`VfModalDialog.disconnectedCallback`): HTML's dialog removing steps skip the close algorithm entirely, which is exactly what a framework unmount does — the teardown routes through the same native-`close` funnel (`vf-close` fires on the removed element; nothing bubbles, it left the tree), `open` and the written origin reconcile so a re-append mounts closed, and focus returns to the element focused at open time. **Opened again before a close's `close` event comes** — the native event is queued, so a `close()` and a `show()` in one turn do it — the open runs the funnel for that close first: its `vf-close` comes before the open's `vf-show`, and the late event finds the modal open and changes nothing, where it used to close the dialog that had just opened. That `vf-close` fires inside `show()`, so a one-time listener for the open's own answer goes on after `show()` (system-online-sdk's `ask()` does). The open drops any close value, reason or keys still pending, so a close asked for in that `vf-close` leaves nothing for a later one. `npm run verify:dialog`, REOPEN. **The placement re-settles while open** whenever the dialog's own box resizes — slotted content upgrading after `showModal()`, `--vf-scale` moving under zoom — the viewport resizes, or the scale changes. What survives depends on how the modal got where it is: an unplaced one re-centers (it never claimed a spot, and the stranded case is exactly this one), while a dragged or authored origin is kept and only re-clamped on screen.
|
|
238
238
|
- **Visual (default chrome):** `vfModalFrame` with `vfTitleBar` set into it (§4) — the bar is literally the recipe `vf-window` uses, so it is identical by construction (stripes + centered title) rather than by matching copies; the frame is the modal double frame, **not** the window's shadowed `vfChromeFrame`. Outside → in: 1px outer rule; the 18px bar directly under it, 2px in at either end with its stripes starting 2px from the rule; the 2px band as the bar's floor (the bar's rule + the inner box's 1px top border); 2px gap + 2px band down the sides and along the bottom; no shadow. The body begins 20px below the dialog's top and 5px inside its other three edges. The bar takes the default `--vf-title-inset` (16px) — 60px when `closable`, the same clearance as `vf-window`, since the centered title needs symmetric room for the widget — and sets `touch-action: none` unconditionally, having no immovable state. `closable` renders the shared close box (`vfWindowWidgets` + `closeBox()` — byte-identical to `vf-window`'s, per the `moveable modal dialog.png` reference). Body is WHITE (`--vf-surface: #fff`) and is the content region, with no inset of its own: the positioning anchor, so a child placed with `top`/`left` measures from the frame's inner edge, and flow content starts at the same corner. Both chromes are full-height flex columns and the body takes the slack, for the same reason `vf-window`'s frame is: the declared `height` lands on the `<dialog>` (see `dialogSize`), and the recipes are skin only. The frame is the flex child of the `<dialog>` itself (`dialog[open]` is a flex column in `modalDialogStyles` — `[open]`-scoped, or it would out-cascade the UA's `dialog:not([open]) { display: none }`), not a `height: 100%` block: a percentage can't resolve against the undeclared-height dialog that only the UA's `dialog:modal` max-height caps, and that spill was how a viewport-tall modal stranded its buttons off-screen. **The box never grows, but over-stuffed content scrolls instead of clipping**: the body holds a `.content` scroll region (the default slot; part `content`). Placed children anchor to the body, outside that scroller, so they hold still while it scrolls. While the content fits, nothing matches. Once it overflows (`ScrollStateController`, the always-a-rail machinery), the region reserves the rail's 15px as its own right padding, wears the drawn `vfScrollRail` rail (§4) flush against the frame's inner band, which supplies the cell's outer line, and becomes a keyboard stop (`tabindex="0"`, `role="group"`, the kit's dotted ring) so the copy is scrollable without a pointer. The rail rides the body as an overlay pinned to its right edge, deliberately out of the layout flow: a rail *column*'s two fixed 15px arrow cells would hand the region a 32px minimum height, and a short dialog would then measure as fitting with the rail shown and overflowing without it — flip-flopping forever. The drop-open exemption is unchanged.
|
|
239
239
|
- **The CSS and the controller state the same thing.** `.content` is `overflow-y: hidden` until the controller flags overflow, then `overflow-y: scroll`. It is deliberately never `auto`: the controller ignores the body face's negative half-leading (`LEADING_SPILL_SYS`, src/scroll-state.ts — `vf-paragraph` sets a 12-system-px line box under a 16-system-px em, so the inline box spills 2 inkless system px past the block box and `scrollHeight` counts it), and `auto` has no way to know that. With `auto` the two disagreed, and a fixed info dialog whose copy ends in a `vf-paragraph` rubber-banded under the wheel with no rail shown — 6 CSS px at scale 3. `hidden` still scrolls programmatically, so `scrollIntoView` on a focused control is unaffected. `npm run verify:contract`, OVERFLOW group.
|
|
240
240
|
- **Visual (`frame="plain"`):** the same `vfModalFrame` (§4 — 1px outer, 2px gap, 2px inner band, no shadow, per `Windows/modal dialog.png`) with no title bar, and immovable like the original dBoxProc dialog (nothing renders a drag handle). The body begins 5px inside every edge. With no bar, `heading` only names the dialog and `closable` is ignored; a title in the body is the composer's, placed like any other item.
|
|
@@ -314,7 +314,7 @@ The color-swatch button: a well of solid color — a palette cell.
|
|
|
314
314
|
- **Attributes/props:** `value`, `placeholder`, `disabled`, `readonly`, `autoselect`, `type: string` (default `'text'`; pass through to input), `name`. The input-behavior attributes — `autocomplete`, `inputmode`, `enterkeyhint`, `maxlength`, `pattern`, `spellcheck`, `autocapitalize` — are **forwarded verbatim** from the host onto the inner input (observed attributes, not reactive properties: four of them are globals with IDL accessors already on `HTMLElement`, and a Lit `@property` would shadow the platform member — the `align`/`draggable` trap). The platform only honors them on the element that actually takes the input, which is in the shadow root.
|
|
315
315
|
- **Visual:** inner `<input>`: white bg, `1px solid black`, NO radius, height `var(--vf-control-height, 22px)`, `padding: 0 6px`, font tokens but `font-weight: var(--vf-font-weight, 700)`. `user-select: text`. Focus: for a **keyboard** focus, the kit's 1px dashed rule (`vfFocusUnderline`, §4) one blank system px row under the well — no dotted outline and no thickened border. A click leaves it unmarked: the insertion point is already the answer to where focus went (see §4 on why this can't be `:focus-visible`). Disabled: the text dims to gray; the black border stays. Selected text inverts to solid black-on-white (`.vf-field::selection`, using `--vf-highlight`/`--vf-highlight-text`) — the 1-bit System 7 selection, shared by all three editable fields via the `vfField` skin.
|
|
316
316
|
- **Structure:** the `<input>` sits in a `.vf-field-well` wrapper, which is what the focus rule hangs from and what carries `vf-snap`. A replaced element draws no pseudo-element of its own, and the host is never the thing grid snapping moves, so the wrapper is the only box that is both the well's exact shape and on the corrected grid. Same wrapper in all three fields, assembled by `VfTextControlBase.wellClass` so the focus gate can't drift between them. Two consequences when embedding: the rule paints 2 system px **below the host's own box** (`pointer-events: none`, so it never takes a click meant for what sits under it, but a tight `overflow: hidden` ancestor clips it), and a `width` set on `::part(input)` sizes the control without moving the wrapper the rule spans — a field's width belongs on the host or on `--vf-field-width`.
|
|
317
|
-
- **Behavior:** form-associated; syncs `value` on input; `formResetCallback` restores default. `select()` selects the whole value, as a native field's does, on the inner input; with a new value not yet rendered it selects once the value is in, so `field.value = x; field.select()` selects `x`. `focus()` focuses the inner input and keeps its caret (Chromium selects all of an input's text when a host's `delegatesFocus` hands the focus on). `autoselect` is `autofocus` plus a selection: the first control carrying either takes the opening focus of its `vf-dialog`, which selects the field's whole text on every open, or of a window
|
|
317
|
+
- **Behavior:** form-associated; syncs `value` on input; `formResetCallback` restores default. `select()` selects the whole value, as a native field's does, on the inner input; with a new value not yet rendered it selects once the value is in, so `field.value = x; field.select()` selects `x`. `focus()` focuses the inner input and keeps its caret (Chromium selects all of an input's text when a host's `delegatesFocus` hands the focus on). `autoselect` is `autofocus` plus a selection: the first control carrying either takes the opening focus of its `vf-dialog`, which selects the field's whole text on every open, or of a window system-online-sdk's window manager opens, which selects it on the window's first open only (that SDK's docs/SHELL.md). A click still places the caret. A commit — native `change`, leaving the field, or Return in a form — is announced once: a commit that brings nothing new since the field took focus or since the last commit fires nothing. In a form, Return commits first, then runs the form's implicit submission, the order a native input keeps, so `change` comes before the form's `submit`. That submission works the way HTML defines it — activating the form's default button (first submit button in tree order, `vf-button` included), so the submission carries a real `submitter` and that button's `name`/`value`, a disabled default button submits nothing, and only a form with no submit button falls back to a bare `requestSubmit()` (`requestImplicitSubmit`, text-control.ts). When a form owner took the press the keydown is cancelled, so an enclosing `vf-dialog` does not route the same Enter to its default button a second time; with no form the press goes on to the dialog, whose default button it activates.
|
|
318
318
|
- **Parts:** `input`.
|
|
319
319
|
- **Events:** `vf-input` detail `{ value }` on every keystroke; `vf-change` detail `{ value }` on commit. Plus the native pair per §2: the inner input's own composed `input` crosses the boundary itself; `change` is re-dispatched from the host.
|
|
320
320
|
|
|
@@ -365,7 +365,7 @@ The classic popup menu control ("Macintosh HD ▼").
|
|
|
365
365
|
#### `vf-menu-bar` (`VfMenuBar`, vf-menu-bar.ts)
|
|
366
366
|
- **Attributes/props:** `label: string` — accessible name for the menubar, mirrored as host `aria-label` (guarded: a consumer's own `aria-label`/`aria-labelledby` is left alone). `rounded: boolean` (reflected) — draws the System 7 screen-corner mask over the bar's top-left/top-right corners. `shortcuts: boolean` (reflected) — makes every slotted menu's item `shortcut`s live key equivalents, one declaration for the whole bar the way MenuKey() answered for every menu at once (see vf-menu-item for the claim contract). Off by default: key equivalents are page-global, and only *the* menu bar should own them.
|
|
367
367
|
- **Visual:** `display: block/flex`, height `var(--vf-menubar-height, 20px)` — 19 white system px over the 1px black rule, the Menus.png bar strip exactly — white bg, the rule `vfRule`'s `.vf-rule-bottom` on the bar (§4), children laid out horizontally from left. Adjacent titles are pulled 4px into each other (`margin-inline-start: -4px` on every slotted menu, absorbed by 13px of bar start padding — 9px of bar before the first plate plus the first title's 4px share): title ink sits 15px apart while each title's plate runs 10px left / 9px right of its own ink, so neighboring plates *overlap* by 4px. With `rounded`, two 5×5 system-px corner overlays paint the traced stair-step mask (per-row runs 5/3/2/1/1 — `SCREEN_CORNER` + `steppedCornerClip` in `pixel-frame.ts`) in `--vf-black` *over* the bar: on the classic screen the rounding was the ROM's black corner mask sitting on top of the menu bar, not a shape of the bar's own, so it is ink over any backdrop rather than a cutout.
|
|
368
|
-
- **Behavior:** container/controller for slotted `vf-menu` children. Pressing a menu label → opens it (label inverts while open). While any menu is open, hovering another label switches to it (classic behavior). Escape / outside click / item selection closes. `role="menubar"`, behind a first-connect ownership latch so a consumer's own role survives upgrade; the shadow `.bar` is `role="presentation"` and each slotted `vf-menu` host `role="none"`, so the `menubar → menuitem` ownership chain has no generics in it. While a menu is open
|
|
368
|
+
- **Behavior:** container/controller for slotted `vf-menu` children. Pressing a menu label → opens it (label inverts while open). While any menu is open, hovering another label switches to it (classic behavior). Escape / outside click / item selection closes. `role="menubar"`, behind a first-connect ownership latch so a consumer's own role survives upgrade; the shadow `.bar` is `role="presentation"` and each slotted `vf-menu` host `role="none"`, so the `menubar → menuitem` ownership chain has no generics in it. While a menu is open the keyboard is `src/menu-navigation.ts`'s, which a standalone `vf-menu` shares, acting on the deepest open menu holding focus (the APG menubar pattern): ArrowDown/Up walk its items, Home/End jump to its first/last enabled item, and printable keys run the shared Finder first-letter type-ahead over them (`src/type-ahead.ts`; Space stays the focused item's activation key, and the prefix resets on menu switch, a level change or close). ArrowRight on an item that opens a submenu opens it and focuses its first enabled item; on any other item it opens the next menu. ArrowLeft in a submenu closes it and refocuses its item; at the top level it opens the previous menu. Escape closes one level: a submenu back to its item, the menu back to its title. The bar also **owns the press-drag-release gesture** across its menus (`MenuPressController`, `src/menu-press.ts` — see `vf-menu`), since one press may travel over several of them: it binds the opening `pointerdown` and hands the controller its own open/close rules, so the gesture changes *when* a menu opens, never *how*.
|
|
369
369
|
- **Slots:** default (vf-menu elements); `end` — the bar's right end, where a clock goes: a flex row of its own pushed to the end with an auto start margin and held 9px in, mirroring the first title's inset, setting `--vf-label-line-height` to the bar's height so a `vf-label` there rides the titles' baseline. Empty, it is `display: none` and takes no part in the row, so a page that pushes its own item to the end keeps the whole of the slack. `npm run verify:archetypes` (END SLOT). **Parts:** `bar`.
|
|
370
370
|
|
|
371
371
|
#### `vf-menu` (`VfMenu`, vf-menu.ts)
|
|
@@ -374,18 +374,22 @@ The classic popup menu control ("Macintosh HD ▼").
|
|
|
374
374
|
- **Keyboard focus: no ring** — `vfFocusUnderline` (§4) at `--vf-focus-underline-offset: -2px`, a dashed rule one blank system px row under the `.title` box. That box is `line-height: 1`, i.e. the face's own em, whose bottom edge is the descent line — so the one offset clears a descender *and* a slotted 16px `vf-img` (the Apple menu), where the button's baseline-anchored rule is crossed by both. In `currentColor`, so it inverts with the title on an open menu's black cell.
|
|
375
375
|
- **Closed only** (`:host(:not([open]))`). A dropped menu inverts its whole cell, which already shows where focus is; the rule marks the state the inversion can't — focused but not yet open — and drawing both would put a second mark (in white, since it is `currentColor`) under the first. The class stays on through the open state, so the rule returns by itself when the menu closes and hands focus back.
|
|
376
376
|
- Gated on a `.vf-focus-rule` class from the page's input modality, not `:focus-visible` (§4): `MenuPressController` `preventDefault`s the opening `pointerdown` and calls `focusLabel()` itself. The menu also calls the controller's `suppress()` from its own host `pointerdown`, so a press on an already-focused title — which moves no focus, and so fires no `focusin` — still drops the mark, as does mousing into the dropped panel. `npm run verify:focus`.
|
|
377
|
-
- **Behavior:** delegates open-state coordination to parent `vf-menu-bar` when present (only one open at a time; standalone, `#requestToggle` doesn't emit the coordination event at all — see the events convention in §2). Keyboard on the label: Enter, Space and ArrowDown all open **and move focus to the first enabled item** (APG, menubar and menu-button patterns alike — opening without entering would park the keyboard on the title with the panel dropped). While open standalone the menu runs its
|
|
377
|
+
- **Behavior:** delegates open-state coordination to parent `vf-menu-bar` when present (only one open at a time; standalone, `#requestToggle` doesn't emit the coordination event at all — see the events convention in §2). Keyboard on the label: Enter, Space and ArrowDown all open **and move focus to the first enabled item** (APG, menubar and menu-button patterns alike — opening without entering would park the keyboard on the title with the panel dropped). While open standalone the menu runs the bar's keyboard itself (`src/menu-navigation.ts`, see `vf-menu-bar`) over its items and its submenus: ArrowDown/Up wrap, Home/End jump, printable keys run the shared first-letter type-ahead (`src/type-ahead.ts`), ArrowRight and ArrowLeft open and close submenus, Escape closes one level and at the top refocuses the label. Sets `--vf-separator-color: var(--vf-disabled, #c0c0c0)` on its panel so slotted `vf-separator`s render dimmed, each spending one full `--vf-menu-row-height` — the MDEF's divider-as-item — with the rule 8px in (12px under the ink band above, 10 over the one below, per InfiniteMac's System 7.5 Edit menu and the 32px cross-divider ink pitch in `Menus.png`). **Pointer:** the two styles `vf-select` supports, on the same terms and the same `PRESS_HOLD_MS` threshold (`src/motion.ts`) — the menus get theirs from `MenuPressController` (`src/menu-press.ts`), which a standalone menu hosts itself and a `vf-menu-bar` hosts for all of its menus. Opening is on **pointerdown**, and the press is then tracked to its release anywhere:
|
|
378
378
|
- *System 7 press-drag-release* — press the title, slide onto a command, release over it to run it. Sliding sideways onto another title switches menus mid-press. Releasing over a disabled row, a separator, the title, or off the menu closes with nothing chosen (the classic "release outside").
|
|
379
|
-
- *Modern click-to-open* — a quick in-place tap (under `PRESS_HOLD_MS`, 200ms) on a title leaves the menu dropped for a second, independent click; a *held* in-place press closes it, and a press on an already-dropped title closes it. Time is consulted only for an in-place release: any press that travels is a drag-pick however long it took. Hit-testing is by **coordinates**, not event target, so tracking survives touch's implicit pointer capture (every move is delivered to the pressed title)
|
|
379
|
+
- *Modern click-to-open* — a quick in-place tap (under `PRESS_HOLD_MS`, 200ms) on a title leaves the menu dropped for a second, independent click; a *held* in-place press closes it, and a press on an already-dropped title closes it. Time is consulted only for an in-place release: any press that travels is a drag-pick however long it took. Hit-testing is by **coordinates**, not event target, so tracking survives touch's implicit pointer capture (every move is delivered to the pressed title). The trailing `click` the browser synthesises after a press is swallowed by the label and by the row (the same guard `vf-select` uses), so a keyboard/assistive-tech click — which arrives with no preceding pointerdown — is the only click that still toggles or activates.
|
|
380
|
+
- *One pointer model for both.* While a menu is open the pointer is tracked with or without a button down; the button only changes what a release means. Each open menu highlights one row as `vf-menu-item[active]`: the row under the pointer, or, with the pointer elsewhere, the item whose submenu is open. `:hover` paints no row inside a menu, so none shows two. Hit-testing tries the open menus deepest first, since a submenu is painted over its parent.
|
|
381
|
+
- **Submenus.** A `vf-menu` in a `vf-menu-item`'s `submenu` slot is a submenu: no title, only its panel, beside its item. Its host is `role="none"`, its panel `role="menu"`, named by the item's `labelText` unless it has a `label`. The menu holding the item opens it through `expand(item, { focus })`, which closes the submenu already open in that menu and everything under it (`null` closes it); `expandedItem` reads it. The pointer resting on the item for `SUBMENU_OPEN_MS` (150ms) opens it, in either style; on another row of the same menu an open submenu closes after `SUBMENU_CLOSE_MS` (300ms), and never once the pointer reaches it, so a diagonal slide into it can cross the rows between (`src/motion.ts`, the interaction itself, so not reduced-motion gated). A release or a click on the item opens its submenu now and leaves every menu dropped, so the visitor carries on clicking; a finger taps it. A pick at any depth closes every menu through the usual close request, which a submenu passes up; a key equivalent's flash request passes up to the top title too. Closing a menu closes its open submenu. Menus nest to any depth.
|
|
382
|
+
- *Placement*, traced from an Infinite Mac capture of System 7.5's Apple menu at 3×: the submenu's top frame is the row above its item, so its first row is level with the item, and its left frame is 5px left of the parent's right frame, covering the parent's last 4px, frame and shadow, the item's highlight running up to it. The host is a box as wide as the row at the row's top (the item's `.submenu`), and the panel hangs from it by those offsets. It opens toward the side its parent opened toward, the right first, and flips (mirrored: its right frame 5px right of the parent's left frame) where that side has no room and the other has. It is raised by whole system px to fit above the screen's bottom, never past its top. The screen is the closest `vf-desktop`'s `.screen` below its menu bar, within the viewport, else the viewport. It stays inside its parent's panel, so it paints in the parent's stacking context and takes its grid correction.
|
|
380
383
|
- **Slots:** default (vf-menu-item / vf-separator), `label` (replaces the `label` text in the bar — e.g. the Apple menu's `vf-img` apple; the `label` attribute stays set as the accessible name, mirrored to the bar item's `aria-label`). **Parts:** `label`, `panel`.
|
|
381
384
|
- **An icon title.** When the `label` slot holds only art (`vf-img`, `img`, `svg`, `picture` or `canvas`), the title is drawn as an icon: the art goes in a 16×16 cell, 6 system px into its plate and 6 short of the plate's end (`padding-inline` 6/6 in place of the text title's 9/8), on the plate's top row: one row above where centering on the bar's line would put it. Art drawn from x 2, y 1 of its cell, as the apple is (its ink is 11 × 14 at x 2..12, y 1..14), then has 8 px of plate before its ink: as the first title, the cell lands at x 15, its ink at x 17..27 on rows 2..15, and the next title's plate at x 33, its ink at x 43. A text title, from `label` or text in the slot, keeps the text geometry. `npm run verify:archetypes` (ICON TITLE).
|
|
382
385
|
|
|
383
386
|
#### `vf-menu-item` (`VfMenuItem`, vf-menu-item.ts)
|
|
384
|
-
- **Attributes/props:** `disabled`, `checked` (shows ✓ in left gutter), `checkable` (declares a toggle up front — see Behavior), `shortcut: string` (e.g. `"⌘H"`, drawn in the left-aligned shortcut column; a **live key equivalent** under a `shortcuts` grant — see Behavior), `value?: string` (defaults to
|
|
385
|
-
- **
|
|
387
|
+
- **Attributes/props:** `disabled`, `checked` (shows ✓ in left gutter), `checkable` (declares a toggle up front — see Behavior), `shortcut: string` (e.g. `"⌘H"`, drawn in the left-aligned shortcut column; a **live key equivalent** under a `shortcuts` grant — see Behavior), `value?: string` (defaults to `labelText`), `active` (reflect; the highlight under the pointer, or on the item whose submenu is open, managed by the menu — mirrors `vf-option[active]`, not an authoring API), `expanded` (reflect; its submenu is open, managed by the menu, announced as `aria-expanded`). Read-only: `submenu` (the `vf-menu` in the `submenu` slot, or null) and `labelText` (the default slot's text, trimmed, without a submenu's rows: the default value, the menus' type-ahead and a submenu item's name read it).
|
|
388
|
+
- **Submenu items.** An item holding a `vf-menu` in its `submenu` slot draws ▶ (`CARET_RIGHT`, 6×11, `CARET_DOWN` on its side, traced from `Menus.png`'s submenu rows) where a shortcut would go: 8px in from the row's end, as a System 7.5 capture has it too, 2 rows down the 16px row (centered, the odd row below), with an 8px label↔▶ minimum. Its `shortcut` isn't drawn, announced or matched. It is never chosen: a click, Enter, Space or `activate()` opens its submenu and focuses the submenu's first enabled item. It is `menuitem` with `aria-haspopup="menu"` and `aria-expanded`, and its accessible name is its `labelText`, so an open submenu's rows never join it. Events from the submenu pass through it on their way up, and it leaves them alone: a click, press or keydown that came from inside its submenu is that row's.
|
|
389
|
+
- **Visual:** height `var(--vf-menu-row-height, 16px)` — `Menus.png` puts every menu row on a 16px pitch (3px above + the 9px glyph + 4px below), so a pulldown row matches a popup row exactly; the line box is locked to the same expression so an inherited line-height can never overflow the panel. `padding: 0 12px 0 var(--vf-select-gutter, 16px)` (left gutter for ✓, shared with `vf-select`/`vf-option` — `Menus.png` puts a pulldown's label ink at the same inset as a popup's; a shortcut row trades the 12 for 1px, the slot being the clearance there). The shortcut is left-aligned in a right-anchored `--vf-menu-shortcut-column` (23px) slot — every ⌘ at the same x, 23px from the right border, the widest letters running to within ~3px of it (`Menus.png`'s File pulldown) — with an 8px label↔shortcut min gap. `color: var(--vf-disabled)` when disabled. `[active]`, keyboard focus, and hover for a row outside a menu (not disabled): full-width inversion — each with its own `.blink-off` override at matching specificity, so a drag-picked row keeps its flag through the blink and the release reads as the highlight flashing off. Inside a menu the pointer's row is the menu's `[active]`, since the item whose submenu is open stays lit while the pointer is in the submenu. A disabled row dims its ✓ along with the label — a **documented deviation** from the §1 "dim the label, chrome glyphs stay black" rule, because authentic System 7 greyed the whole disabled row.
|
|
386
390
|
- **Behavior:** `role="menuitem"` — or `role="menuitemcheckbox"` with `aria-checked` when the item is *checkable*: either `checkable` is set, or the item has ever been `checked`. Set `checkable` on a toggle that starts **off**, which would otherwise announce as a plain command until its first flip (a boolean `checked` attribute can't express "checkable but off"). The role is re-derived on every connect, so re-parenting a checkable item keeps it; an author-supplied `role` is left alone. On activation — a click, Enter/Space, or the public `activate()` the menu's press gesture calls for the row a drag was released over (which the row's own `click` never sees, since a press that started on the title dispatches its click above both): classic **blink** (invert toggles 3 times over ~250ms via timer; skipped under `prefers-reduced-motion`, selecting at once), then dispatch `vf-menu-select` detail `{ value, item }` and signal ancestors to close the menu — the close signal is skipped when the ancestor menu is already closed (the key-equivalent path below), since the close paths return focus to the bar label and would yank it from wherever the user was typing. Disabling an item mid-blink cancels it and drops the pending `vf-menu-select`.
|
|
387
391
|
- **Key equivalents** (the MenuKey() half of `shortcut`): under an ancestor `vf-menu[shortcuts]` / `vf-menu-bar[shortcuts]` grant, a document-level keydown matching the shortcut activates the item from anywhere on the page, menu open or not — File → Save *shows* ⌘S and *answers* it, no page-side plumbing. Matching is exact on modifiers (⌘=Meta, ⌃=Control, ⌥=Alt, ⇧=Shift — the same mapping `aria-keyshortcuts` announces) with `event.key` compared case-insensitively, except that a declared ⌘ is met by Control too — Ctrl standing in for ⌘ off the Mac, vf-icon's own ⌘O reading — while a shortcut spelling ⌃ itself keeps meaning the Control key alone; the grant is checked per event, so toggling it needs no re-wiring. The claim contract: a `defaultPrevented` stroke is already someone's (a page handler that ran first keeps its key); a match claims with `preventDefault()` and no `stopPropagation`, so the first connected item wins a contested key and later page listeners still observe the claimed event; a **disabled** item claims nothing — the stroke falls through, so a grayed Undo leaves ⌘Z to the focused field's own native undo; auto-repeat strokes are claimed but activate once; a shortcut with no ⌘/⌃/⌥ and a single printable key renders and announces but never matches (a bare letter claimed globally would hijack typing — a named key like "F1" needs no modifier). The arrow glyphs ← ↑ → ↓ name the arrow keys: "⌘→" matches `ArrowRight` and announces as `Meta+ArrowRight`; a bare arrow counts as a single key and never matches, so it can't take the arrows from a list, a menu or a field. Activation is the normal path — blink, `vf-menu-select` — and a **closed** ancestor menu answers by flashing its bar title on the same cadence (MenuKey's acknowledgment; skipped with the blink under `prefers-reduced-motion`): the panel never opens and focus never moves. `npm run verify:menu-keys`.
|
|
388
|
-
- **Slots:** default (label). **Parts:** `item`, `check`, `label`, `shortcut`.
|
|
392
|
+
- **Slots:** default (label), `submenu` (a `vf-menu`). **Parts:** `item`, `check`, `label`, `shortcut`, `arrow`.
|
|
389
393
|
- **Events:** `vf-menu-select` — menu-specific by design. A plain `vf-select` would collide with the `<vf-select>` popup on any delegated ancestor listener, since both bubble and compose while `<vf-select>` itself commits with `vf-change`. Parallels the existing `vf-menu-*` coordination names.
|
|
390
394
|
|
|
391
395
|
#### `vf-list` (`VfList`, vf-list-item children) (vf-list.ts)
|
|
@@ -766,7 +766,7 @@
|
|
|
766
766
|
},
|
|
767
767
|
{
|
|
768
768
|
"name": "vf-menu-bar",
|
|
769
|
-
"description": "`<vf-menu-bar>` — the System 7 menu bar: white strip, 1px black bottom\nrule, slotted `<vf-menu>` children laid out from the left. `rounded` adds\nthe classic screen-corner mask to the top corners.\n\nCoordinates its menus: pressing a label opens that menu (and inverts the\nlabel); while any menu is open, hovering another label switches to it;\nEscape, an outside click, or item selection closes.
|
|
769
|
+
"description": "`<vf-menu-bar>` — the System 7 menu bar: white strip, 1px black bottom\nrule, slotted `<vf-menu>` children laid out from the left. `rounded` adds\nthe classic screen-corner mask to the top corners.\n\nCoordinates its menus: pressing a label opens that menu (and inverts the\nlabel); while any menu is open, hovering another label switches to it;\nEscape, an outside click, or item selection closes. While a menu is open\nthe keyboard is src/menu-navigation.ts's: ArrowDown/ArrowUp move focus\nthrough the open menu's items, Home/End jump to its first/last, typed\nletters run the shared Finder first-letter type-ahead (src/type-ahead.ts),\nArrowRight opens an item's submenu and ArrowLeft closes it, and at the top\nlevel ArrowLeft/ArrowRight move between menus.\n\nThe bar also owns the **press-drag-release** gesture (see src/menu-press.ts)\n— press a title, slide onto a command, release over it — because one press\nmay travel across several of its menus. `vf-select` drives its popup by the\nsame mechanic.\n---\n\n\n### **Slots:**\n - _default_ - `vf-menu` elements.\n- **end** - The bar's right end: a clock, say. Held 9px in from the end, and a `vf-label` there rides the bar's line, so its baseline is the menu titles'.\n\n### **CSS Properties:**\n - **--vf-menubar-height** - `vf-menu-bar` _(default: 20px)_\n\n### **CSS Parts:**\n - **bar** - The horizontal layout container.",
|
|
770
770
|
"attributes": [
|
|
771
771
|
{
|
|
772
772
|
"name": "label",
|
|
@@ -808,7 +808,7 @@
|
|
|
808
808
|
},
|
|
809
809
|
{
|
|
810
810
|
"name": "vf-menu-item",
|
|
811
|
-
"description": "`<vf-menu-item>` — a single command inside a `<vf-menu>` panel.\n\nRenders the classic System 7 menu row: optional ✓ check in the\n`--vf-select-gutter` left column (16px, shared with vf-select/vf-option),\nlabel, and the keyboard shortcut left-aligned in a right-anchored column,\nso every ⌘ lands at the same x. On activation the item performs the classic\n3-blink inversion (~250ms), then dispatches `vf-menu-select` and asks its\nancestors to close the menu.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants a row somewhere other than a pulldown. Inside its parent\n`<vf-menu>` the panel is as wide as its widest row and stacks them in flow,\nso stating an origin takes that row out of both: it no longer contributes to\nthe panel's width and the rows below close the gap. The placement doing what\nit says — but not how a pulldown is laid out.\n---\n\n\n### **Events:**\n - **vf-menu-select** - After the blink completes. `detail: { value, item }`. Named for the menu rather than plain `vf-select`, which would collide with the `<vf-select>` popup on any delegated ancestor listener (that component commits with `vf-change`).\n\n### **Slots:**\n - _default_ - The item label.\n\n### **CSS Properties:**\n - **--vf-menu-row-height** - `vf-menu-item` row pitch (`Menus.png`; kept separate from `--vf-popup-height` so re-theming the popup pill doesn't move pulldown rows) _(default: 16px)_\n- **--vf-menu-shortcut-column** - `vf-menu-item` shortcut slot, right-anchored with the text left-aligned in it so every ⌘ lands at the same x (`Menus.png`) — the MDEF reserve, ⌘'s 11px advance + the face's widest letter (M/W, 12px); widen it to line up longer shortcuts (\"⌘⇧S\") _(default: 23px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **item** - The row container.\n- **check** - The ✓ checkmark glyph (rendered when `checked`).\n- **label** - The label wrapper around the default slot.\n- **shortcut** - The shortcut text, left-aligned in the shared column.",
|
|
811
|
+
"description": "`<vf-menu-item>` — a single command inside a `<vf-menu>` panel.\n\nRenders the classic System 7 menu row: optional ✓ check in the\n`--vf-select-gutter` left column (16px, shared with vf-select/vf-option),\nlabel, and the keyboard shortcut left-aligned in a right-anchored column,\nso every ⌘ lands at the same x. On activation the item performs the classic\n3-blink inversion (~250ms), then dispatches `vf-menu-select` and asks its\nancestors to close the menu.\n\nA `<vf-menu>` in the `submenu` slot makes the item open a submenu: it ends\nin ▶ where a shortcut would go, and choosing it opens the submenu beside\nit rather than firing `vf-menu-select`. The item stays highlighted while\nits submenu is open.\n\nTakes `top`/`left` like every other element (VfPositioned), for the\nconsumer who wants a row somewhere other than a pulldown. Inside its parent\n`<vf-menu>` the panel is as wide as its widest row and stacks them in flow,\nso stating an origin takes that row out of both: it no longer contributes to\nthe panel's width and the rows below close the gap. The placement doing what\nit says — but not how a pulldown is laid out.\n---\n\n\n### **Events:**\n - **vf-menu-select** - After the blink completes. `detail: { value, item }`. Named for the menu rather than plain `vf-select`, which would collide with the `<vf-select>` popup on any delegated ancestor listener (that component commits with `vf-change`).\n\n### **Slots:**\n - _default_ - The item label.\n- **submenu** - A `vf-menu`, the submenu the item opens.\n\n### **CSS Properties:**\n - **--vf-menu-row-height** - `vf-menu-item` row pitch (`Menus.png`; kept separate from `--vf-popup-height` so re-theming the popup pill doesn't move pulldown rows) _(default: 16px)_\n- **--vf-menu-shortcut-column** - `vf-menu-item` shortcut slot, right-anchored with the text left-aligned in it so every ⌘ lands at the same x (`Menus.png`) — the MDEF reserve, ⌘'s 11px advance + the face's widest letter (M/W, 12px); widen it to line up longer shortcuts (\"⌘⇧S\") _(default: 23px)_\n- **--vf-select-gutter** - checkmark column: `vf-select` left inset / `vf-option` + `vf-menu-item` ✓ column (shared so the value doesn't shift on open) _(default: 16px)_\n\n### **CSS Parts:**\n - **item** - The row container.\n- **check** - The ✓ checkmark glyph (rendered when `checked`).\n- **label** - The label wrapper around the default slot.\n- **shortcut** - The shortcut text, left-aligned in the shared column.\n- **arrow** - The ▶ of an item that opens a submenu.",
|
|
812
812
|
"attributes": [
|
|
813
813
|
{
|
|
814
814
|
"name": "disabled",
|
|
@@ -822,7 +822,12 @@
|
|
|
822
822
|
},
|
|
823
823
|
{
|
|
824
824
|
"name": "active",
|
|
825
|
-
"description": "Transient highlight — the full-row inversion
|
|
825
|
+
"description": "Transient highlight — the full-row inversion on the row under the pointer\nwhile its menu is open, button down or not (`:hover` can't: under touch\nthe pointer is captured by the title the press started on), or on the\nitem whose submenu is open while the pointer is elsewhere. Managed by the\nmenu, mirroring `vf-option[active]`; not part of the authoring API.",
|
|
826
|
+
"values": []
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
"name": "expanded",
|
|
830
|
+
"description": "Whether this item's submenu is open. Reflected, and announced as\n`aria-expanded`. Managed by the menu (VfMenu.expand), like\nactive; not part of the authoring API.",
|
|
826
831
|
"values": []
|
|
827
832
|
},
|
|
828
833
|
{
|
|
@@ -837,7 +842,7 @@
|
|
|
837
842
|
},
|
|
838
843
|
{
|
|
839
844
|
"name": "value",
|
|
840
|
-
"description": "Value reported in the `vf-menu-select` event detail. Defaults to the item's\
|
|
845
|
+
"description": "Value reported in the `vf-menu-select` event detail. Defaults to the item's\nlabelText when unset.",
|
|
841
846
|
"values": []
|
|
842
847
|
},
|
|
843
848
|
{
|
|
@@ -865,7 +870,7 @@
|
|
|
865
870
|
},
|
|
866
871
|
{
|
|
867
872
|
"name": "vf-menu",
|
|
868
|
-
"description": "`<vf-menu>` — a pull-down menu: a bar label plus a dropped panel of\n`<vf-menu-item>` / `<vf-separator>` children.\n\nInside a `<vf-menu-bar>` the bar coordinates open state (only one menu open,\nhover-switching, outside-click/Escape dismissal) and owns the pointer\ngesture, which may travel between its menus. Used standalone, the menu\ntoggles itself on label click and manages its own dismissal, its own press\ngesture and item keyboard navigation (ArrowUp/ArrowDown, Home/End, and the\nshared first-letter type-ahead — src/type-ahead.ts) while open.\n\nPointer — the two styles `vf-select` supports, on the same terms (see\nsrc/menu-press.ts): the System 7 press-drag-release (press the title, slide\nonto a command, release over it) and a modern quick tap that leaves the menu\ndropped for a second click.\n\nTakes `top`/`left` like every other element (VfPositioned) — the\nnatural fit being a standalone menu, which is a free-standing menu button and\nplaces like any other control. The panel is anchored to the host's own box\n(`top: 100%`), so it follows a placed menu down without any further work.\nInside a `<vf-menu-bar>`, the bar lays its titles out in flow — placing one\nlifts it off the bar and the titles beside it close the gap. Slotted straight\ninto a `<vf-desktop>`, a free-standing menu sits on the desktop's menu tier\nabove every window, palettes included, as a bar does.\n---\n\n\n### **Slots:**\n - _default_ - Menu contents: `vf-menu-item` and `vf-separator` elements.\n- **label** - Replaces the `label` text in the bar — e.g. a `vf-img` apple icon for the Apple menu. An icon (`vf-img`, `img`, `svg`, `picture` or `canvas`) is drawn in a 16×16 cell and placed for one: 6 system px into its plate, 6 short of the plate's end, on the plate's top row. Text keeps the text title's place. Keep the `label` attribute set too: it stays the menu's accessible name (the bar item's `aria-label` and the panel's) when the visible title is an image.\n\n### **CSS Properties:**\n - **--vf-menubar-height** - `vf-menu-bar` _(default: 20px)_\n- **--vf-menu-row-height** - `vf-menu-item` row pitch; the panel also spends one of these on every slotted `vf-separator`, the full row the MDEF gave a divider (rule 8px into it — see the panel CSS) _(default: 16px)_\n\n### **CSS Parts:**\n - **label** - The menu title in the bar (inverts while open; flashes when a closed menu's item is activated by its key equivalent).\n- **panel** - The dropped `.vf-panel` containing the items.",
|
|
873
|
+
"description": "`<vf-menu>` — a pull-down menu: a bar label plus a dropped panel of\n`<vf-menu-item>` / `<vf-separator>` children.\n\nInside a `<vf-menu-bar>` the bar coordinates open state (only one menu open,\nhover-switching, outside-click/Escape dismissal) and owns the pointer\ngesture, which may travel between its menus. Used standalone, the menu\ntoggles itself on label click and manages its own dismissal, its own press\ngesture and item keyboard navigation (ArrowUp/ArrowDown, Home/End, and the\nshared first-letter type-ahead — src/type-ahead.ts) while open.\n\nPointer — the two styles `vf-select` supports, on the same terms (see\nsrc/menu-press.ts): the System 7 press-drag-release (press the title, slide\nonto a command, release over it) and a modern quick tap that leaves the menu\ndropped for a second click.\n\nA `vf-menu` in a `vf-menu-item`'s `submenu` slot is a **submenu**: no title,\nonly its panel, opened beside its item by the menu holding the item\n(expand). The pointer resting on the item opens it in either style,\nand so do a click or tap on the item, →, Enter and Space. It overlaps its\nparent as System 7.5's did: its first row level with its item, its frame\n5px inside the parent's right edge, covering the parent's frame and\nshadow. It opens on the side its parent opened toward, the right first,\nflips where that side has no room, and moves up to fit the screen. Menus\nnest to any depth.\n\nTakes `top`/`left` like every other element (VfPositioned) — the\nnatural fit being a standalone menu, which is a free-standing menu button and\nplaces like any other control. The panel is anchored to the host's own box\n(`top: 100%`), so it follows a placed menu down without any further work.\nInside a `<vf-menu-bar>`, the bar lays its titles out in flow — placing one\nlifts it off the bar and the titles beside it close the gap. Slotted straight\ninto a `<vf-desktop>`, a free-standing menu sits on the desktop's menu tier\nabove every window, palettes included, as a bar does.\n---\n\n\n### **Slots:**\n - _default_ - Menu contents: `vf-menu-item` and `vf-separator` elements.\n- **label** - Replaces the `label` text in the bar — e.g. a `vf-img` apple icon for the Apple menu. An icon (`vf-img`, `img`, `svg`, `picture` or `canvas`) is drawn in a 16×16 cell and placed for one: 6 system px into its plate, 6 short of the plate's end, on the plate's top row. Text keeps the text title's place. Keep the `label` attribute set too: it stays the menu's accessible name (the bar item's `aria-label` and the panel's) when the visible title is an image.\n\n### **CSS Properties:**\n - **--vf-menubar-height** - `vf-menu-bar` _(default: 20px)_\n- **--vf-menu-row-height** - `vf-menu-item` row pitch; the panel also spends one of these on every slotted `vf-separator`, the full row the MDEF gave a divider (rule 8px into it — see the panel CSS) _(default: 16px)_\n\n### **CSS Parts:**\n - **label** - The menu title in the bar (inverts while open; flashes when a closed menu's item is activated by its key equivalent).\n- **panel** - The dropped `.vf-panel` containing the items.",
|
|
869
874
|
"attributes": [
|
|
870
875
|
{
|
|
871
876
|
"name": "label",
|
|
@@ -961,7 +966,7 @@
|
|
|
961
966
|
},
|
|
962
967
|
{
|
|
963
968
|
"name": "autoselect",
|
|
964
|
-
"description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window
|
|
969
|
+
"description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
|
|
965
970
|
"values": []
|
|
966
971
|
},
|
|
967
972
|
{
|
|
@@ -1619,7 +1624,7 @@
|
|
|
1619
1624
|
},
|
|
1620
1625
|
{
|
|
1621
1626
|
"name": "autoselect",
|
|
1622
|
-
"description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window
|
|
1627
|
+
"description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
|
|
1623
1628
|
"values": []
|
|
1624
1629
|
},
|
|
1625
1630
|
{
|
|
@@ -1696,7 +1701,7 @@
|
|
|
1696
1701
|
},
|
|
1697
1702
|
{
|
|
1698
1703
|
"name": "autoselect",
|
|
1699
|
-
"description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window
|
|
1704
|
+
"description": "Opens with the whole text selected, so typing replaces it. Implies\n`autofocus`: the first control carrying either takes the opening focus of\nits `vf-dialog`, or of a window system-online-sdk's window manager opens.\nA dialog selects the text on every open, a window on its first open only.\nA click still places the caret.",
|
|
1700
1705
|
"values": []
|
|
1701
1706
|
},
|
|
1702
1707
|
{
|