vintage-frames 0.14.0 → 0.14.1

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 (46) hide show
  1. package/README.md +16 -0
  2. package/custom-elements.json +177 -13
  3. package/dist/components/vf-container.d.ts +3 -1
  4. package/dist/components/vf-container.js +34 -16
  5. package/dist/components/vf-desktop.d.ts +114 -12
  6. package/dist/components/vf-desktop.js +119 -58
  7. package/dist/components/vf-icon-field.d.ts +1 -1
  8. package/dist/components/vf-icon.js +68 -61
  9. package/dist/components/vf-menu-bar.d.ts +5 -0
  10. package/dist/components/vf-menu-bar.js +67 -43
  11. package/dist/components/vf-window.d.ts +13 -0
  12. package/dist/components/vf-window.js +10 -1
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +44 -43
  15. package/dist/pattern-fill.d.ts +11 -8
  16. package/dist/pattern-fill.js +2 -4
  17. package/dist/position.d.ts +25 -7
  18. package/dist/position.js +6 -3
  19. package/dist/shell/alert.d.ts +32 -0
  20. package/dist/shell/alert.js +44 -0
  21. package/dist/shell/catalog.d.ts +168 -0
  22. package/dist/shell/catalog.js +278 -0
  23. package/dist/shell/clock.d.ts +16 -0
  24. package/dist/shell/clock.js +36 -0
  25. package/dist/shell/filing.d.ts +39 -0
  26. package/dist/shell/filing.js +68 -0
  27. package/dist/shell/finder.d.ts +116 -0
  28. package/dist/shell/finder.js +564 -0
  29. package/dist/shell/geometry.d.ts +196 -0
  30. package/dist/shell/geometry.js +241 -0
  31. package/dist/shell/index.d.ts +29 -0
  32. package/dist/shell/index.js +11 -0
  33. package/dist/shell/shell.d.ts +150 -0
  34. package/dist/shell/shell.js +213 -0
  35. package/dist/shell/state.d.ts +88 -0
  36. package/dist/shell/state.js +103 -0
  37. package/dist/shell/storage.d.ts +14 -0
  38. package/dist/shell/storage.js +41 -0
  39. package/dist/shell/windows.d.ts +215 -0
  40. package/dist/shell/windows.js +343 -0
  41. package/dist/window-chrome.d.ts +43 -0
  42. package/dist/window-chrome.js +13 -0
  43. package/docs/SPEC.md +12 -11
  44. package/editor/vscode.html-custom-data.json +9 -4
  45. package/editor/web-types.json +45 -9
  46. package/package.json +10 -2
package/README.md CHANGED
@@ -23,6 +23,22 @@ import 'vintage-frames/vf-checkbox.js'
23
23
 
24
24
  There is no need to import a stylesheet. The components carry their styles with them.
25
25
 
26
+ ## The shell
27
+
28
+ `vintage-frames/shell` is an experimental second entry point that runs applications over one desktop: a window manager, a menu bar that shows the front application's menus, and a Finder with folders, filing, Clean Up and the Trash.
29
+
30
+ ```ts
31
+ import 'vintage-frames'
32
+ import { createShell, finder, memoryStorage } from 'vintage-frames/shell'
33
+
34
+ createShell(document.querySelector('vf-desktop'), {
35
+ apps: [finder({ storage: memoryStorage(), seed: 'markup', art })],
36
+ fit: 'viewport',
37
+ })
38
+ ```
39
+
40
+ See [docs/SHELL.md](https://github.com/aportilla/vintage-frames/blob/main/docs/SHELL.md). Its API may change in any minor release while it is experimental.
41
+
26
42
 
27
43
  ## Sizing
28
44
 
@@ -2246,7 +2246,7 @@
2246
2246
  "declarations": [
2247
2247
  {
2248
2248
  "kind": "class",
2249
- "description": "`<vf-container>` — a box that is nothing but its declared size.\n\nThe kit's positioned-placement story (src/position.ts) ends with one line of\nCSS it can't write for you: children placed with `top`/`left` need a\npositioned ancestor, and while every kit container is one — a window body, a\nstack, a fieldset — a region of *your own* needs `position: relative` in a\nstylesheet. This component is that region as an element: declare `width` and\n`height` in whole system px, slot anything into it, place children against\nits origin. A DITL's enclosing rectangle, with nothing drawn in it.\n\n```html\n<vf-container width=\"200\" height=\"120\">\n <vf-icon label=\"System\" width=\"64\" top=\"8\" left=\"12\">…</vf-icon>\n <vf-icon label=\"Finder\" width=\"64\" top=\"8\" left=\"104\">…</vf-icon>\n</vf-container>\n```\n\nThe rectangle is the whole API — `width`/`height` here, plus the `top`/`left`\npair nearly every component takes (VfPositioned), so a container is\nitself placeable: inside a window, a desktop, or another container, at whole\nsystem px that keep its box on the device-pixel grid by construction.\n\n**It is not a `vf-stack`.** The stack is a flexbox with opinions — it\ndistributes children along an axis, compiles `fill-width`/`fill-height` into\nflex, defaults a cross-axis alignment. This box has no layout opinion at\nall: in-flow children get normal flow, placed children get a coordinate\nsystem, and that is the whole API. Reach for it when the stack's opinions\nare the thing in the way — a field of placed icons, a fixed stage for\nabsolutely-positioned art, a consumer's own composition that brings its\nlayout with it.\n\n**The declared size is the layout.** `width`/`height` land on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)` (VfSized), so the box scales\nwith the display and sits on the device-pixel grid by construction. Content\nthat outgrows the box overflows it rather than growing it — the number is\nthe layout, and content that doesn't fit is a number to raise. Leave a\ndimension off and that axis shrink-wraps: `fit-content`, not the parent's\nwidth, because a layout box that silently claimed a size nobody declared\nwould be inventing one (the `vf-stack` rule, held here too).\n\n**It paints nothing and means nothing** — unless `pattern` or `rule` say\nwhat to paint. No role, keyboard behavior or selection; what it holds\ndecides what it is.\n\n**`rule` draws the 1px rule on the edges it names** — `rule=\"bottom\"` is\nthe menu bar's anatomy (the box's rows over one row of ink), `rule=\"top\"`\na status strip's, all four a framed box. The rule is drawn inside the\ndeclared size (vfRule), so a 24px `rule=\"bottom\"` strip is 23 rows\nof box over the line; content, `fill-width` children and placed children\nbegin inside it, as a rectangle's interior begins inside FrameRect's line.\n\n**`pattern` fills the box with a 1-bit pattern**: one of the 38 standard\nMacPaint patterns by name (`pattern=\"bricks\"`, `pattern=\"gray-50\"` —\ndocs/PATTERNS.md has the table), or sixteen hex digits stating a custom\n8×8 pattern row by row, the way a PAT resource did. It is painted as the\nbox's own background — black ink on a `--vf-white` ground, anchored at\nthe box's top-left corner, under the content — by the same repeating tile\nas the desktop dither, so it is 1-bit at every density and zoom and a box\nof any size, declared or not, costs nothing more (src/pattern-fill.ts).\nUnder forced colors the pattern goes flat Canvas.\n\n**It holds its box on the device-pixel grid** — with a `GridSnapController`.\nA container's box is itself the consumer's coordinate system, including for\nnon-`vf` content that cannot correct itself, so the box is the thing to\nhold on the grid. The correction lands on the shadow box below, and the\ncoordinate system sits inside it, so the correction moves the whole\ncoordinate system — everything placed against it rides along instead of\nbeing re-corrected child by child. (`vf-stack` shipped\nwithout a controller on the theory that slotted `vf-*` children correct\ntheir own origins; this component is where that theory's gap — consumer\ncontent — became visible, and the stack has since adopted the same\narrangement.)\n\nLike the stack it is **typographically transparent**: `vfBase`'s chrome\ndress is returned to `inherit` on the host, so wrapping content in a sized\nbox changes nothing about how that content reads.\n\n`fill-width` / `fill-height` work here the way they do everywhere: read\nabout the host (be as big as *its* parent allows), and compiled for slotted\nchildren — `width: 100%` in normal flow, so a child filling the cross of a\ndeclared box needs no stylesheet. A height fill needs a declared `height`\nto resolve against; with none it is inert, not an error.",
2249
+ "description": "`<vf-container>` — a box that is nothing but its declared size.\n\nThe kit's positioned-placement story (src/position.ts) ends with one line of\nCSS it can't write for you: children placed with `top`/`left` need a\npositioned ancestor, and while every kit container is one — a window body, a\nstack, a fieldset — a region of *your own* needs `position: relative` in a\nstylesheet. This component is that region as an element: declare `width` and\n`height` in whole system px, slot anything into it, place children against\nits origin. A DITL's enclosing rectangle, with nothing drawn in it.\n\n```html\n<vf-container width=\"200\" height=\"120\">\n <vf-icon label=\"System\" width=\"64\" top=\"8\" left=\"12\">…</vf-icon>\n <vf-icon label=\"Finder\" width=\"64\" top=\"8\" left=\"104\">…</vf-icon>\n</vf-container>\n```\n\nThe rectangle is the whole API — `width`/`height` here, plus the `top`/`left`\npair nearly every component takes (VfPositioned), so a container is\nitself placeable: inside a window, a desktop, or another container, at whole\nsystem px that keep its box on the device-pixel grid by construction.\n\n**It is not a `vf-stack`.** The stack is a flexbox with opinions — it\ndistributes children along an axis, compiles `fill-width`/`fill-height` into\nflex, defaults a cross-axis alignment. This box has no layout opinion at\nall: in-flow children get normal flow, placed children get a coordinate\nsystem, and that is the whole API. Reach for it when the stack's opinions\nare the thing in the way — a field of placed icons, a fixed stage for\nabsolutely-positioned art, a consumer's own composition that brings its\nlayout with it.\n\n**The declared size is the layout.** `width`/`height` land on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)` (VfSized), so the box scales\nwith the display and sits on the device-pixel grid by construction. Content\nthat outgrows the box overflows it rather than growing it — the number is\nthe layout, and content that doesn't fit is a number to raise. Leave a\ndimension off and that axis shrink-wraps: `fit-content`, not the parent's\nwidth, because a layout box that silently claimed a size nobody declared\nwould be inventing one (the `vf-stack` rule, held here too).\n\n**It paints nothing and means nothing** — unless `pattern` or `rule` say\nwhat to paint. No role, keyboard behavior or selection; what it holds\ndecides what it is. A page that gives it a role and a `tabindex` — a\npattern chooser's cells as radios — gets the kit's keyboard focus mark\nrather than the browser's ring: the dashed rule below the box.\n\n**`rule` draws the 1px rule on the edges it names** — `rule=\"bottom\"` is\nthe menu bar's anatomy (the box's rows over one row of ink), `rule=\"top\"`\na status strip's, all four a framed box. The rule is drawn inside the\ndeclared size (vfRule), so a 24px `rule=\"bottom\"` strip is 23 rows\nof box over the line; content, `fill-width` children and placed children\nbegin inside it, as a rectangle's interior begins inside FrameRect's line.\n\n**`pattern` fills the box with a 1-bit pattern**: one of the 38 standard\nMacPaint patterns by name (`pattern=\"bricks\"`, `pattern=\"gray-50\"` —\ndocs/PATTERNS.md has the table), or sixteen hex digits stating a custom\n8×8 pattern row by row, the way a PAT resource did. It is painted as the\nbox's own background — black ink on a `--vf-white` ground, anchored at\nthe box's top-left corner, under the content — by the same repeating tile\nas the desktop dither, so it is 1-bit at every density and zoom and a box\nof any size, declared or not, costs nothing more (src/pattern-fill.ts).\nUnder forced colors the pattern goes flat Canvas.\n\n**It holds its box on the device-pixel grid** — with a `GridSnapController`.\nA container's box is itself the consumer's coordinate system, including for\nnon-`vf` content that cannot correct itself, so the box is the thing to\nhold on the grid. The correction lands on the shadow box below, and the\ncoordinate system sits inside it, so the correction moves the whole\ncoordinate system — everything placed against it rides along instead of\nbeing re-corrected child by child. (`vf-stack` shipped\nwithout a controller on the theory that slotted `vf-*` children correct\ntheir own origins; this component is where that theory's gap — consumer\ncontent — became visible, and the stack has since adopted the same\narrangement.)\n\nLike the stack it is **typographically transparent**: `vfBase`'s chrome\ndress is returned to `inherit` on the host, so wrapping content in a sized\nbox changes nothing about how that content reads.\n\n`fill-width` / `fill-height` work here the way they do everywhere: read\nabout the host (be as big as *its* parent allows), and compiled for slotted\nchildren — `width: 100%` in normal flow, so a child filling the cross of a\ndeclared box needs no stylesheet. A height fill needs a declared `height`\nto resolve against; with none it is inert, not an error.",
2250
2250
  "name": "VfContainer",
2251
2251
  "slots": [
2252
2252
  {
@@ -2563,7 +2563,7 @@
2563
2563
  "declarations": [
2564
2564
  {
2565
2565
  "kind": "class",
2566
- "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\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`).",
2566
+ "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`).",
2567
2567
  "name": "VfDesktop",
2568
2568
  "cssProperties": [
2569
2569
  {
@@ -2629,6 +2629,33 @@
2629
2629
  "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.",
2630
2630
  "attribute": "pattern"
2631
2631
  },
2632
+ {
2633
+ "kind": "field",
2634
+ "name": "windowTop",
2635
+ "type": {
2636
+ "text": "number | null | undefined"
2637
+ },
2638
+ "description": "The least `top` a window may take, in system px from the screen's top —\nfor a page that keeps a strip of its own under the menu bar, over the\nwindows. A window dragged on this desktop stops there, and\nwindowArea starts there; icons still stop at the menu bar. Below\nthe bar's bottom it changes nothing. Unset, windows stop at the bar.",
2639
+ "attribute": "window-top"
2640
+ },
2641
+ {
2642
+ "kind": "field",
2643
+ "name": "workArea",
2644
+ "type": {
2645
+ "text": "{ left: number; top: number; width: number; height: number }"
2646
+ },
2647
+ "description": "The screen less a slotted `vf-menu-bar` along its top edge, in system\npx from the screen's corner (the bezel excluded, as for\nplacementAt): `{ left, top, width, height }`. The desktop clamps\nthe gestures on its screen into it — an icon dropped, nudged or walked\non the desktop lands below the menu bar, where it can be grabbed again.\nWithout a bar it is the whole screen. Measured at the call.",
2648
+ "readonly": true
2649
+ },
2650
+ {
2651
+ "kind": "field",
2652
+ "name": "windowArea",
2653
+ "type": {
2654
+ "text": "{ left: number; top: number; width: number; height: number }"
2655
+ },
2656
+ "description": "workArea, deepened to windowTop where that is lower: the\narea windows are dragged in. A window's title bar never goes above it.",
2657
+ "readonly": true
2658
+ },
2632
2659
  {
2633
2660
  "kind": "method",
2634
2661
  "name": "fitWithin",
@@ -2807,6 +2834,14 @@
2807
2834
  "default": "false",
2808
2835
  "description": "Whether a one-shot post-upgrade re-normalization is already pending."
2809
2836
  },
2837
+ {
2838
+ "kind": "field",
2839
+ "name": "#visibility",
2840
+ "privacy": "private",
2841
+ "readonly": true,
2842
+ "default": "new MutationObserver(() => this._resolveActive())",
2843
+ "description": "Watches each slotted window's `hidden`, which a page may set directly as\nwell as through `vf-window.hide()`: a hidden window never holds the\nactive state, so the holder hiding hands it on (see _resolveActive).\nRe-pointed at the windows on every slot change."
2844
+ },
2810
2845
  {
2811
2846
  "kind": "field",
2812
2847
  "name": "_pointerGesture",
@@ -2862,7 +2897,7 @@
2862
2897
  }
2863
2898
  }
2864
2899
  ],
2865
- "description": "Whether a slotted window belongs to the floating (utility) tier. The\nattribute is the source of truth here so a not-yet-upgraded element\nstill lands in the right tier (variant reflects, so an upgraded\nproperty-set window agrees)."
2900
+ "description": "Whether a slotted window belongs to the floating (utility) tier: the\n`variant` property where the window has one, else the attribute, so a\nnot-yet-upgraded element still lands in the right tier. Not the\nattribute alone — it reflects in the window's next update, after a\nwindow made by script and appended in the same task has been slotted,\nand that palette would land in the document tier and lose its\n`active`."
2866
2901
  },
2867
2902
  {
2868
2903
  "kind": "method",
@@ -2888,7 +2923,16 @@
2888
2923
  "type": {
2889
2924
  "text": "HTMLElement | null"
2890
2925
  },
2891
- "description": "The active document-tier window, or null while the tier is deactivated\n(or has no windows). Utility windows are never the holder.",
2926
+ "description": "The active document-tier window, or null while the tier is deactivated\n(or has no shown windows). Utility windows and hidden windows are never\nthe holder.",
2927
+ "readonly": true
2928
+ },
2929
+ {
2930
+ "kind": "field",
2931
+ "name": "stackingOrder",
2932
+ "type": {
2933
+ "text": "HTMLElement[]"
2934
+ },
2935
+ "description": "The slotted windows in stacking order, bottom-most first: the document\ntier, then the floating tier above it, each in the order its windows\nwere last raised. Hidden windows are included where they stand. This is\nthe order the eye sees; the windows' light-DOM order catches up with it\nonly once a gesture ends, and a raise by keyboard focus moves no node.",
2892
2936
  "readonly": true
2893
2937
  },
2894
2938
  {
@@ -2918,7 +2962,7 @@
2918
2962
  }
2919
2963
  }
2920
2964
  ],
2921
- "description": "The z/active half of a raise, shared by every path."
2965
+ "description": "The z/active half of a raise, shared by every path. A hidden window\ntakes its place in the stack and nothing else: it activates when it is\nshown and raised."
2922
2966
  },
2923
2967
  {
2924
2968
  "kind": "method",
@@ -2968,6 +3012,23 @@
2968
3012
  "privacy": "private",
2969
3013
  "description": "A dragged icon, or a window opening from a rect, asking for the screen's\ndrag surface (see the class doc). Non-composed, so it reaches here only\nthrough the light-DOM path the asker is slotted on — a window body\nincluded. The nearest desktop answers; an outer one leaves a filled\ndetail alone."
2970
3014
  },
3015
+ {
3016
+ "kind": "field",
3017
+ "name": "_onBoundsRequest",
3018
+ "privacy": "private",
3019
+ "description": "A gesture measuring the box it is clamped into (src/position.ts): for a\nhost placed on this screen — a slotted window, an icon in an unplaced\nfield — the screen, bezel excluded, and the work area's top as the\nfloor: windowArea's for a window, workArea's for\nanything else. The floor is rounded up to the host's placement lattice,\nso a snapped landing never rounds above it. A host placed in a box of its\nown (a window body, a placed field) is that box's; an outer desktop\nleaves a filled detail alone."
3020
+ },
3021
+ {
3022
+ "kind": "method",
3023
+ "name": "_menuBarFloor",
3024
+ "privacy": "private",
3025
+ "return": {
3026
+ "type": {
3027
+ "text": "number"
3028
+ }
3029
+ },
3030
+ "description": "The bottom of a slotted, shown menu bar along the screen's top edge, system px; else 0."
3031
+ },
2971
3032
  {
2972
3033
  "kind": "field",
2973
3034
  "name": "_onFocusIn",
@@ -3004,6 +3065,28 @@
3004
3065
  },
3005
3066
  "description": "Wire up newly slotted windows: seed z-indices, normalize `active`."
3006
3067
  },
3068
+ {
3069
+ "kind": "method",
3070
+ "name": "_resolveActive",
3071
+ "privacy": "private",
3072
+ "return": {
3073
+ "type": {
3074
+ "text": "void"
3075
+ }
3076
+ },
3077
+ "description": "Resolve who should hold `active` after the window set or a window's\nvisibility changed: a holder still slotted and shown keeps it, a\ndeliberate deactivation is preserved (never promote a survivor over the\nuser's \"clicked the Finder\"), and only then does the topmost shown\nwindow inherit — the active window left the DOM or was hidden, or a\nwindow was shown into a tier with none active. Applied even when null,\nso a stray `active` attribute on slotted markup, or on a hidden window,\nis normalized in the deactivated state too."
3078
+ },
3079
+ {
3080
+ "kind": "method",
3081
+ "name": "_observeWindows",
3082
+ "privacy": "private",
3083
+ "return": {
3084
+ "type": {
3085
+ "text": "void"
3086
+ }
3087
+ },
3088
+ "description": "Watch the `hidden` attribute of every slotted window, and no other."
3089
+ },
3007
3090
  {
3008
3091
  "kind": "method",
3009
3092
  "name": "_normalizeAfterUpgrade",
@@ -3098,7 +3181,32 @@
3098
3181
  "text": "void"
3099
3182
  }
3100
3183
  },
3101
- "description": "Re-order the slotted windows in the light DOM to match their z-order\n(bottom-most first), so sequential focus navigation walks the stack the\nway the eye does — and Shift+Tab is its exact mirror. The utility band\nsorts the floating tier after every document window by construction.\n\nMinimal-move: windows already in relative order are never touched (the\ncommon case — after one raise, one window moves). Non-window siblings\n(a menu bar, page content) keep their positions; only a window that must\ncross the stack moves past them. Moving a node containing the focused\nelement drops focus to `<body>`, so it is restored afterwards — behind\n`_restoringFocus`, because the restore re-fires focusin (see\n_onFocusIn) on an element that may sit in a background window.\nReached only through _requestDomSync's task — after a pointer\ngesture or a programmatic raise, never from a focus-driven one (see\n_raise for why)."
3184
+ "description": "Re-order the slotted windows in the light DOM to match their z-order\n(bottom-most first), so sequential focus navigation walks the stack the\nway the eye does — and Shift+Tab is its exact mirror. The utility band\nsorts the floating tier after every document window by construction.\n\nMinimal-move: windows already in relative order are never touched (the\ncommon case — after one raise, one window moves). Non-window siblings\n(a menu bar, page content) keep their positions; only a window that must\ncross the stack moves past them. `moveBefore()` keeps the focus inside a\nmoved window where the browser has it (_move); re-inserting a\nnode containing the focused element drops focus to `<body>`, so it is\nrestored afterwards — behind `_restoringFocus`, because the restore\nre-fires focusin (see _onFocusIn) on an element that may sit in a\nbackground window.\nReached only through _requestDomSync's task — after a pointer\ngesture or a programmatic raise, never from a focus-driven one (see\n_raise for why)."
3185
+ },
3186
+ {
3187
+ "kind": "method",
3188
+ "name": "_move",
3189
+ "privacy": "private",
3190
+ "return": {
3191
+ "type": {
3192
+ "text": "void"
3193
+ }
3194
+ },
3195
+ "parameters": [
3196
+ {
3197
+ "name": "win",
3198
+ "type": {
3199
+ "text": "HTMLElement"
3200
+ }
3201
+ },
3202
+ {
3203
+ "name": "before",
3204
+ "type": {
3205
+ "text": "Node | null"
3206
+ }
3207
+ }
3208
+ ],
3209
+ "description": "Move a slotted window before `before`: with `Element.moveBefore()` where\nthe browser has it, which keeps the focus inside the window — a rename\nfield, a text field's caret — through the move; else by re-inserting it,\nand _syncDomOrder puts the focus back."
3102
3210
  },
3103
3211
  {
3104
3212
  "kind": "field",
@@ -3158,7 +3266,7 @@
3158
3266
  ],
3159
3267
  "events": [
3160
3268
  {
3161
- "description": "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 with none behind it). Fired once per change of holder, never for a re-assertion of the same one.",
3269
+ "description": "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.",
3162
3270
  "name": "vf-activate"
3163
3271
  }
3164
3272
  ],
@@ -3193,6 +3301,14 @@
3193
3301
  "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.",
3194
3302
  "fieldName": "pattern"
3195
3303
  },
3304
+ {
3305
+ "name": "window-top",
3306
+ "type": {
3307
+ "text": "number | null | undefined"
3308
+ },
3309
+ "description": "The least `top` a window may take, in system px from the screen's top —\nfor a page that keeps a strip of its own under the menu bar, over the\nwindows. A window dragged on this desktop stops there, and\nwindowArea starts there; icons still stop at the menu bar. Below\nthe bar's bottom it changes nothing. Unset, windows stop at the bar.",
3310
+ "fieldName": "windowTop"
3311
+ },
3196
3312
  {
3197
3313
  "name": "top",
3198
3314
  "type": {
@@ -4708,7 +4824,7 @@
4708
4824
  "declarations": [
4709
4825
  {
4710
4826
  "kind": "class",
4711
- "description": "`<vf-icon-field>` — the container a field of `vf-icon`s sits in: a desktop's\nicons, a folder window's contents.\n\nEvery other set in the kit has its container — `vf-list` owns\n`vf-list-item`, `vf-radio-group` owns `vf-radio`, `vf-menu` owns\n`vf-menu-item`, `vf-select` owns `vf-option` — and `vf-icon` was the one\nitem whose container the consumer wrote by hand: a bare\n`<div role=\"listbox\" aria-multiselectable=\"true\">`, because an `option` is\nonly valid inside a `listbox` that owns it, and every kit container is a\npositioning anchor by design, so nothing in the kit was neutral enough to\nstand in. A consumer who forgot the role got icons that silently degraded\nto pictures. This is that container, as kit furniture:\n\n```html\n<vf-desktop width=\"512\" height=\"342\">\n <vf-icon-field label=\"Desktop\" fill-width fill-height>\n <vf-icon label=\"Macintosh HD\" width=\"64\" selectable movable editable left=\"16\" top=\"24\">…</vf-icon>\n <vf-icon label=\"Trash\" width=\"64\" selectable movable editable left=\"16\" top=\"280\">…</vf-icon>\n </vf-icon-field>\n <vf-window heading=\"Documents\" width=\"320\" height=\"201\" scrollbars=\"both\" top=\"40\" left=\"120\">\n <vf-icon-field label=\"Documents\">\n <vf-icon label=\"Read Me\" width=\"64\" selectable movable editable left=\"16\" top=\"16\">…</vf-icon>\n </vf-icon-field>\n </vf-window>\n</vf-desktop>\n```\n\n### Semantics through internals\n\n`role=\"listbox\"` and `aria-multiselectable=\"true\"` always — the Finder was\nalways multi-select, so there is no attribute for it — and `label` as the\naccessible name, the way `vf-scroll-area` takes one. Internals values are\ndefaults (SPEC §2): a consumer's own `role`/`aria-*` on the tag still wins.\nA `selectable` icon inside becomes a real `option` carrying `aria-selected`;\n`vf-icon` matches this tag directly, since a role written through internals\nnever lands as an attribute for `closest()` to find.\n\n### Layout-neutral, exactly what the div was\n\nAn in-flow block with no size, no inset and no position of its own,\npainting nothing — the `vf-stack` posture. A field whose icons are all\nplaced is a zero-height block, so the icons keep anchoring to the desktop's\nraster or the window's plane, stored coordinates mean what they meant, and\na press on the bare dither still reaches the desktop. It takes `top`/`left`\nand `width`/`height` like the other declared-box containers, so a page that\nwants the field to be a box places and sizes it, at which point it becomes\nthe anchor — the placement working, as documented for everything.\n`fill-width`/`fill-height` are the other way to give it a box, read about\nthe host the way `vf-stack` and `vf-container` read them: a filled field\nstays in flow and static, so its icons keep their anchor, and it has a\nsurface to press — which is what the rubber band needs.\n\n### The rubber band\n\nA press on the field's own background — not on an icon — dragged across\nthe field draws the Finder's dotted selection rectangle from the press to\nthe pointer, and the selection follows it live by the Finder's one rule:\n**an icon is selected exactly when it was selected at the press XOR the\nrectangle touches it.** The press fixes that anchor: a plain press has\nalready cleared everything (the way any press outside an icon does), so\nthe rectangle simply selects what it touches and releases what it leaves;\nwith Shift or ⌘ held the selection survives the press and the rectangle\n*toggles* against it — an icon already selected deselects while the\nrectangle covers it and comes back when it leaves, one that was not\nselects. Each change reports `vf-select` as a press would. \"Touches\"\nmeans the icon's art cell or its name plate (VfIcon.touches), not\nthe empty cell beside them, so a rectangle through the gap between two\nicons selects neither. The release keeps the result; Escape or a\n`pointercancel` cancels, the rectangle vanishes and the selection goes\nback to the anchor. A press that never leaves its system pixel draws\nnothing.\n\nThe rectangle is drawn with the drag outline's pen (* paintSelectionRect: the dotted one-pixel outline, `filter: invert(1)`\nunder `mix-blend-mode: difference`) at the field's own level, which is\nwhere the Finder drew its marquee — on the desktop under the windows, in a\nwindow body over that window's content and under any window above it —\npositioned against the viewport from the field's own shadow, since a\nlayout-neutral field has no box of its own to anchor to. It reaches no\nfurther than the field, inside every clip above the field (a window body,\na scroll viewport, the raster's edge), so it never paints outside the\nregion the icons are visible in. Its dots take the pen's phase on the\n*screen* — the desktop's, found by the `vf-drag-surface-request`\nhandshake the drag outline uses (penPhase) — so over the desktop\ndither the rectangle reads as a black line wherever the field sits; with\nno desktop, the field's own box is the reference.\n\nOnly a field with a box can be pressed: fill it, or place and size it. An\nunplaced, unfilled field of placed icons is zero-height and draws no band.\n`touch-action` is left alone, so on a touch screen a drag on the\nbackground pans a scrolling window as it always did; the band is a mouse\nand pen gesture.\n\n### The walk\n\nThe Finder's Clean Up moved a container's icons one at a time, each\nicon's dotted outline travelling to its cell and the icon landing when\nthe outline arrived. dragIcons is that walk: `{ icon, left, top }`\nmoves in the page's order — which icon goes first is a statement about\nthe page's lattice, and the kit ships none — each through the icon's own\n`dragTo`, with a beat (`WALK_BEAT_MS`, src/motion.ts) after every landing\nthat showed. It resolves when the last icon has landed. One walk per\nfield: a second call finishes the first. A press anywhere, or Escape,\nfinishes it too — every icon still to go lands at its target at once, the\nFinder never made you wait — and so does the field leaving the DOM; an\nicon gone from the DOM by its turn is skipped. Under\n`prefers-reduced-motion` every icon lands at once, no outline, no beat.\n\n### `size` is the view's\n\n`large` or `small`, written onto every icon in the field when set and\nwhenever it changes, so View → by Small Icon is one attribute instead of a\nloop over every icon. Unset, each icon keeps its own.\n\n### The desktop uses it, and renders none\n\nA desktop's icons sit in a field slotted into `vf-desktop` beside the\nwindows, written by the page; a folder window's icons sit in one slotted\ninto the window body. `vf-desktop` builds no field of its own: its furniture\nis slotted light DOM and the page decides what it is, a desktop with no\nicons should carry no empty listbox, and a field inside the desktop's\nshadow is one `closest()` could not reach.",
4827
+ "description": "`<vf-icon-field>` — the container a field of `vf-icon`s sits in: a desktop's\nicons, a folder window's contents.\n\nEvery other set in the kit has its container — `vf-list` owns\n`vf-list-item`, `vf-radio-group` owns `vf-radio`, `vf-menu` owns\n`vf-menu-item`, `vf-select` owns `vf-option` — and `vf-icon` was the one\nitem whose container the consumer wrote by hand: a bare\n`<div role=\"listbox\" aria-multiselectable=\"true\">`, because an `option` is\nonly valid inside a `listbox` that owns it, and every kit container is a\npositioning anchor by design, so nothing in the kit was neutral enough to\nstand in. A consumer who forgot the role got icons that silently degraded\nto pictures. This is that container, as kit furniture:\n\n```html\n<vf-desktop width=\"512\" height=\"342\">\n <vf-icon-field label=\"Desktop\" fill-width fill-height>\n <vf-icon label=\"Macintosh HD\" width=\"64\" selectable movable editable left=\"16\" top=\"24\">…</vf-icon>\n <vf-icon label=\"Trash\" width=\"64\" selectable movable editable left=\"16\" top=\"280\">…</vf-icon>\n </vf-icon-field>\n <vf-window heading=\"Documents\" width=\"320\" height=\"201\" scrollbars=\"both\" top=\"40\" left=\"120\">\n <vf-icon-field label=\"Documents\">\n <vf-icon label=\"Read Me\" width=\"64\" selectable movable editable left=\"16\" top=\"16\">…</vf-icon>\n </vf-icon-field>\n </vf-window>\n</vf-desktop>\n```\n\n### Semantics through internals\n\n`role=\"listbox\"` and `aria-multiselectable=\"true\"` always — the Finder was\nalways multi-select, so there is no attribute for it — and `label` as the\naccessible name, the way `vf-scroll-area` takes one. Internals values are\ndefaults (SPEC §2): a consumer's own `role`/`aria-*` on the tag still wins.\nA `selectable` icon inside becomes a real `option` carrying `aria-selected`;\n`vf-icon` matches this tag directly, since a role written through internals\nnever lands as an attribute for `closest()` to find.\n\n### Layout-neutral, exactly what the div was\n\nAn in-flow block with no size, no inset and no position of its own,\npainting nothing — the `vf-stack` posture. A field whose icons are all\nplaced is a zero-height block, so the icons keep anchoring to the desktop's\nraster or the window's plane, stored coordinates mean what they meant, and\na press on the bare dither still reaches the desktop. It takes `top`/`left`\nand `width`/`height` like the other declared-box containers, so a page that\nwants the field to be a box places and sizes it, at which point it becomes\nthe anchor — the placement working, as documented for everything.\n`fill-width`/`fill-height` are the other way to give it a box, read about\nthe host the way `vf-stack` and `vf-container` read them: a filled field\nstays in flow and static, so its icons keep their anchor, and it has a\nsurface to press — which is what the rubber band needs.\n\n### The rubber band\n\nA press on the field's own background — not on an icon — dragged across\nthe field draws the Finder's dotted selection rectangle from the press to\nthe pointer, and the selection follows it live by the Finder's one rule:\n**an icon is selected exactly when it was selected at the press XOR the\nrectangle touches it.** The press fixes that anchor: a plain press has\nalready cleared everything (a press on a field's background does), so\nthe rectangle simply selects what it touches and releases what it leaves;\nwith Shift or ⌘ held the selection survives the press and the rectangle\n*toggles* against it — an icon already selected deselects while the\nrectangle covers it and comes back when it leaves, one that was not\nselects. Each change reports `vf-select` as a press would. \"Touches\"\nmeans the icon's art cell or its name plate (VfIcon.touches), not\nthe empty cell beside them, so a rectangle through the gap between two\nicons selects neither. The release keeps the result; Escape or a\n`pointercancel` cancels, the rectangle vanishes and the selection goes\nback to the anchor. A press that never leaves its system pixel draws\nnothing.\n\nThe rectangle is drawn with the drag outline's pen (* paintSelectionRect: the dotted one-pixel outline, `filter: invert(1)`\nunder `mix-blend-mode: difference`) at the field's own level, which is\nwhere the Finder drew its marquee — on the desktop under the windows, in a\nwindow body over that window's content and under any window above it —\npositioned against the viewport from the field's own shadow, since a\nlayout-neutral field has no box of its own to anchor to. It reaches no\nfurther than the field, inside every clip above the field (a window body,\na scroll viewport, the raster's edge), so it never paints outside the\nregion the icons are visible in. Its dots take the pen's phase on the\n*screen* — the desktop's, found by the `vf-drag-surface-request`\nhandshake the drag outline uses (penPhase) — so over the desktop\ndither the rectangle reads as a black line wherever the field sits; with\nno desktop, the field's own box is the reference.\n\nOnly a field with a box can be pressed: fill it, or place and size it. An\nunplaced, unfilled field of placed icons is zero-height and draws no band.\n`touch-action` is left alone, so on a touch screen a drag on the\nbackground pans a scrolling window as it always did; the band is a mouse\nand pen gesture.\n\n### The walk\n\nThe Finder's Clean Up moved a container's icons one at a time, each\nicon's dotted outline travelling to its cell and the icon landing when\nthe outline arrived. dragIcons is that walk: `{ icon, left, top }`\nmoves in the page's order — which icon goes first is a statement about\nthe page's lattice, and the kit ships none — each through the icon's own\n`dragTo`, with a beat (`WALK_BEAT_MS`, src/motion.ts) after every landing\nthat showed. It resolves when the last icon has landed. One walk per\nfield: a second call finishes the first. A press anywhere, or Escape,\nfinishes it too — every icon still to go lands at its target at once, the\nFinder never made you wait — and so does the field leaving the DOM; an\nicon gone from the DOM by its turn is skipped. Under\n`prefers-reduced-motion` every icon lands at once, no outline, no beat.\n\n### `size` is the view's\n\n`large` or `small`, written onto every icon in the field when set and\nwhenever it changes, so View → by Small Icon is one attribute instead of a\nloop over every icon. Unset, each icon keeps its own.\n\n### The desktop uses it, and renders none\n\nA desktop's icons sit in a field slotted into `vf-desktop` beside the\nwindows, written by the page; a folder window's icons sit in one slotted\ninto the window body. `vf-desktop` builds no field of its own: its furniture\nis slotted light DOM and the page decides what it is, a desktop with no\nicons should carry no empty listbox, and a field inside the desktop's\nshadow is one `closest()` could not reach.",
4712
4828
  "name": "VfIconField",
4713
4829
  "slots": [
4714
4830
  {
@@ -5664,13 +5780,13 @@
5664
5780
  "privacy": "private",
5665
5781
  "readonly": true,
5666
5782
  "default": "new DocumentListenersController(this, () => [ [document, 'pointerdown', this.#onOutsidePointerDown, true], ])",
5667
- "description": "Clicking elsewhere deselects, the way it does on a real desktop. Attached\nonly while this icon is both selectable and selected, so an unselected\nfield of icons costs nothing. Capture phase, so a handler that stops the\npress still deselects — and so the icon being clicked *into* selects after\nthis runs, which is what makes plain clicks single-select with no container\nmanaging the set. A container that wants multi-select suppresses it by\nowning `selected` and leaving `selectable` off."
5783
+ "description": "Clicking elsewhere deselects, the way it does on a real desktop: anywhere\noutside an icon alone, and on another icon or a field's background for an\nicon in a field, whose selection outlives a press on the menu bar, a\ndialog or another window (see #onOutsidePointerDown). Attached\nonly while this icon is both selectable and selected, so an unselected\nfield of icons costs nothing. Capture phase, so a handler that stops the\npress still deselects — and so the icon being clicked *into* selects after\nthis runs, which is what makes plain clicks single-select with no container\nmanaging the set. A container that wants multi-select suppresses it by\nowning `selected` and leaving `selectable` off."
5668
5784
  },
5669
5785
  {
5670
5786
  "kind": "field",
5671
5787
  "name": "#keepWhole",
5672
5788
  "privacy": "private",
5673
- "description": "Where a moved icon is allowed to end up, in system px. Unlike `vf-window`,\nwhich only keeps a grabbable strip on screen, an icon is small enough to\nhold whole — losing half of one to an edge reads as a bug rather than as a\nwindow pushed aside. Except in a box that scrolls (#anchorScrolls):\nthe rails reach anything placed past such a box, so only its origin holds\n— the Finder put an icon below a window's fold and scrolled to it."
5789
+ "description": "Where a moved icon is allowed to end up, in system px. Unlike `vf-window`,\nwhich only keeps a grabbable strip on screen, an icon is small enough to\nhold whole — losing half of one to an edge reads as a bug rather than as a\nwindow pushed aside. On a desktop's screen it holds below the menu bar\n(the bounds' `top`). Except in a box that scrolls (#anchorScrolls):\nthe rails reach anything placed past such a box, so only its origin holds\n— the Finder put an icon below a window's fold and scrolled to it."
5674
5790
  },
5675
5791
  {
5676
5792
  "kind": "method",
@@ -6111,6 +6227,12 @@
6111
6227
  },
6112
6228
  "description": "Keep the typed name and close the field — unless there is no name left, in\nwhich case the edit is refused and the old one comes back.\n\nA file has to be called something: System 7 would not let you commit an\nempty name, and this does the same, treating a run of nothing but spaces\nas empty too (the plate would read as blank either way). The refusal is\nreported as `vf-name-rejected` for the same reason `vf-name-too-long` is —\nso a host can say why instead of leaving the name to silently snap back."
6113
6229
  },
6230
+ {
6231
+ "kind": "field",
6232
+ "name": "#onRenameBlur",
6233
+ "privacy": "private",
6234
+ "description": "The rename field lost focus, which commits the name — unless the focus\nis back on the field before the task is out. That is a node move, not\nthe user leaving: a desktop re-inserting the window this icon sits in, to\nkeep the DOM in stacking order, takes the focus off the field and puts it\nstraight back. A microtask, so the check runs after that task's own\ncode and before anything else."
6235
+ },
6114
6236
  {
6115
6237
  "kind": "method",
6116
6238
  "name": "cancelEditing",
@@ -8145,6 +8267,10 @@
8145
8267
  {
8146
8268
  "description": "`vf-menu` elements.",
8147
8269
  "name": ""
8270
+ },
8271
+ {
8272
+ "description": "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'.",
8273
+ "name": "end"
8148
8274
  }
8149
8275
  ],
8150
8276
  "members": [
@@ -8203,6 +8329,16 @@
8203
8329
  },
8204
8330
  "privacy": "private"
8205
8331
  },
8332
+ {
8333
+ "kind": "field",
8334
+ "name": "_hasEnd",
8335
+ "type": {
8336
+ "text": "boolean"
8337
+ },
8338
+ "privacy": "private",
8339
+ "default": "false",
8340
+ "description": "Whether the `end` slot has assigned content (drives its `.empty` gate)."
8341
+ },
8206
8342
  {
8207
8343
  "kind": "field",
8208
8344
  "name": "#openMenu",
@@ -8279,6 +8415,25 @@
8279
8415
  "privacy": "private",
8280
8416
  "description": "Focus left the bar entirely while a menu was open: close it, so an open\npanel can't outlive the focus that operates it and go on capturing every\narrow key on the page. Modeled on `VfSelect.handleHostFocusOut`; the\nrelatedTarget is retargeted to light-DOM scope, so slotted menus (and\ntheir panels' items) read as contained."
8281
8417
  },
8418
+ {
8419
+ "kind": "method",
8420
+ "name": "#onEndSlotChange",
8421
+ "privacy": "private",
8422
+ "return": {
8423
+ "type": {
8424
+ "text": "void"
8425
+ }
8426
+ },
8427
+ "parameters": [
8428
+ {
8429
+ "name": "event",
8430
+ "type": {
8431
+ "text": "Event"
8432
+ }
8433
+ }
8434
+ ],
8435
+ "description": "The `.empty` gate: the end slot takes part in the row only while populated."
8436
+ },
8282
8437
  {
8283
8438
  "kind": "method",
8284
8439
  "name": "#onSlotChange",
@@ -20297,6 +20452,15 @@
20297
20452
  "description": "See minWidth. Unbounded by default.",
20298
20453
  "attribute": "max-height"
20299
20454
  },
20455
+ {
20456
+ "kind": "field",
20457
+ "name": "sizeLimits",
20458
+ "type": {
20459
+ "text": "{ minWidth: number; minHeight: number; maxWidth: number; maxHeight: number }"
20460
+ },
20461
+ "description": "The sizeRect as it applies, in whole system px: `min-width` and\n`min-height`, or the 80×54 floor where unset, and `max-width` and\n`max-height`, `Infinity` where unbounded — the range the grow box drags\nthe window in. A page that sizes the window itself, re-fitting it to a\nsmaller screen say, reads it to stay inside the same range.",
20462
+ "readonly": true
20463
+ },
20300
20464
  {
20301
20465
  "kind": "field",
20302
20466
  "name": "scrollbars",
@@ -20413,7 +20577,7 @@
20413
20577
  "kind": "field",
20414
20578
  "name": "#keepGrabbable",
20415
20579
  "privacy": "private",
20416
- "description": "Where a dragged window is allowed to end up, in system px: clamped against\nthe positioning parent (the desktop, usually) so it can't be pushed fully\npast an edge and lost. Only a grabbable strip has to stay in — a window\npushed mostly off-screen is a thing System 7 let you do.\n\nThe box comes from the placement controller, measured once when the drag\nbegan — see PlacementController.moveTo for why a fresh measurement\nper move is the wrong one."
20580
+ "description": "Where a dragged window is allowed to end up, in system px: clamped against\nthe positioning parent (the desktop, usually) so it can't be pushed fully\npast an edge and lost. Only a grabbable strip has to stay in — a window\npushed mostly off-screen is a thing System 7 let you do — and the title\nbar never goes above the bounds' top: on a desktop, the bottom of its\nmenu bar (or `window-top`), where it would be out of reach.\n\nThe box comes from the placement controller, measured once when the drag\nbegan — see PlacementController.moveTo for why a fresh measurement\nper move is the wrong one."
20417
20581
  },
20418
20582
  {
20419
20583
  "kind": "field",
@@ -23868,7 +24032,7 @@
23868
24032
  },
23869
24033
  {
23870
24034
  "kind": "mixin",
23871
- "description": "Explicit placement — `top`/`left` in whole system px, on **every** component.\n\nNo exceptions, deliberately. These are web components, and a consumer may put\none wherever they like; the kit does not get to decide that a `vf-option` is\nonly ever a popup row. The rows a container normally owns (`vf-option`,\n`vf-menu-item`, `vf-list-item`) and a bar's `vf-menu` take the pair on the\nsame terms as anything else — and stating an origin on one *inside* its\nmanaging parent does take it out of that parent's flow layout, which is the\nplacement working, not failing. Each of those components documents what its\ncontainer stops doing for it. `vf-dialog` takes the pair in viewport\ncoordinates, the one difference the platform forces (see modal-dialog.ts).\n\nA DITL resource laid a dialog out as a list of items, each with a rectangle\nin the window's own coordinates; arranging controls by stating where they go\nis as native to System 7 as stacking them. This mixin is that mechanism:\ndeclaring `top` or `left` takes the element out of normal flow and\nabsolutely positions it within its parent, both coordinates in the art's own\nunit so the position scales with the display like every other metric. A\nwindow or dialog body can be laid out either way — a tree of `vf-stack`s, or\npositioned children — with no stylesheet in either case.\n\nMechanics, mirroring how `vf-stack` writes its declared size:\n\n- Setting either property writes `position: absolute` and both offsets onto\n the host's inline style as `calc(var(--vf-scale, 1) * Npx)` — live against\n the display, resolved at paint time. The coordinate left unset is 0.\n- `right`/`bottom` are released to `auto` and `margin` zeroed, so the stated\n offsets are the whole story — an auto-width box with both edges set would\n stretch rather than sit (the `vf-icon` seed logic, generalized).\n- Unsetting both returns the element to normal flow: every inline\n declaration this wrote is removed, and stylesheet values resume.\n\nThe anchor is CSS's own: the nearest positioned ancestor's padding box. The\nkit's containers are all deliberate anchors — a desktop's raster, a window's\nor a dialog's content region (the frame's inner edge, below any title bar,\nwith no inset — flow content starts there too, exactly the DITL\nconvention), a stack's box, a fieldset's border box, a scroll area's\nscrolled plane. In a non-kit parent, give the parent `position: relative`,\nthe one line of CSS this feature can't write for you.\n\nThe writing rides a ReactiveController (`hostUpdated`) rather than\nan `updated()` override, for a lifecycle reason worth keeping in the source:\nthe mixin sits *under* each component class, and most components override\n`updated()` without calling `super.updated()` — Lit's base is a no-op, so\nnothing ever forced the call — which would silently shadow a mixin-level\noverride. A controller is invoked by ReactiveElement itself, after every\nupdate, no matter what the subclass does.\n\nA gesture writes **through these properties**: `vf-window`'s title-bar drag\nand `vf-icon`'s drag and arrow nudge hand their new origin to a\nPlacementController, which snaps it onto the placement lattice and\nsets `left`/`top`. A moved element is therefore placed exactly the way an\nauthored one is — a live `calc()` in the art's own unit — and stays where it\nwas dropped when the zoom or the display changes what a system px costs.\n\nIt did not always. Writing the resolved CSS px straight to the inline style\nfroze the coordinate in the wrong unit: `--vf-scale` moved under it at every\nzoom step and the same constant read back as a different number of system px,\nso moved windows and icons slid off the grid the rest of the kit stayed on\n(by `3z / round(3z)` — 10% at 110% zoom, where nothing else moves at all).\n\nSetting a property yourself is still the deliberate way to re-place a moved\nelement. The controller re-applies **only when the values changed**, so an\nunrelated update — a heading change, a desktop toggling `active` — never\nre-asserts a coordinate and costs nothing.\n\n**Every write it makes is announced** as `vf-placement-change` (bubbles,\n`composed: false`, detail `{ left, top }` — nulls on a return to flow). It\nis an internal coordination event on the terms of the menu handshakes:\nparent and child share one light tree, and a kit protocol must not leak out\nof a consumer's shadow boundary. `vf-scroll-area` listens for it and\nre-measures, because a placed child moved through `top`/`left` changes the\nscroll range with no box changing anywhere a ResizeObserver can see — the\nplane's box is never grown by an absolutely positioned child. One funnel\ncovers a drop, an arrow nudge and a page's own `icon.left = …` alike.\n\n**`origin`** names which point of the host's own box the pair places —\nnine keywords, vertical then horizontal (`top center`, `bottom right`,\n`center`), `top left` the default. A title stays centered on its `left`\nwhatever its text; a button group keeps its bottom-right corner in from\nthe dialog's however long its labels run. The point is measured, not\ntransformed: the controller reads the host's border box in whole system px\n(w × h) and writes `left` as the stated `L` less ⌈w·fx⌉ (`fx` 0, ½ or 1),\n`top` the same way — the ceiling sends an odd size's leftover half toward\nthe start, the tie rule the kit's other centering uses. A ResizeObserver\nkeeps the offset current through a relabel or a late font, and each such\nrewrite is announced like any other write. A zoom step changes CSS px, not\nsystem px, so it recomputes the same offset. `translate: -50% 0` would have\nbeen the no-JavaScript version, and fails twice: a percentage of an odd\nwidth is half a pixel, which smears the 1-bit art, and a transform makes\nthe host the containing block for `position: fixed` descendants — a placed\ngroup holding a `vf-select` would trap its list. `top left` is the plain\ncode path: no observer, no measurement, the same inline style as before\nthe attribute existed. An unknown value places as `top left` and warns once\nper element. On its own the attribute places nothing: it changes what the\npair means, and is inert on a host in flow.\n\n**`fixed`** holds the placement against the *visible* region of the nearest\nscrolling ancestor instead of its scrolled plane — a tool strip over a\ndocument, a column header over rows — and the flag alone places at (0,0).\nThe engine underneath is `position: sticky`, the one way the platform holds\na box against a scrollport from inside it on the compositor (a scroll\nlistener counter-translating lags a frame; a layer outside the scroller\nstops wheel and touch over it). Sticky boxes stay in flow, which is not\nwhat a placement is, so the controller erases the footprint: the host is\nblockified (no line box), shrink-wrapped, and given a negative right/bottom\nmargin equal to its own box — a 0×0 margin box that flow content lays out\nas if it weren't there, measured by a ResizeObserver so a relabel or a zoom\nstep keeps it exact. `z-index: 1` puts it over the plane's placed children,\nwhich is where a fixed strip belongs. Two facts of the engine that the\ndocs state as rules: sticky only ever pushes a box *down* from where the\nflow put it, so a fixed child comes before the flow content in its parent;\nand a scroll container that never scrolls (a plain window body is\n`overflow: hidden`) holds it exactly where placement would.",
24035
+ "description": "Explicit placement — `top`/`left` in whole system px, on **every** component.\n\nNo exceptions, deliberately. These are web components, and a consumer may put\none wherever they like; the kit does not get to decide that a `vf-option` is\nonly ever a popup row. The rows a container normally owns (`vf-option`,\n`vf-menu-item`, `vf-list-item`) and a bar's `vf-menu` take the pair on the\nsame terms as anything else — and stating an origin on one *inside* its\nmanaging parent does take it out of that parent's flow layout, which is the\nplacement working, not failing. Each of those components documents what its\ncontainer stops doing for it. `vf-dialog` takes the pair in viewport\ncoordinates, the one difference the platform forces (see modal-dialog.ts).\n\nA DITL resource laid a dialog out as a list of items, each with a rectangle\nin the window's own coordinates; arranging controls by stating where they go\nis as native to System 7 as stacking them. This mixin is that mechanism:\ndeclaring `top` or `left` takes the element out of normal flow and\nabsolutely positions it within its parent, both coordinates in the art's own\nunit so the position scales with the display like every other metric. A\nwindow or dialog body can be laid out either way — a tree of `vf-stack`s, or\npositioned children — with no stylesheet in either case.\n\nMechanics, mirroring how `vf-stack` writes its declared size:\n\n- Setting either property writes `position: absolute` and both offsets onto\n the host's inline style as `calc(var(--vf-scale, 1) * Npx)` — live against\n the display, resolved at paint time. The coordinate left unset is 0.\n- `right`/`bottom` are released to `auto` and `margin` zeroed, so the stated\n offsets are the whole story — an auto-width box with both edges set would\n stretch rather than sit (the `vf-icon` seed logic, generalized).\n- Unsetting both returns the element to normal flow: every inline\n declaration this wrote is removed, and stylesheet values resume.\n\nThe anchor is CSS's own: the nearest positioned ancestor's padding box. The\nkit's containers are all deliberate anchors — a desktop's raster, a window's\nor a dialog's content region (the frame's inner edge, below any title bar,\nwith no inset — flow content starts there too, exactly the DITL\nconvention), a stack's box, a fieldset's border box, a scroll area's\nscrolled plane. In a non-kit parent, give the parent `position: relative`,\nthe one line of CSS this feature can't write for you.\n\nThe writing rides a ReactiveController (`hostUpdated`) rather than\nan `updated()` override, for a lifecycle reason worth keeping in the source:\nthe mixin sits *under* each component class, and most components override\n`updated()` without calling `super.updated()` — Lit's base is a no-op, so\nnothing ever forced the call — which would silently shadow a mixin-level\noverride. A controller is invoked by ReactiveElement itself, after every\nupdate, no matter what the subclass does.\n\nA gesture writes **through these properties**: `vf-window`'s title-bar drag\nand `vf-icon`'s drag and arrow nudge hand their new origin to a\nPlacementController, which snaps it onto the placement lattice and\nsets `left`/`top`. A moved element is therefore placed exactly the way an\nauthored one is — a live `calc()` in the art's own unit — and stays where it\nwas dropped when the zoom or the display changes what a system px costs.\n\nIt did not always. Writing the resolved CSS px straight to the inline style\nfroze the coordinate in the wrong unit: `--vf-scale` moved under it at every\nzoom step and the same constant read back as a different number of system px,\nso moved windows and icons slid off the grid the rest of the kit stayed on\n(by `3z / round(3z)` — 10% at 110% zoom, where nothing else moves at all).\n\nSetting a property yourself is still the deliberate way to re-place a moved\nelement. The controller re-applies **only when the values changed**, so an\nunrelated update — a heading change, a desktop toggling `active` — never\nre-asserts a coordinate and costs nothing.\n\n**Every write it makes is announced** as `vf-placement-change` (bubbles,\n`composed: false`, detail `{ left, top }` — nulls on a return to flow), so\na page hears every move the one way: a title-bar drag's steps and its\nrelease (an outline drag's single write), an arrow nudge, a drop, a walk's\nlanding, an `origin` re-measure, and its own property writes. Non-composed,\nbecause parent and child share one light tree and the event has no reason\nto leave a consumer's shadow root. It is dispatched from the host's update,\nwith the new pair already on its style: a handler that writes the pair\nagain takes effect on the next update. `vf-scroll-area` listens for it and\nre-measures, because a placed child moved through `top`/`left` changes the\nscroll range with no box changing anywhere a ResizeObserver can see — the\nplane's box is never grown by an absolutely positioned child.\n\n**`origin`** names which point of the host's own box the pair places —\nnine keywords, vertical then horizontal (`top center`, `bottom right`,\n`center`), `top left` the default. A title stays centered on its `left`\nwhatever its text; a button group keeps its bottom-right corner in from\nthe dialog's however long its labels run. The point is measured, not\ntransformed: the controller reads the host's border box in whole system px\n(w × h) and writes `left` as the stated `L` less ⌈w·fx⌉ (`fx` 0, ½ or 1),\n`top` the same way — the ceiling sends an odd size's leftover half toward\nthe start, the tie rule the kit's other centering uses. A ResizeObserver\nkeeps the offset current through a relabel or a late font, and each such\nrewrite is announced like any other write. A zoom step changes CSS px, not\nsystem px, so it recomputes the same offset. `translate: -50% 0` would have\nbeen the no-JavaScript version, and fails twice: a percentage of an odd\nwidth is half a pixel, which smears the 1-bit art, and a transform makes\nthe host the containing block for `position: fixed` descendants — a placed\ngroup holding a `vf-select` would trap its list. `top left` is the plain\ncode path: no observer, no measurement, the same inline style as before\nthe attribute existed. An unknown value places as `top left` and warns once\nper element. On its own the attribute places nothing: it changes what the\npair means, and is inert on a host in flow.\n\n**`fixed`** holds the placement against the *visible* region of the nearest\nscrolling ancestor instead of its scrolled plane — a tool strip over a\ndocument, a column header over rows — and the flag alone places at (0,0).\nThe engine underneath is `position: sticky`, the one way the platform holds\na box against a scrollport from inside it on the compositor (a scroll\nlistener counter-translating lags a frame; a layer outside the scroller\nstops wheel and touch over it). Sticky boxes stay in flow, which is not\nwhat a placement is, so the controller erases the footprint: the host is\nblockified (no line box), shrink-wrapped, and given a negative right/bottom\nmargin equal to its own box — a 0×0 margin box that flow content lays out\nas if it weren't there, measured by a ResizeObserver so a relabel or a zoom\nstep keeps it exact. `z-index: 1` puts it over the plane's placed children,\nwhich is where a fixed strip belongs. Two facts of the engine that the\ndocs state as rules: sticky only ever pushes a box *down* from where the\nflow put it, so a fixed child comes before the flow content in its parent;\nand a scroll container that never scrolls (a plain window body is\n`overflow: hidden`) holds it exactly where placement would.",
23872
24036
  "name": "VfPositioned",
23873
24037
  "members": [
23874
24038
  {
@@ -24137,7 +24301,7 @@
24137
24301
  "text": "PlacementBounds"
24138
24302
  }
24139
24303
  },
24140
- "description": "The positioning parent's content box, in system px.\n\nA parent with no box at all falls back to the viewport, the same as no\nparent — that is the *other* contract failure (a `position: relative`\ncontainer nobody gave a size), and clamping into nothing would leave the\nhost unable to move at all. The viewport keeps the gesture usable while\nwarnMovableContract does the teaching."
24304
+ "description": "The positioning parent's content box, in system px.\n\nA desktop answers for a host placed on its screen\n(`vf-placement-bounds-request`, the drag surface's handshake idiom): the\nscreen, bezel excluded, with the menu bar's floor as `top`. Any other\nparent is measured.\n\nA parent with no box at all falls back to the viewport, the same as no\nparent — that is the *other* contract failure (a `position: relative`\ncontainer nobody gave a size), and clamping into nothing would leave the\nhost unable to move at all. The viewport keeps the gesture usable while\nwarnMovableContract does the teaching."
24141
24305
  }
24142
24306
  ]
24143
24307
  },
@@ -43,7 +43,9 @@ declare const VfContainer_base: (new (...args: any[]) => import("../size.js").Vf
43
43
  *
44
44
  * **It paints nothing and means nothing** — unless `pattern` or `rule` say
45
45
  * what to paint. No role, keyboard behavior or selection; what it holds
46
- * decides what it is.
46
+ * decides what it is. A page that gives it a role and a `tabindex` — a
47
+ * pattern chooser's cells as radios — gets the kit's keyboard focus mark
48
+ * rather than the browser's ring: the dashed rule below the box.
47
49
  *
48
50
  * **`rule` draws the 1px rule on the edges it names** — `rule="bottom"` is
49
51
  * the menu bar's anatomy (the box's rows over one row of ink), `rule="top"`
@@ -4,16 +4,18 @@ import n from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js
4
4
  import { VfPositioned as r } from "../position.js";
5
5
  import { vfBase as i } from "../styles/recipes/host.js";
6
6
  import { parsePattern as a } from "../patterns.js";
7
- import { parseRule as o, ruleClasses as s, vfRule as c } from "../styles/recipes/rule.js";
8
- import { PatternFillController as l, vfPatternFill as u } from "../pattern-fill.js";
9
- import { GridSnapController as d } from "../grid-snap.js";
10
- import { VfSized as f } from "../size.js";
11
- import { LitElement as p, css as m, html as h } from "lit";
12
- import { property as g, query as _ } from "lit/decorators.js";
7
+ import { vfStrokeInset as o } from "../styles/recipes/stroke.js";
8
+ import { parseRule as s, ruleClasses as c, vfRule as l } from "../styles/recipes/rule.js";
9
+ import { vfFocusUnderline as u } from "../styles/recipes/focus.js";
10
+ import { PatternFillController as d, vfPatternFill as f } from "../pattern-fill.js";
11
+ import { GridSnapController as p } from "../grid-snap.js";
12
+ import { VfSized as m } from "../size.js";
13
+ import { LitElement as h, css as g, html as _ } from "lit";
14
+ import { property as v, query as y } from "lit/decorators.js";
13
15
  //#region src/components/vf-container.ts
14
- var v = class extends f(r(p)) {
16
+ var b = class extends m(r(h)) {
15
17
  constructor(...e) {
16
- super(...e), this._pattern = null, this.#e = !1, this._rule = [], this.#t = !1, this.scale = new t(this), this.gridSnap = new d(this), this.patternFill = new l(this, {
18
+ super(...e), this._pattern = null, this.#e = !1, this._rule = [], this.#t = !1, this.scale = new t(this), this.gridSnap = new p(this), this.patternFill = new d(this, {
17
19
  getBox: () => this.box,
18
20
  getPattern: () => this._pattern
19
21
  });
@@ -21,9 +23,9 @@ var v = class extends f(r(p)) {
21
23
  static {
22
24
  this.styles = [
23
25
  i,
24
- u,
25
- c,
26
- m`
26
+ f,
27
+ l,
28
+ g`
27
29
  :host {
28
30
  display: block;
29
31
  /* Shrink-wrap an undeclared axis rather than claim the parent's
@@ -74,6 +76,22 @@ var v = class extends f(r(p)) {
74
76
  height: 100%;
75
77
  }
76
78
 
79
+ /* A box the page makes focusable — a pattern chooser's cells as
80
+ radios, say — wears the kit's focus mark, not the browser's ring: the
81
+ dashed rule below the box, one blank row under it, since a box like
82
+ that is nothing but fill (vf-swatch's placement). The rule spans the
83
+ box; under forced colors a ruled box's stroke is a border outside the
84
+ padding box the rule measures from, so the insets grow by it there. */
85
+ :host(:focus-visible) {
86
+ outline: none;
87
+ }
88
+ :host(:focus-visible) .box::after {
89
+ ${u}
90
+ bottom: ${o(-3)};
91
+ left: ${o(-1)};
92
+ right: ${o(-1)};
93
+ }
94
+
77
95
  /* The same two words a child uses, read about the host itself — for the
78
96
  parents that can give this box a size: a window body, a filled stack
79
97
  column, a grid cell. A percentage is the one fill a page can always
@@ -105,19 +123,19 @@ var v = class extends f(r(p)) {
105
123
  #t;
106
124
  willUpdate(e) {
107
125
  if (super.willUpdate(e), e.has("pattern") && (this._pattern = a(this.pattern), this._pattern === null && this.pattern?.trim() && !this.#e && (this.#e = !0, console.warn(`vf-container: unknown pattern "${this.pattern}" — a library name (docs/PATTERNS.md) or sixteen hex digits. Painting nothing.`))), e.has("rule")) {
108
- let e = o(this.rule);
126
+ let e = s(this.rule);
109
127
  this._rule = e ?? [], e === null && !this.#t && (this.#t = !0, console.warn(`vf-container: unknown rule "${this.rule}" — edge names (top, right, bottom, left) separated by spaces. Drawing none.`));
110
128
  }
111
129
  }
112
130
  render() {
113
- let e = s(this._rule);
114
- return h`<div
131
+ let e = c(this._rule);
132
+ return _`<div
115
133
  class="vf-snap box vf-pattern-fill${this._pattern ? " vf-patterned" : ""}${e ? ` ${e}` : ""}"
116
134
  >
117
135
  <slot></slot>
118
136
  </div>`;
119
137
  }
120
138
  };
121
- n([g()], v.prototype, "pattern", void 0), n([g()], v.prototype, "rule", void 0), n([_(".box")], v.prototype, "box", void 0), v = n([e("vf-container")], v);
139
+ n([v()], b.prototype, "pattern", void 0), n([v()], b.prototype, "rule", void 0), n([y(".box")], b.prototype, "box", void 0), b = n([e("vf-container")], b);
122
140
  //#endregion
123
- export { v as VfContainer };
141
+ export { b as VfContainer };