vintage-frames 0.14.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/custom-elements.json +770 -74
  2. package/dist/chrome.d.ts +10 -1
  3. package/dist/chrome.js +8 -3
  4. package/dist/components/vf-button.d.ts +5 -1
  5. package/dist/components/vf-button.js +1 -1
  6. package/dist/components/vf-desktop.d.ts +5 -2
  7. package/dist/components/vf-desktop.js +5 -5
  8. package/dist/components/vf-dialog.d.ts +13 -2
  9. package/dist/components/vf-dialog.js +27 -27
  10. package/dist/components/vf-menu-bar.js +9 -10
  11. package/dist/components/vf-menu-item.js +18 -13
  12. package/dist/components/vf-menu.d.ts +8 -3
  13. package/dist/components/vf-menu.js +85 -61
  14. package/dist/components/vf-number-field.d.ts +4 -0
  15. package/dist/components/vf-number-field.js +28 -25
  16. package/dist/components/vf-paragraph.d.ts +3 -1
  17. package/dist/components/vf-paragraph.js +7 -0
  18. package/dist/components/vf-scroll-area.d.ts +16 -1
  19. package/dist/components/vf-scroll-area.js +39 -29
  20. package/dist/components/vf-select.d.ts +5 -4
  21. package/dist/components/vf-select.js +26 -17
  22. package/dist/components/vf-text-field.d.ts +5 -5
  23. package/dist/components/vf-text-field.js +1 -1
  24. package/dist/components/vf-window.d.ts +22 -5
  25. package/dist/components/vf-window.js +113 -98
  26. package/dist/cursor.d.ts +8 -0
  27. package/dist/cursor.js +70 -73
  28. package/dist/events.d.ts +3 -2
  29. package/dist/events.js +3 -3
  30. package/dist/index.d.ts +2 -1
  31. package/dist/modal-dialog.d.ts +49 -4
  32. package/dist/modal-dialog.js +115 -87
  33. package/dist/position.js +25 -20
  34. package/dist/shell/alert.d.ts +17 -9
  35. package/dist/shell/alert.js +25 -28
  36. package/dist/shell/catalog.d.ts +1 -1
  37. package/dist/shell/catalog.js +32 -18
  38. package/dist/shell/finder.d.ts +46 -1
  39. package/dist/shell/finder.js +166 -126
  40. package/dist/shell/index.d.ts +2 -4
  41. package/dist/shell/index.js +5 -6
  42. package/dist/shell/pure.d.ts +10 -0
  43. package/dist/shell/pure.js +4 -0
  44. package/dist/shell/shell.d.ts +40 -13
  45. package/dist/shell/shell.js +87 -51
  46. package/dist/shell/windows.d.ts +69 -12
  47. package/dist/shell/windows.js +245 -144
  48. package/dist/text-control.d.ts +30 -1
  49. package/dist/text-control.js +27 -6
  50. package/dist/top-layer.d.ts +13 -0
  51. package/dist/top-layer.js +16 -0
  52. package/docs/SPEC.md +25 -24
  53. package/editor/vscode.html-custom-data.json +22 -12
  54. package/editor/web-types.json +58 -25
  55. package/package.json +5 -1
@@ -64,7 +64,7 @@
64
64
  },
65
65
  {
66
66
  "name": "value",
67
- "description": "Value submitted under `name` when `type=\"submit\"`.",
67
+ "description": "Value submitted under `name` when `type=\"submit\"`. In a\n`<form method=\"dialog\">` inside a `vf-dialog`, the dialog's answer\n(`returnValue`), with or without a `name`.",
68
68
  "values": []
69
69
  },
70
70
  {
@@ -251,7 +251,7 @@
251
251
  },
252
252
  {
253
253
  "name": "vf-desktop",
254
- "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the screen-corner mask is in front of it.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\n**Hidden windows.** A window with `hidden` — closed by a page that hides\nits windows rather than removing them, through `vf-window.hide()` or the\nattribute itself — never holds the active state. One slotted hidden waits\nto be shown; `bringToFront()` on one gives it its place in the stack and\nactivates nothing; the holder hiding hands the state to the topmost shown\ndocument window, or to none. A window shown into a tier where none is\nactive, unless the tier was deliberately deactivated, takes it.\nstackingOrder lists the windows as they stand, bottom-most first.\n\n**The work area.** A slotted `vf-menu-bar` along the top takes its band\noff the screen: workArea is what is left, and the gestures on the\nscreen stay in it — a window dragged up stops with its title bar under the\nbar, where it can still be grabbed, and an icon dropped, nudged or walked\non the desktop lands below the bar. `window-top` deepens the area for\nwindows alone (windowArea), for a page with a strip of its own\nunder the bar. A gesture asks for the box it is clamped into with\n`vf-placement-bounds-request` (bubbles, non-composed), the drag surface's\nhandshake, and the desktop answers for the hosts placed on its screen.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, a repeating tile drawn at the display's resolution, 1-bit at every\ndensity and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM or hiding with no shown window behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
254
+ "description": "`<vf-desktop>` — the full-bleed classic desktop container.\n\nRenders the desktop pattern — the classic 50% dither by default, or any of\nthe standard patterns by name (`pattern`, System 7's General Controls\nsetting) — and manages the stacking order and `active` state of slotted\n`vf-window` children: a `pointerdown` or\n`focusin` (keyboard focus) anywhere inside a window brings it to the front\nand makes it the single active window. A press on a title bar with ⌘ held\nraises nothing, so a background window is dragged where it stands in the\nstack. The windows' light-DOM order is kept\nin step with the stacking order (bottom-most first, synced in a task once\nany pointer gesture has ended), so tabbing walks the stack the way the eye\ndoes and Shift+Tab is its exact mirror.\n\nUtility windows (`vf-window[variant=\"utility\"]`) stack in a floating tier\nabove every document-tier window, restack only among themselves, and stand\noutside the single-active invariant entirely — clicking a palette neither\ndeactivates the active document window nor greys the palette, exactly as\nSystem 7's floating windoids behaved while their application was frontmost.\nA slotted `vf-menu-bar` (or a free-standing `vf-menu`) sits on a tier\nabove both, so its dropped menus cover palettes and document windows\nalike; only the screen-corner mask is in front of it.\n\n**Deactivation.** On a real System 7 machine clicking the desktop clicked\nthe *Finder* — the frontmost application's windows lost their stripes.\nclearActive is that gesture's handler: it clears `active` from\nthe whole document tier, and **zero active windows is a legal state**,\nheld until a press or keyboard focus re-enters a document window (or a\nnew one is slotted, which activates it — opening a window brings its\napplication forward). The desktop never takes this decision itself: its\nfurniture is slotted light DOM (an icon layer, say), so only the page\nknows which of its children — or which presses on the bare dither — mean\n\"the Finder\", and it routes those through `clearActive()`. Left alone,\nthe classic always-one-active behavior is unchanged. activeWindow\nreads the current holder, and every change of holder — including to and\nfrom none — fires `vf-activate`.\n\n**Hidden windows.** A window with `hidden` — closed by a page that hides\nits windows rather than removing them, through `vf-window.hide()` or the\nattribute itself — never holds the active state. One slotted hidden waits\nto be shown; `bringToFront()` on one gives it its place in the stack and\nactivates nothing; the holder hiding hands the state to the topmost shown\ndocument window, or to none. A window shown into a tier where none is\nactive, unless the tier was deliberately deactivated, takes it.\nstackingOrder lists the windows as they stand, bottom-most first.\n\n**The work area.** A slotted `vf-menu-bar` along the top takes its band\noff the screen: workArea is what is left, and the gestures on the\nscreen stay in it — a window dragged up stops with its title bar under the\nbar, where it can still be grabbed, and an icon dropped, nudged or walked\non the desktop lands below the bar. `window-top` deepens the area for\nwindows alone (windowArea), for a page with a strip of its own\nunder the bar. A gesture asks for the box it is clamped into with\n`vf-placement-bounds-request` (bubbles, non-composed), the drag surface's\nhandshake, and the desktop answers for the hosts placed on its screen.\n\nThe desktop is a raster with an explicit size, always: **`width` and\n`height`**, in system px, the way a WIND resource declared a window's —\nthe host box renders at the declared screen plus `2 × bezel` per axis, a\nwhole number of system pixels by construction (default 512×342, the\ncompact Mac's screen). Pure CSS sizing is not supported; the page sets\nthe numbers — directly, or via VfDesktop.fitWithin on\n`resize`/`onScaleChange` for a viewport-filling desktop — and positions\nthe sized box with its own stylesheet, keeping any sub-system-pixel\nslack on its side. `bezel` (system px) draws the black screen surround —\nthe CRT's unlit margin — around the screen, rounding its four corners\nwith the classic corner mask.\n\n**`pattern`** names the desktop pattern — `gray-50` (the dither) by\ndefault, any of the 38 standard patterns (docs/PATTERNS.md), or sixteen\nhex digits stating a custom 8×8 pattern, as on `vf-container`. It is\npainted as the screen's own background: black ink on an opaque white\npaper, a repeating tile drawn at the display's resolution, 1-bit at every\ndensity and zoom (src/pattern-fill.ts).\n\n**The drag surface.** The Finder dragged an icon as a dotted outline drawn\non the *screen*, clipped at its edge and phase-locked to its raster, so\nthe screen hosts the layer that outline draws on: a full-bleed child of\nthe screen, `pointer-events: none` (never a hit for the page's\n`elementsFromPoint`), whose children paint one tier above the menu bar\nand under the corner mask — the layer itself is deliberately no stacking\ncontext, since a blend inside one would composite against nothing. An\nicon reaches it without an import, the menu-handshake idiom: on a drag's\nfirst step it dispatches `vf-drag-surface-request` (bubbles, non-composed,\n`detail.surface` null), and the nearest desktop on its light-DOM path\nfills the detail with the layer. A window asks the same way and draws\nthere with the same pen: its zoom rects when it opens from a rect\n(`vf-window.show({ from })`), its outline under an `outline-drag`. Not the\ntop layer: stable Safari cannot\nblend an element there against the page (src/cursor.ts), and a second\nkit entry in the top layer would need a re-promotion handshake.\n\nCustom properties:\n- `--vf-desktop-pattern` — a consumer's own tile art in place of the\n pattern (the kit's default is a 1-bit 50% checker, opaque\n black-on-white on a 30-system-px tile). Set, it wins over `pattern` and\n renders as a placed tile grid at that same 30-px geometry\n (src/tile-grid.ts); a token swapped at runtime without touching the\n component wants a `requestUpdate()`.\n- `--vf-desktop` — base color painted *under* the pattern (default\n `#808080`). The pattern's paper is opaque, so this only becomes visible\n when `--vf-desktop-pattern` is overridden with a tile that has\n transparent cells (or with `none`).\n---\n\n\n### **Events:**\n - **vf-activate** - The active document-tier window changed. Detail `{ window: HTMLElement | null }` — the new holder, or `null` when the document tier deactivated (a {@link clearActive} call, or the active window leaving the DOM or hiding with no shown window behind it). Fired once per change of holder, never for a re-assertion of the same one.\n\n### **Slots:**\n - _default_ - Default slot: menu bar, windows, anything.\n\n### **CSS Properties:**\n - **--vf-desktop** - base color under the desktop pattern — occluded by the pattern's opaque paper, so it only shows through a custom `--vf-desktop-pattern` _(default: #808080)_\n- **--vf-desktop-pattern** - a consumer's own desktop tile, in place of `pattern` — the kit's default is the 50% checker drawn as opaque black-on-white rects on a 30-system-px tile. Override the whole tile; consumer art renders as a placed tile grid at that same geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom) _(default: undefined)_\n\n### **CSS Parts:**\n - **desktop** - The patterned screen surface — the whole-system-px raster (inset by `bezel` when one is set).",
255
255
  "attributes": [
256
256
  {
257
257
  "name": "width",
@@ -270,7 +270,7 @@
270
270
  },
271
271
  {
272
272
  "name": "pattern",
273
- "description": "The desktop pattern — System 7's General Controls setting. A library\npattern by name (`gray-50`, the classic dither, by default; `gray-75`,\n`bricks`, … — docs/PATTERNS.md) or sixteen hex digits stating a custom\n8×8 pattern, as on `vf-container`. Painted black on opaque white over\nthe whole screen, 1-bit at every density and zoom. A\n`--vf-desktop-pattern` token override still wins and renders the\nconsumer's tile as a placed grid; an unrecognized value warns once and\nkeeps the dither.",
273
+ "description": "The desktop pattern — System 7's General Controls setting. A library\npattern by name (`gray-50`, the classic dither, by default; `gray-75`,\n`bricks`, … — docs/PATTERNS.md) or sixteen hex digits stating a custom\n8×8 pattern, as on `vf-container`. Painted black on opaque white over\nthe whole screen, 1-bit at every density and zoom. A\n`--vf-desktop-pattern` token override still wins and renders the\nconsumer's tile as a placed grid; an unrecognized value warns once and\nkeeps the dither. Reflects, so a change can be observed on the\nattribute.",
274
274
  "values": []
275
275
  },
276
276
  {
@@ -303,7 +303,7 @@
303
303
  },
304
304
  {
305
305
  "name": "vf-dialog",
306
- "description": "`<vf-dialog>` — the System 7 modal dialog shell.\n\nTwo chromes, one modal lifecycle (native `<dialog>` for top-layer rendering\nand focus trapping, with a fully transparent backdrop — no dimming). Both\nare the same dBoxProc double frame — 1px outer rule, 2px gap, 2px inner\nband, no shadow (vfModalFrame):\n\n- **Default:** the movable modal (movableDBoxProc) — the striped title bar\n set into the top of that frame, with a centered title over a white body.\n Drag the title bar to move it. `closable` adds the standard close box\n (left of the bar) — the HIG's own figures disagree on whether a movable\n modal carries one (Figure 5-1 says yes, Figure 6-1 and the Chapter 6 text\n say no), so the component enables either reading rather than enforcing\n one.\n- **`frame=\"plain\"`:** the modal dialog box — the bare frame, no title bar —\n and immovable, like the original. With no bar, `heading` only names the\n dialog and `closable` is ignored.\n\nOpen it with `show()` (or set the `open` attribute/property); close with\n`close()`. Escape closes it and fires `vf-close` with\n`{ reason: 'escape' }`; the close box and programmatic closing fire\n`{ reason: 'close' }`. With `light-dismiss`, a click outside the frame\ncloses it too, with `{ reason: 'outside' }` — for the About box; off by\ndefault, since the classic modal ignored an outside click.\n\nKeyboard, the classic Dialog Manager's two rules (VfModalDialog):\non open, focus goes to the first text-entry control — a slotted control\nwith `autofocus` first — or, with none, to the default button\n(`vf-button variant=\"default\"`). Return or Enter activates the default\nbutton from anywhere in the dialog, a focused Cancel included; Space\npresses the focused control. A link keeps its own Enter, and in a\nmulti-line editor Return inserts the newline while the keypad's Enter\nactivates the button.\n\nSlotted children placed with `top`/`left` measure from the content region's\ncorner — the frame's inner edge, below the title bar — and flow content\nstarts at the same corner.\n---\n\n\n### **Events:**\n - **vf-close** - Dialog closed. Detail `{ reason: 'escape' | 'close' | 'outside' }` — `'outside'` only under `light-dismiss`.\n\n### **Slots:**\n - _default_ - Default slot: the dialog's content.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 tile, one black pixel at the origin (`vfDots`; override the whole pattern like `--vf-desktop-pattern`) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-scrollbar-thumb** - scrollbar thumb/elevator (white) _(default: undefined)_\n- **--vf-scrollbar-track** - the scroll trough's base color under the dot-dither (white) _(default: undefined)_\n\n### **CSS Parts:**\n - **frame** - The outer frame (the double frame's 1px rule; the bar and the inner band sit inside it).\n- **title-bar** - The striped title bar (default chrome only).\n- **title** - The centered title patch (default chrome only).\n- **close-box** - The close widget (`closable`, default chrome only).\n- **body** - The white content region.\n- **content** - The scrolling region inside the body (the slotted content). Inert while the content fits; over-stuffed, it scrolls under a System 7 rail and becomes a keyboard stop.",
306
+ "description": "`<vf-dialog>` — the System 7 modal dialog shell.\n\nTwo chromes, one modal lifecycle (native `<dialog>` for top-layer rendering\nand focus trapping, with a fully transparent backdrop — no dimming). Both\nare the same dBoxProc double frame — 1px outer rule, 2px gap, 2px inner\nband, no shadow (vfModalFrame):\n\n- **Default:** the movable modal (movableDBoxProc) — the striped title bar\n set into the top of that frame, with a centered title over a white body.\n Drag the title bar to move it. `closable` adds the standard close box\n (left of the bar) — the HIG's own figures disagree on whether a movable\n modal carries one (Figure 5-1 says yes, Figure 6-1 and the Chapter 6 text\n say no), so the component enables either reading rather than enforcing\n one.\n- **`frame=\"plain\"`:** the modal dialog box — the bare frame, no title bar —\n and immovable, like the original. With no bar, `heading` only names the\n dialog and `closable` is ignored.\n\nOpen it with `show()` (or set the `open` attribute/property); close with\n`close()`. Escape closes it and fires `vf-close` with\n`{ reason: 'escape' }`; the close box and programmatic closing fire\n`{ reason: 'close' }`. With `light-dismiss`, a click outside the frame\ncloses it too, with `{ reason: 'outside' }` — for the About box; off by\ndefault, since the classic modal ignored an outside click.\n\nAnswers: a `<form method=\"dialog\">` inside it closes it with the\nsubmitting button's `value` as `returnValue`, and `close(value)` does the\nsame. `vf-close` carries it as `detail.returnValue`, null when the close\ncarried none (Escape, a click outside, the close box).\n\nKeyboard, the classic Dialog Manager's two rules (VfModalDialog):\non open, focus goes to the first text-entry control — a slotted control\nwith `autofocus` first — or, with none, to the default button\n(`vf-button variant=\"default\"`). Return or Enter activates the default\nbutton from anywhere in the dialog, a focused Cancel included; Space\npresses the focused control. A link keeps its own Enter, and in a\nmulti-line editor Return inserts the newline while the keypad's Enter\nactivates the button.\n\nSlotted children placed with `top`/`left` measure from the content region's\ncorner — the frame's inner edge, below the title bar — and flow content\nstarts at the same corner.\n---\n\n\n### **Events:**\n - **vf-show** - Dialog opened, placed and given its initial focus. Detail `{ focus }`, the element focused: the moment to select a field's default text (`select()`) or measure copy that varies.\n- **vf-close** - Dialog closed. Detail `{ reason: 'escape' | 'close' | 'outside', returnValue: string | null }` — `'outside'` only under `light-dismiss` — plus `altKey`, `shiftKey`, `metaKey` and `ctrlKey` when the close box closed it.\n\n### **Slots:**\n - _default_ - Default slot: the dialog's content.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 tile, one black pixel at the origin (`vfDots`; override the whole pattern like `--vf-desktop-pattern`) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-scrollbar-thumb** - scrollbar thumb/elevator (white) _(default: undefined)_\n- **--vf-scrollbar-track** - the scroll trough's base color under the dot-dither (white) _(default: undefined)_\n\n### **CSS Parts:**\n - **frame** - The outer frame (the double frame's 1px rule; the bar and the inner band sit inside it).\n- **title-bar** - The striped title bar (default chrome only).\n- **title** - The centered title patch (default chrome only).\n- **close-box** - The close widget (`closable`, default chrome only).\n- **body** - The white content region.\n- **content** - The scrolling region inside the body (the slotted content). Inert while the content fits; over-stuffed, it scrolls under a System 7 rail and becomes a keyboard stop.",
307
307
  "attributes": [
308
308
  {
309
309
  "name": "heading",
@@ -317,7 +317,7 @@
317
317
  },
318
318
  {
319
319
  "name": "closable",
320
- "description": "Show the close box (left side of the title bar). Off by default — the\nbare movable-modal bar. Ignored with `frame=\"plain\"` (no bar). Clicking\nit closes the dialog and fires `vf-close` with `{ reason: 'close' }`.",
320
+ "description": "Show the close box (left side of the title bar). Off by default — the\nbare movable-modal bar. Ignored with `frame=\"plain\"` (no bar). Clicking\nit closes the dialog and fires `vf-close` with `{ reason: 'close' }` and\nthe modifier keys held on the click, as `vf-window`'s close box does.",
321
321
  "values": []
322
322
  },
323
323
  {
@@ -860,7 +860,7 @@
860
860
  },
861
861
  {
862
862
  "name": "vf-menu",
863
- "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. 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.",
863
+ "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.",
864
864
  "attributes": [
865
865
  {
866
866
  "name": "label",
@@ -1036,7 +1036,7 @@
1036
1036
  },
1037
1037
  {
1038
1038
  "name": "vf-paragraph",
1039
- "description": "`<vf-paragraph>` — a paragraph of copy on the kit's body face and grid.\n\nThe installer's welcome text, a dialog's explanation, an article's prose:\nanything a page would otherwise set with a `<p>` and hope the leading landed\nsomewhere sensible. What it adds over that `<p>`:\n\n- the **Geneva body face** by default (`face=\"display\"` switches to\n the Chicago-style chrome face);\n- a **whole-system-pixel line box at the face's native pitch** —\n `--vf-paragraph-line-height`: 12px for the body face (Geneva 9's own\n strike line) and 16px under `face=\"display\"` (Chicago 12's — ascent 12 +\n descent 3 + leading 1, the pitch a real dialog wrapped its copy on).\n Whole pixels are the point of the component: line boxes are the single\n biggest source of off-grid layout, because a ratio resolves to whatever\n it resolves to (`1.65 × 17px = 28.05px`) and every line of prose nudges\n everything after it further off the device-pixel grid, smearing 1-bit\n borders and bitmap glyph stems (docs/SIZING.md rule 2). A\n whole-pixel line box accumulates whole offsets;\n- its own GridSnapController, so it holds its own origin\n wherever the page puts it;\n- a **declared box** when the layout wants one — `width`/`height` in whole\n system px (VfSized). In flow a paragraph takes its container's\n width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps\n its longest line instead — a fractional glyph-run width, wrapped wherever\n the parent's edge happens to fall — so a DITL-style layout states the\n measure the copy wraps to, whole and on the grid.\n\nThe shadow root renders a real `<p>`, so the copy keeps paragraph semantics\nfor assistive tech, and — unlike the kit's chrome — the text is selectable.\nThere is no margin: per SPEC §2 a component adds nothing outside its own box,\nso paragraph spacing is the page's (a `gap` on the column, or a margin on the\nhost — kept a whole number of pixels, like everything else in the contract).\n---\n\n\n### **Slots:**\n - _default_ - The paragraph copy.\n\n### **CSS Properties:**\n - **--vf-paragraph-line-height** - This paragraph kind's own line box, in system px. Unset (the default) the box follows the face tokens below; set, it overrides both faces for paragraphs alone. Keep any override a whole *even* number — a ratio is what puts a page off the grid in the first place, and half the difference to the 16px em is half-leading, so an odd value lands the baseline on a half pixel. _(default: undefined)_\n- **--vf-line-height** - The body face's native line (default `12px`, Geneva 9's) — the face-level knob: retheming the body face to another strike states this alongside `--vf-font-family` / `--vf-font-size`. _(default: undefined)_\n- **--vf-line-height-display** - The display face's native line (default `16px`, Chicago 12's) — the display retheme's third number. _(default: undefined)_\n\n### **CSS Parts:**\n - **paragraph** - The inner `<p>`.",
1039
+ "description": "`<vf-paragraph>` — a paragraph of copy on the kit's body face and grid.\n\nThe installer's welcome text, a dialog's explanation, an article's prose:\nanything a page would otherwise set with a `<p>` and hope the leading landed\nsomewhere sensible. What it adds over that `<p>`:\n\n- the **Geneva body face** by default (`face=\"display\"` switches to\n the Chicago-style chrome face);\n- a **whole-system-pixel line box at the face's native pitch** —\n `--vf-paragraph-line-height`: 12px for the body face (Geneva 9's own\n strike line) and 16px under `face=\"display\"` (Chicago 12's — ascent 12 +\n descent 3 + leading 1, the pitch a real dialog wrapped its copy on).\n Whole pixels are the point of the component: line boxes are the single\n biggest source of off-grid layout, because a ratio resolves to whatever\n it resolves to (`1.65 × 17px = 28.05px`) and every line of prose nudges\n everything after it further off the device-pixel grid, smearing 1-bit\n borders and bitmap glyph stems (docs/SIZING.md rule 2). A\n whole-pixel line box accumulates whole offsets;\n- its own GridSnapController, so it holds its own origin\n wherever the page puts it;\n- a **declared box** when the layout wants one — `width`/`height` in whole\n system px (VfSized). In flow a paragraph takes its container's\n width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps\n its longest line instead — a fractional glyph-run width, wrapped wherever\n the parent's edge happens to fall — so a DITL-style layout states the\n measure the copy wraps to, whole and on the grid.\n\nThe shadow root renders a real `<p>`, so the copy keeps paragraph semantics\nfor assistive tech, and — unlike the kit's chrome — the text is selectable,\nselected text inverting to the 1-bit highlight (`--vf-highlight` /\n`--vf-highlight-text`) as a field's does.\nThere is no margin: per SPEC §2 a component adds nothing outside its own box,\nso paragraph spacing is the page's (a `gap` on the column, or a margin on the\nhost — kept a whole number of pixels, like everything else in the contract).\n---\n\n\n### **Slots:**\n - _default_ - The paragraph copy.\n\n### **CSS Properties:**\n - **--vf-paragraph-line-height** - This paragraph kind's own line box, in system px. Unset (the default) the box follows the face tokens below; set, it overrides both faces for paragraphs alone. Keep any override a whole *even* number — a ratio is what puts a page off the grid in the first place, and half the difference to the 16px em is half-leading, so an odd value lands the baseline on a half pixel. _(default: undefined)_\n- **--vf-line-height** - The body face's native line (default `12px`, Geneva 9's) — the face-level knob: retheming the body face to another strike states this alongside `--vf-font-family` / `--vf-font-size`. _(default: undefined)_\n- **--vf-line-height-display** - The display face's native line (default `16px`, Chicago 12's) — the display retheme's third number. _(default: undefined)_\n\n### **CSS Parts:**\n - **paragraph** - The inner `<p>`.",
1040
1040
  "attributes": [
1041
1041
  {
1042
1042
  "name": "face",
@@ -1224,7 +1224,7 @@
1224
1224
  },
1225
1225
  {
1226
1226
  "name": "vf-scroll-area",
1227
- "description": "`<vf-scroll-area>` — a container whose scrollbars look like System 7.\n\nWhite box with a 1px black frame, an inner scrolling viewport, and scroll\nrails the kit draws itself as shadow DOM (the shared `vfScrollRail` recipe):\nboxed arrow buttons at each end, a loose 1-bit dot-dither trough, and the\nclassic fixed 16px thumb. The native scrollbar is hidden, never the native\nscrolling — wheel, trackpad momentum, keyboard, touch and assistive-tech\nscrolling stay the platform's, and ScrollRailController keeps the\nrail in sync while driving the classic interactions (thumb drag, trough\npaging, arrow stepping with auto-repeat). Every engine renders the same\nrail — there is no Firefox fallback skin anymore.\n\nEach reserved scroll rail is a permanent placeholder: arrow buttons on an\nempty white channel sit in the gutter even when the content fits, the\ndither and thumb filling in only once that axis overflows (System 7 drew\nan active window's no-overflow bar as arrows on a bare channel; driven\nby ScrollStateController). Which rails are reserved is set by\naxis; when both are reserved the bottom-right corner joins them,\nand corner reserves that cell on a single-axis rail too — the\nrail stops 15px short of the frame, for a grow box to land in.\n\nSize the host (width/height) from the outside; the viewport fills it.\nContent runs to the frame and the rails — the area adds no inset of its\nown; an inset is the content's (a `vf-stack pad`).\n\nThe scrolled plane sizes to its content — never narrower than the\nviewport, as wide as content that cannot wrap — so the rails follow a row\nthat grows sideways the way they follow copy that grows down, and a\n`slotchange` re-measures. A placed child moved through `top`/`left` is\nheard too: every such write is announced as `vf-placement-change`\n(src/position.ts), and the area re-measures on it — a drop, an arrow\nnudge, a page's own `icon.left = …`, with no code on the page. * measure covers a scroll range that changes with no box and no placement\nchanging (a transform).\n\nplacementAt converts a viewport point into the plane's own\nplacement coordinates, for a drop that lands in this area from elsewhere.\n---\n\n\n### **Slots:**\n - _default_ - Scrollable content.\n\n### **CSS Properties:**\n - **--vf-scrollbar-thumb** - scrollbar thumb/elevator (white) _(default: undefined)_\n- **--vf-scrollbar-track** - the scroll trough's base color under the dot-dither (white) _(default: undefined)_\n\n### **CSS Parts:**\n - **viewport** - The inner scrolling container.",
1227
+ "description": "`<vf-scroll-area>` — a container whose scrollbars look like System 7.\n\nWhite box with a 1px black frame, an inner scrolling viewport, and scroll\nrails the kit draws itself as shadow DOM (the shared `vfScrollRail` recipe):\nboxed arrow buttons at each end, a loose 1-bit dot-dither trough, and the\nclassic fixed 16px thumb. The native scrollbar is hidden, never the native\nscrolling — wheel, trackpad momentum, keyboard, touch and assistive-tech\nscrolling stay the platform's, and ScrollRailController keeps the\nrail in sync while driving the classic interactions (thumb drag, trough\npaging, arrow stepping with auto-repeat). Every engine renders the same\nrail — there is no Firefox fallback skin anymore.\n\nEach reserved scroll rail is a permanent placeholder: arrow buttons on an\nempty white channel sit in the gutter even when the content fits, the\ndither and thumb filling in only once that axis overflows (System 7 drew\nan active window's no-overflow bar as arrows on a bare channel; driven\nby ScrollStateController). Which rails are reserved is set by\naxis; when both are reserved the bottom-right corner joins them,\nand corner reserves that cell on a single-axis rail too — the\nrail stops 15px short of the frame, for a grow box to land in.\n\nSize the host (width/height) from the outside; the viewport fills it.\nContent runs to the frame and the rails — the area adds no inset of its\nown; an inset is the content's (a `vf-stack pad`). `pattern` fills the\nscrolled plane with a 1-bit pattern, as on `vf-container`.\n\nThe scrolled plane sizes to its content — never narrower than the\nviewport, as wide as content that cannot wrap — so the rails follow a row\nthat grows sideways the way they follow copy that grows down, and a\n`slotchange` re-measures. A placed child moved through `top`/`left` is\nheard too: every such write is announced as `vf-placement-change`\n(src/position.ts), and the area re-measures on it — a drop, an arrow\nnudge, a page's own `icon.left = …`, with no code on the page. * measure covers a scroll range that changes with no box and no placement\nchanging (a transform).\n\nplacementAt converts a viewport point into the plane's own\nplacement coordinates, for a drop that lands in this area from elsewhere.\n---\n\n\n### **Slots:**\n - _default_ - Scrollable content.\n\n### **CSS Properties:**\n - **--vf-scrollbar-thumb** - scrollbar thumb/elevator (white) _(default: undefined)_\n- **--vf-scrollbar-track** - the scroll trough's base color under the dot-dither (white) _(default: undefined)_\n\n### **CSS Parts:**\n - **viewport** - The inner scrolling container.",
1228
1228
  "attributes": [
1229
1229
  {
1230
1230
  "name": "axis",
@@ -1245,6 +1245,11 @@
1245
1245
  "description": "Accessible name for the scrolling viewport, applied as its `aria-label`\n(an `aria-label` on the host would not reach into the shadow DOM). The\nviewport is keyboard-focusable while its content overflows, so without a\nname it is announced only as an anonymous scrollable group; setting\n`label` also promotes it to a named `role=\"region\"` landmark. While\n`label` is empty the viewport is a plain `group` when scrollable (an\nunnamed region is inert) and role-less when not.",
1246
1246
  "values": []
1247
1247
  },
1248
+ {
1249
+ "name": "pattern",
1250
+ "description": "A 1-bit fill for the scrolled plane, as on `vf-container`: a library\npattern by name (docs/PATTERNS.md) or sixteen hex digits. It fills the\nvisible area, runs on past the content, and scrolls with it, its phase\nanchored at the content's top-left. Unset, the plane stays white; an\nunrecognized value paints nothing and warns once.",
1251
+ "values": []
1252
+ },
1248
1253
  {
1249
1254
  "name": "top",
1250
1255
  "description": "Offset from the top of the positioning parent, in whole system px.\nSetting this (or `left`) absolutely positions the element within its\nparent; the coordinate left unset is 0. Remove both to return the\nelement to normal flow.",
@@ -1270,7 +1275,7 @@
1270
1275
  },
1271
1276
  {
1272
1277
  "name": "vf-select",
1273
- "description": "`<vf-select>` — the classic System 7 popup menu control (\"Macintosh HD ▼\").\n\nChildren are `<vf-option>` elements in the default slot. The closed control\nis a white box with a 1px black border, the small 1px hard shadow, the\nselected option's label on the left and a solid black ▼ triangle on the\nright. The open panel uses the shared `.vf-panel` recipe and is positioned\n`position: fixed` (computed from `getBoundingClientRect()`) so it escapes\nclipping containers; when possible the currently-selected item opens\ndirectly over the control, like the real popup menu.\n\nPointer — two interaction styles are supported and disambiguated by the\ngesture itself, resolved at the first pointer release:\n - System 7 press-drag-release: press the pill (the list appears under the\n pointer), drag onto an item, release over it to pick — one continuous\n press. Releasing over the current item or off the list closes with no\n change.\n - Modern click-to-open: a quick in-place click (no drag, released within\n PRESS_HOLD_MS) leaves the list open; a second, independent click\n then picks an item.\nThe two share one opening trigger (pointerdown) and diverge only on how the\npress ends — whether the pointer travelled to another item, and (for an\nin-place release) whether it was a quick tap or a held press. The pull-down\nmenus drive themselves the same way, on the same threshold; that half lives\nin src/menu-press.ts, because one menu press may travel across a whole bar.\n\nA list too tall for the screen is **clipped**, never scrolled: the edge slot\nwith items beyond it shows a solid arrow instead of a row, which rolls the\nlist one row at a time while the pointer rests on it. The clamp is quantized\nto the pill lattice, so a clipped popup still opens with its selected row\nover the closed pill — and the panel keeps every slot the list asked for, so\na box that had to slide to fit the screen opens with *blank rows* at whichever\nend the list no longer reaches: the exact travel it will roll through, in\neither direction. See src/popup-overflow.ts.\n\n`size=\"small\"` sets the pill and its rows in the body face: a 12px pill\n(`--vf-popup-height-small`) over 10px rows, the baseline 8 rows down so the\nface's 2-row descenders end on the last content row. Its ▼ and scroll\narrows are the 9×5 `CARET_DOWN_SMALL` / `CARET_UP_SMALL`; the ✓ and the\nhorizontal insets are the regular pill's. Marks above a capital (É, Å, Ñ)\nreach past the 8 rows above the baseline and are clipped.\n\n`no-shadow` draws the closed pill without its 1px hard shadow, and the focus\nrule moves up the row the shadow took. The open list keeps its shadow.\n\nKeyboard: Space/Enter/ArrowDown open; while open ArrowUp/ArrowDown move the\nhighlight, Home/End jump, Enter/Space select, Escape cancels. Selecting an\nitem plays the classic inversion blink (~250 ms) before closing. Keyboard\nfocus is marked with the kit's 1px dashed rule under the closed pill\n(`vfFocusUnderline`) rather than a ring around it — keyboard only, which\nhere means the page's input modality rather than `:focus-visible` (the pill\ndrives its own focus, and Blink reads that as visible either way).\n\nForm-associated: submits `value` under `name`.\n---\n\n\n### **Events:**\n - **vf-change** - After a selection commits. `detail: { value: string }`.\n- **input** - Native event, dispatched from the host per committed pick (with `change`, the pair a native `<select>` fires). A programmatic `value` set fires nothing.\n- **change** - Native event, dispatched from the host per committed pick so form delegation and framework bindings hear it.\n\n### **Slots:**\n - _default_ - `<vf-option>` elements.\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-popup-height-small** - `vf-select[size=\"small\"]` pill (border box; its rows are this less the two borders) _(default: 12px)_\n- **--vf-popup-inset-top** - room a clipped popup panel keeps clear at the TOP screen edge — set it once on `:root` (or the `vf-desktop`) to clear a `vf-menu-bar`: `24px` is the 20px bar plus the default 4 _(default: 4px)_\n- **--vf-popup-inset-bottom** - room a clipped popup panel keeps clear at the BOTTOM screen edge _(default: 4px)_\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 - **control** - The closed popup control box.\n- **label** - The selected-option label inside the control.\n- **arrow** - The black ▼ triangle.\n- **panel** - The popup panel (listbox).\n- **scroll-arrow** - Either of the two scroll arrows a clipped panel shows in its edge row slots.",
1278
+ "description": "`<vf-select>` — the classic System 7 popup menu control (\"Macintosh HD ▼\").\n\nChildren are `<vf-option>` elements in the default slot. The closed control\nis a white box with a 1px black border, the small 1px hard shadow, the\nselected option's label on the left and a solid black ▼ triangle on the\nright. The open panel uses the shared `.vf-panel` recipe and is a manual\npopover in the top layer, positioned `position: fixed` (computed from\n`getBoundingClientRect()`), so it escapes clipping containers and paints\nover every window, palettes included; when possible the currently-selected\nitem opens directly over the control, like the real popup menu.\n\nPointer — two interaction styles are supported and disambiguated by the\ngesture itself, resolved at the first pointer release:\n - System 7 press-drag-release: press the pill (the list appears under the\n pointer), drag onto an item, release over it to pick — one continuous\n press. Releasing over the current item or off the list closes with no\n change.\n - Modern click-to-open: a quick in-place click (no drag, released within\n PRESS_HOLD_MS) leaves the list open; a second, independent click\n then picks an item.\nThe two share one opening trigger (pointerdown) and diverge only on how the\npress ends — whether the pointer travelled to another item, and (for an\nin-place release) whether it was a quick tap or a held press. The pull-down\nmenus drive themselves the same way, on the same threshold; that half lives\nin src/menu-press.ts, because one menu press may travel across a whole bar.\n\nA list too tall for the screen is **clipped**, never scrolled: the edge slot\nwith items beyond it shows a solid arrow instead of a row, which rolls the\nlist one row at a time while the pointer rests on it. The clamp is quantized\nto the pill lattice, so a clipped popup still opens with its selected row\nover the closed pill — and the panel keeps every slot the list asked for, so\na box that had to slide to fit the screen opens with *blank rows* at whichever\nend the list no longer reaches: the exact travel it will roll through, in\neither direction. See src/popup-overflow.ts.\n\n`size=\"small\"` sets the pill and its rows in the body face: a 12px pill\n(`--vf-popup-height-small`) over 10px rows, the baseline 8 rows down so the\nface's 2-row descenders end on the last content row. Its ▼ and scroll\narrows are the 9×5 `CARET_DOWN_SMALL` / `CARET_UP_SMALL`; the ✓ and the\nhorizontal insets are the regular pill's. Marks above a capital (É, Å, Ñ)\nreach past the 8 rows above the baseline and are clipped.\n\n`no-shadow` draws the closed pill without its 1px hard shadow, and the focus\nrule moves up the row the shadow took. The open list keeps its shadow.\n\nKeyboard: Space/Enter/ArrowDown open; while open ArrowUp/ArrowDown move the\nhighlight, Home/End jump, Enter/Space select, Escape cancels. Selecting an\nitem plays the classic inversion blink (~250 ms) before closing. Keyboard\nfocus is marked with the kit's 1px dashed rule under the closed pill\n(`vfFocusUnderline`) rather than a ring around it — keyboard only, which\nhere means the page's input modality rather than `:focus-visible` (the pill\ndrives its own focus, and Blink reads that as visible either way).\n\nForm-associated: submits `value` under `name`.\n---\n\n\n### **Events:**\n - **vf-change** - After a selection commits. `detail: { value: string }`.\n- **input** - Native event, dispatched from the host per committed pick (with `change`, the pair a native `<select>` fires). A programmatic `value` set fires nothing.\n- **change** - Native event, dispatched from the host per committed pick so form delegation and framework bindings hear it.\n\n### **Slots:**\n - _default_ - `<vf-option>` elements.\n\n### **CSS Properties:**\n - **--vf-popup-height** - `vf-select` pill (border box; its 1px hard shadow makes the sheet's 157×19 ink box) _(default: 18px)_\n- **--vf-popup-height-small** - `vf-select[size=\"small\"]` pill (border box; its rows are this less the two borders) _(default: 12px)_\n- **--vf-popup-inset-top** - room a clipped popup panel keeps clear at the TOP screen edge — set it once on `:root` (or the `vf-desktop`) to clear a `vf-menu-bar`: `24px` is the 20px bar plus the default 4 _(default: 4px)_\n- **--vf-popup-inset-bottom** - room a clipped popup panel keeps clear at the BOTTOM screen edge _(default: 4px)_\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 - **control** - The closed popup control box.\n- **label** - The selected-option label inside the control.\n- **arrow** - The black ▼ triangle.\n- **panel** - The popup panel (listbox).\n- **scroll-arrow** - Either of the two scroll arrows a clipped panel shows in its edge row slots.",
1274
1279
  "attributes": [
1275
1280
  {
1276
1281
  "name": "value",
@@ -1632,7 +1637,7 @@
1632
1637
  },
1633
1638
  {
1634
1639
  "name": "vf-text-field",
1635
- "description": "`<vf-text-field>` — a System 7 single-line text entry field.\n\nA form-associated wrapper around a native `<input>`: white well, 1px solid\nblack border, no corner radius. Keyboard focus draws the kit's dashed rule\none blank row under the well; a click leaves it unmarked (no dotted outline\neither, per SPEC §1/§5). The shared field skin lives in `vfField`; the\nvalue/form scaffolding — and the focus rule's modality gate — in\nVfTextControlBase.\n---\n\n\n### **Events:**\n - **vf-input** - On every keystroke. `detail: { value: string }`.\n- **vf-change** - On commit (native `change`). `detail: { value: string }`.\n- **input** - The native keystroke event: the inner input's own, composed, so it crosses the shadow boundary and retargets to the host by itself.\n- **change** - The native commit event, re-dispatched from the host (the inner one is `composed: false` and never leaves the shadow root), so form delegation and framework bindings hear it. The input-behavior attributes — `autocomplete`, `inputmode`, `enterkeyhint`, `maxlength`, `pattern`, `spellcheck`, `autocapitalize` — are forwarded from the host onto the inner input, where the platform actually honors them.\n\n### **CSS Properties:**\n - **--vf-control-height** - text fields — `vf-text-field`, `vf-text-area`, the `vf-number-field` well _(default: 22px)_\n- **--vf-field-width** - default width of `vf-text-field` / `vf-text-area` _(default: 180px)_\n- **--vf-field-placeholder** - placeholder text in the editable fields — kept off `--vf-disabled`: a placeholder sits in an *enabled* well and holds AA contrast, where the disabled gray is exempt _(default: #767676)_\n\n### **CSS Parts:**\n - **input** - The inner native `<input>` element.",
1640
+ "description": "`<vf-text-field>` — a System 7 single-line text entry field.\n\nA form-associated wrapper around a native `<input>`: white well, 1px solid\nblack border, no corner radius. Keyboard focus draws the kit's dashed rule\none blank row under the well; a click leaves it unmarked (no dotted outline\neither, per SPEC §1/§5). The shared field skin lives in `vfField`; the\nvalue/form scaffolding — and the focus rule's modality gate — in\nVfTextControlBase.\n---\n\n\n### **Events:**\n - **vf-input** - On every keystroke. `detail: { value: string }`.\n- **vf-change** - On commit: the native `change`, leaving the field, or Return in a form, before its submission. `detail: { value: string }`.\n- **input** - The native keystroke event: the inner input's own, composed, so it crosses the shadow boundary and retargets to the host by itself.\n- **change** - The native commit event, re-dispatched from the host (the inner one is `composed: false` and never leaves the shadow root), so form delegation and framework bindings hear it. The input-behavior attributes — `autocomplete`, `inputmode`, `enterkeyhint`, `maxlength`, `pattern`, `spellcheck`, `autocapitalize` — are forwarded from the host onto the inner input, where the platform actually honors them.\n\n### **CSS Properties:**\n - **--vf-control-height** - text fields — `vf-text-field`, `vf-text-area`, the `vf-number-field` well _(default: 22px)_\n- **--vf-field-width** - default width of `vf-text-field` / `vf-text-area` _(default: 180px)_\n- **--vf-field-placeholder** - placeholder text in the editable fields — kept off `--vf-disabled`: a placeholder sits in an *enabled* well and holds AA contrast, where the disabled gray is exempt _(default: #767676)_\n\n### **CSS Parts:**\n - **input** - The inner native `<input>` element.",
1636
1641
  "attributes": [
1637
1642
  {
1638
1643
  "name": "type",
@@ -1704,7 +1709,7 @@
1704
1709
  },
1705
1710
  {
1706
1711
  "name": "vf-window",
1707
- "description": "`<vf-window>` — the System 7 desktop-window shell.\n\nStriped title bar with optional close box (left) and zoom box (right), a\nsolid-white frame with a hard offset shadow, an optional grow box for\nresizing, optional edge scroll rails (`scrollbars`), and the slim windoid\nchrome (`variant=\"utility\"`). The HIG's window archetypes are parameter\nrecipes over this shell rather than fixed anatomies — the component enables\nHIG compliance, it doesn't enforce it (see docs/LAYOUT.md \"Window archetypes\"):\nthe full document window is `closable zoomable movable resizable\nscrollbars=\"both\"`, a modeless dialog box is `closable movable`, a utility\nwindow is `variant=\"utility\" movable`. Place inside `<vf-desktop>` to get\nclick-to-front stacking and automatic `active` management (utility windows\nfloat above the document tier).\n\nEvery recipe also declares `width` AND `height` (VfSized), in whole\nsystem px — the art's own unit, so the window keeps its proportions to the\nchrome inside it at every display density (a CSS-px size stays put while\nthe components in it triple). A window is a fixed box in both axes, the way\nthe WIND resource carried it: left to layout it takes whatever its\ncontainer or content hands it, which is how a title bar ends up wider than\nthe screen or a dialog reflows as it moves — and a window that grows with\nits body is one the user can neither predict nor (via the grow box) own.\nContent taller than the declared box is clipped at the frame the way the\nclassic content region was; `scrollbars` lets the user reach the rest.\nUnset, the window still renders — normal block layout, as before — and\nsays so once in the console.\n\n**Opening from a rect.** show clears `hidden`, and given `from` — a\nbox in viewport CSS px, an icon's `cellRect()` or a button's rect — opens\nthe window from it: dotted rectangles grow from that box toward the\nwindow's frame, four on screen at a time, bunched at the box and opening\nout toward the window, drawn with the icon drag outline's XOR pen on the\ndesktop's drag surface; the window draws once they have drained, eighteen\nsteps later. Until then it matches `:state(opening)` and paints nothing —\nstill laid out, focusable and a hit target, so a page can place it, raise\nit and focus into it at once, and a press finishes the rects and lands on\nthe window. Under `prefers-reduced-motion`, with no `vf-desktop` on its\npath, or without `from`, the window shows at once.\n\n**Closing to a rect.** hide sets `hidden`, and given `to` closes\nthe window to it: the same rectangles run the other way, from where the\nframe was in toward the box, the window gone from the first step. A press\nfinishes them, and removing the window leaves them running. Under\n`prefers-reduced-motion`, with no `vf-desktop` on its path, or without\n`to`, the window hides at once. The window knows nothing about icons:\n`from` and `to` are any rect.\n\n**Dragging as an outline.** A `movable` window moves live under the\npointer. With `outline-drag` a press on the title bar draws a dotted\noutline around the window on the desktop's drag surface instead, with the\nsame pen; the drag moves the outline, held where the window can land, and\nthe window moves there on the release. Escape cancels.\n---\n\n\n### **Events:**\n - **vf-close** - Close box clicked. Detail `{ reason: 'close' }` (shape- compatible with vf-dialog's `vf-close`). The window does NOT remove itself; the consumer decides what closing means.\n- **vf-zoom** - Zoom box clicked. Detail `{}`.\n- **vf-resize** - The grow box resized the window. Detail `{ width, height, commit }`, sizes in whole system px: one event per size the drag writes (`commit: false`), fired after the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles — only when it changed the size. Fired by the gesture alone: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.\n\n### **Slots:**\n - _default_ - Default slot: window body content.\n- **header** - Optional header content — a band between the title bar and the body, the full width of the window (the Finder window's header line): a white interior over a 1px rule, no inset of its own, a positioning anchor for placed children. As tall as its content unless `header-height` states it. Under `scrollbars` the vertical rail begins below it, so the header spans the rail's column. Takes no space until populated.\n- **status** - Optional status-bar content — the classic bottom readout strip (\"40px x 40px\"): a 1px rule over a 15px white band under the body, body-face text on its native line. Takes no space until populated; a `resizable` window's grow box sits flush in its right end.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 motif, one black pixel at its origin, on a 30-system-px tile (`vfDots`; override the whole tile like `--vf-desktop-pattern` — consumer art renders as a placed tile grid at that same geometry) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-titlebar-height-utility** - the slim `vf-window[variant=\"utility\"]` (windoid) bar — 11px interior + 1px bottom rule, traced from `Windows/utility-window.png` _(default: 12px)_\n- **--vf-status-bar-height** - the status strip: 1px rule + 14px interior — the grow box's own height, so the two compose flush _(default: 15px)_\n- **--vf-line-height** - the body face's native line, which the status strip's text rides (whole-pixel centered in the 14px interior) _(default: 12px)_\n\n### **CSS Parts:**\n - **frame** - The outer chrome frame.\n- **title-bar** - The striped (or dithered) title bar.\n- **title** - The centered title patch (hidden on the utility bar).\n- **close-box** - The close widget (left).\n- **zoom-box** - The zoom widget (right).\n- **body** - The content area.\n- **header** - The header strip between the title bar and the body (when the `header` slot is populated).\n- **status-bar** - The bottom status strip (when the `status` slot is populated).\n- **grow-box** - The resize widget (bottom-right, when `resizable`).\n- **viewport** - The built-in scroll area's viewport (when `scrollbars`; re-exported from vf-scroll-area).",
1712
+ "description": "`<vf-window>` — the System 7 desktop-window shell.\n\nStriped title bar with optional close box (left) and zoom box (right), a\nsolid-white frame with a hard offset shadow, an optional grow box for\nresizing, optional edge scroll rails (`scrollbars`), and the slim windoid\nchrome (`variant=\"utility\"`). The HIG's window archetypes are parameter\nrecipes over this shell rather than fixed anatomies — the component enables\nHIG compliance, it doesn't enforce it (see docs/LAYOUT.md \"Window archetypes\"):\nthe full document window is `closable zoomable movable resizable\nscrollbars=\"both\"`, a modeless dialog box is `closable movable`, a utility\nwindow is `variant=\"utility\" closable movable`. Place inside `<vf-desktop>` to get\nclick-to-front stacking and automatic `active` management (utility windows\nfloat above the document tier).\n\nEvery recipe also declares `width` AND `height` (VfSized), in whole\nsystem px — the art's own unit, so the window keeps its proportions to the\nchrome inside it at every display density (a CSS-px size stays put while\nthe components in it triple). A window is a fixed box in both axes, the way\nthe WIND resource carried it: left to layout it takes whatever its\ncontainer or content hands it, which is how a title bar ends up wider than\nthe screen or a dialog reflows as it moves — and a window that grows with\nits body is one the user can neither predict nor (via the grow box) own.\nContent taller than the declared box is clipped at the frame the way the\nclassic content region was; `scrollbars` lets the user reach the rest.\nUnset, the window still renders — normal block layout, as before — and\nsays so once in the console.\n\n**Opening from a rect.** show clears `hidden`, and given `from` — a\nbox in viewport CSS px, an icon's `cellRect()` or a button's rect — opens\nthe window from it: dotted rectangles grow from that box toward the\nwindow's frame, four on screen at a time, bunched at the box and opening\nout toward the window, drawn with the icon drag outline's XOR pen on the\ndesktop's drag surface; the window draws once they have drained, eighteen\nsteps later. Until then it matches `:state(opening)` and paints nothing —\nstill laid out, focusable and a hit target, so a page can place it, raise\nit and focus into it at once, and a press finishes the rects and lands on\nthe window. Under `prefers-reduced-motion`, with no `vf-desktop` on its\npath, or without `from`, the window shows at once.\n\n**Closing to a rect.** hide sets `hidden`, and given `to` closes\nthe window to it: the same rectangles run the other way, from where the\nframe was in toward the box, the window gone from the first step. A press\nfinishes them, and removing the window leaves them running. Under\n`prefers-reduced-motion`, with no `vf-desktop` on its path, or without\n`to`, the window hides at once. The window knows nothing about icons:\n`from` and `to` are any rect.\n\n**Dragging as an outline.** A `movable` window moves live under the\npointer. With `outline-drag` a press on the title bar draws a dotted\noutline around the window on the desktop's drag surface instead, with the\nsame pen; the drag moves the outline, held where the window can land, and\nthe window moves there on the release. Escape cancels.\n---\n\n\n### **Events:**\n - **vf-close** - Close box clicked. Detail `{ reason: 'close', altKey, shiftKey, metaKey, ctrlKey }`, the modifier keys held on the click (shape-compatible with vf-dialog's `vf-close`), so an Option-click can mean more than a click. The window does NOT remove itself; the consumer decides what closing means.\n- **vf-zoom** - Zoom box clicked. Detail `{}`.\n- **vf-resize** - The grow box resized the window. Detail `{ width, height, commit }`, sizes in whole system px: one event per size the drag writes (`commit: false`), fired after the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles — only when it changed the size. Fired by the gesture alone: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.\n\n### **Slots:**\n - _default_ - Default slot: window body content.\n- **header** - Optional header content — a band between the title bar and the body, the full width of the window (the Finder window's header line): a white interior over a 1px rule, no inset of its own, a positioning anchor for placed children. As tall as its content unless `header-height` states it. Under `scrollbars` the vertical rail begins below it, so the header spans the rail's column. Takes no space until populated.\n- **status** - Optional status-bar content — the classic bottom readout strip (\"40px x 40px\"): a 1px rule over a 15px white band under the body, body-face text on its native line. Takes no space until populated; a `resizable` window's grow box sits flush in its right end.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 motif, one black pixel at its origin, on a 30-system-px tile (`vfDots`; override the whole tile like `--vf-desktop-pattern` — consumer art renders as a placed tile grid at that same geometry) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-titlebar-height-utility** - the slim `vf-window[variant=\"utility\"]` (windoid) bar — 11px interior + 1px bottom rule, traced from `Windows/utility-window.png` _(default: 12px)_\n- **--vf-status-bar-height** - the status strip: 1px rule + 14px interior — the grow box's own height, so the two compose flush _(default: 15px)_\n- **--vf-line-height** - the body face's native line, which the status strip's text rides (whole-pixel centered in the 14px interior) _(default: 12px)_\n\n### **CSS Parts:**\n - **frame** - The outer chrome frame.\n- **title-bar** - The striped (or dithered) title bar.\n- **title** - The centered title patch (hidden on the utility bar).\n- **close-box** - The close widget (left).\n- **zoom-box** - The zoom widget (right).\n- **body** - The content area.\n- **header** - The header strip between the title bar and the body (when the `header` slot is populated).\n- **status-bar** - The bottom status strip (when the `status` slot is populated).\n- **grow-box** - The resize widget (bottom-right, when `resizable`).\n- **viewport** - The built-in scroll area's viewport (when `scrollbars`; re-exported from vf-scroll-area).",
1708
1713
  "attributes": [
1709
1714
  {
1710
1715
  "name": "variant",
@@ -1723,7 +1728,7 @@
1723
1728
  },
1724
1729
  {
1725
1730
  "name": "closable",
1726
- "description": "Show the close box (left side of the title bar).",
1731
+ "description": "Show the close box (left side of the title bar). Off by default, like\nevery other boolean attribute, so markup can leave it out; the document\nwindow and the modeless dialog box recipes say `closable`.",
1727
1732
  "values": []
1728
1733
  },
1729
1734
  {
@@ -1780,6 +1785,11 @@
1780
1785
  "description": "The header's height in whole system px, rule included — the way every\nkit bar counts its rule (an 18px title bar is 17 + 1). Unset, the header\nis as tall as what is slotted into it, plus the rule. Only matters while\nthe `header` slot is populated.",
1781
1786
  "values": [{ "name": "" }]
1782
1787
  },
1788
+ {
1789
+ "name": "pattern",
1790
+ "description": "A 1-bit fill for the body, as on `vf-container`: a library pattern by\nname (docs/PATTERNS.md) or sixteen hex digits, under the content and\nanchored at the content region's corner. Under `scrollbars` it fills the\nscrolled plane and scrolls with it (`vf-scroll-area`'s `pattern`). Unset,\nthe body stays white; an unrecognized value paints nothing and warns\nonce.",
1791
+ "values": []
1792
+ },
1783
1793
  {
1784
1794
  "name": "width",
1785
1795
  "description": "Width in whole system px. What the number means is the component's own\naffair — the measure a paragraph wraps to, a caption column, a window's\nbox — but the mechanics are shared: the length lands on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)`, so the box scales with the\ndisplay and sits on the device-pixel grid by construction. Remove it\nand the width returns to layout.",