vintage-frames 0.5.3 → 0.5.5

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.
@@ -2237,7 +2237,7 @@
2237
2237
  "declarations": [
2238
2238
  {
2239
2239
  "kind": "class",
2240
- "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, at pointer-gesture\nends), so tabbing walks the stack the way the eye does and Shift+Tab is\nits 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 top 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, one whole-surface raster at one image px per system px, 1-bit at\nevery density and zoom (src/pattern-fill.ts).\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`).",
2240
+ "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 top 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, one whole-surface raster at one image px per system px, 1-bit at\nevery density and zoom (src/pattern-fill.ts).\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`).",
2241
2241
  "name": "VfDesktop",
2242
2242
  "cssProperties": [
2243
2243
  {
@@ -2460,6 +2460,16 @@
2460
2460
  "default": "false",
2461
2461
  "description": "Whether _syncDomOrder is putting focus back after a node move."
2462
2462
  },
2463
+ {
2464
+ "kind": "field",
2465
+ "name": "_domSyncTimer",
2466
+ "type": {
2467
+ "text": "number"
2468
+ },
2469
+ "privacy": "private",
2470
+ "default": "0",
2471
+ "description": "The pending DOM-order sync task, or 0 (see _requestDomSync)."
2472
+ },
2463
2473
  {
2464
2474
  "kind": "field",
2465
2475
  "name": "_gestureEnd",
@@ -2503,7 +2513,7 @@
2503
2513
  }
2504
2514
  }
2505
2515
  ],
2506
- "description": "Bring a slotted window to the front of its tier. A document-tier window\nalso becomes the single active window (clearing `active` on the other\ndocument windows); a utility window restacks within the floating tier\nand leaves every `active` state alone.\n\nThe light-DOM order of the slotted windows follows (_syncDomOrder):\nvisual stacking and sequential focus order come from independent channels\n(z-index vs DOM position), and letting them drift apart is how a desktop\ntabs front-to-back one way and back-to-front the other, with widgets\nreachable only travelling backwards. Deferred to the end of any in-flight\npointer gesture — moving a node clears the pointer capture a title-bar\ndrag or grow-box resize holds on it, and a background window must stay\ndraggable in the same gesture that raises it."
2516
+ "description": "Bring a slotted window to the front of its tier. A document-tier window\nalso becomes the single active window (clearing `active` on the other\ndocument windows); a utility window restacks within the floating tier\nand leaves every `active` state alone.\n\nThe light-DOM order of the slotted windows follows (_syncDomOrder):\nvisual stacking and sequential focus order come from independent channels\n(z-index vs DOM position), and letting them drift apart is how a desktop\ntabs front-to-back one way and back-to-front the other, with widgets\nreachable only travelling backwards. The sync runs in a task, and not\nbefore any in-flight pointer gesture has ended — moving a node clears the\npointer capture a title-bar drag or grow-box resize holds on it (a\nbackground window must stay draggable in the same gesture that raises\nit), and a node moved between a press's release and its `click` costs a\ncontrol in the raised window that click (see _requestDomSync)."
2507
2517
  },
2508
2518
  {
2509
2519
  "kind": "field",
@@ -2552,7 +2562,7 @@
2552
2562
  "text": "void"
2553
2563
  }
2554
2564
  },
2555
- "description": "Sync the DOM order now, or at gesture end if a pointer is down."
2565
+ "description": "Schedule the DOM-order sync: one task from now, coalesced, and never\nwhile a pointer is down — the gesture's end schedules it instead.\n\nA task rather than the synchronous move this used to be, because the\nbrowser dispatches a release's pointerup, mouseup and click in ONE task,\nand Chromium drops a click whose mousedown node left the tree before the\nclick was dispatched — and re-inserting a window is a removal. Synced at\npointerup, a raise moved the window between the press and its click, so\na control in a background window (a checkbox in a palette under another\npalette) got its press and release but never its click: it looked\npressed and never acted. Not a microtask either — that runs between the\nlisteners of the same dispatch, still ahead of the click. A task runs\nafter the whole chain, the shape `deferActivation` (src/events.ts) falls\nback on for the same reason."
2556
2566
  },
2557
2567
  {
2558
2568
  "kind": "method",
@@ -2571,7 +2581,7 @@
2571
2581
  }
2572
2582
  }
2573
2583
  ],
2574
- "description": "Raise the window an event originated in, skipping the restack/activation\nchurn when it is already on top of its own tier (and, for a document\nwindow, already active): otherwise every click inside the front window\nwould bump _zCounter and re-run the whole-fleet activation loop for\nnothing.\n\nRestacks z/active only — deliberately no DOM sync. A focus-driven raise\nMUST NOT move nodes: moving the window focus just entered re-orders the\nsequence mid-traversal, and a Shift+Tab that raises each window it\nenters (pushing it forward in the DOM, back the way the traversal came)\nwould revisit it forever. The pointer path syncs at gesture end instead,\nwhich is also the next safe point after any keyboard-session staleness."
2584
+ "description": "Raise the window an event originated in, skipping the restack/activation\nchurn when it is already on top of its own tier (and, for a document\nwindow, already active): otherwise every click inside the front window\nwould bump _zCounter and re-run the whole-fleet activation loop for\nnothing.\n\nRestacks z/active only — deliberately no DOM sync. A focus-driven raise\nMUST NOT move nodes: moving the window focus just entered re-orders the\nsequence mid-traversal, and a Shift+Tab that raises each window it\nenters (pushing it forward in the DOM, back the way the traversal came)\nwould revisit it forever. The pointer path schedules the sync at gesture\nend instead, which is also the next safe point after any keyboard-session\nstaleness."
2575
2585
  },
2576
2586
  {
2577
2587
  "kind": "field",
@@ -2583,7 +2593,7 @@
2583
2593
  "kind": "field",
2584
2594
  "name": "_onGestureEnd",
2585
2595
  "privacy": "private",
2586
- "description": "The press ended (or was cancelled) — sync the DOM order. Unconditional\nrather than only-if-raised: the sync no-ops when order already agrees,\nand running it at every gesture end is what heals the staleness a\nkeyboard-only stretch leaves behind (focus-driven raises change z but\nnever move nodes — see _raise)."
2596
+ "description": "The press ended (or was cancelled) — schedule the DOM-order sync.\nUnconditional rather than only-if-raised: the sync no-ops when order\nalready agrees, and running it after every gesture is what heals the\nstaleness a keyboard-only stretch leaves behind (focus-driven raises\nchange z but never move nodes — see _raise)."
2587
2597
  },
2588
2598
  {
2589
2599
  "kind": "field",
@@ -2715,7 +2725,7 @@
2715
2725
  "text": "void"
2716
2726
  }
2717
2727
  },
2718
- "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.\nOnly runs between pointer gestures or programmatically, never from a\nfocus-driven raise (see _raise for why)."
2728
+ "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)."
2719
2729
  },
2720
2730
  {
2721
2731
  "kind": "field",
@@ -10746,7 +10756,7 @@
10746
10756
  "declarations": [
10747
10757
  {
10748
10758
  "kind": "class",
10749
- "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.\n\nSize the host (width/height) from the outside; the viewport fills it.",
10759
+ "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. The\nviewport insets its content 8px; flush drops that inset so content\nruns to the frame and the rails.",
10750
10760
  "name": "VfScrollArea",
10751
10761
  "cssProperties": [
10752
10762
  {
@@ -10797,6 +10807,28 @@
10797
10807
  "attribute": "axis",
10798
10808
  "reflects": true
10799
10809
  },
10810
+ {
10811
+ "kind": "field",
10812
+ "name": "flush",
10813
+ "type": {
10814
+ "text": "boolean"
10815
+ },
10816
+ "default": "false",
10817
+ "description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
10818
+ "attribute": "flush",
10819
+ "reflects": true
10820
+ },
10821
+ {
10822
+ "kind": "field",
10823
+ "name": "corner",
10824
+ "type": {
10825
+ "text": "boolean"
10826
+ },
10827
+ "default": "false",
10828
+ "description": "Reserve the bottom-right corner cell on a single-axis rail: the rail\nstops 15px short of the frame and the viewport spans the rest, leaving\nthe 15×15 cell — with the two dividers a `both` rail's corner carries —\nfor a grow box to land in. A `both` rail always has the cell, so the\nflag changes nothing there. `vf-window[scrollbars resizable]` sets it.",
10829
+ "attribute": "corner",
10830
+ "reflects": true
10831
+ },
10800
10832
  {
10801
10833
  "kind": "field",
10802
10834
  "name": "label",
@@ -10905,6 +10937,24 @@
10905
10937
  "description": "Which scroll rails to reserve as permanent placeholders: `vertical`\n(default), `horizontal`, or `both`. Each reserved rail shows an empty white\nchannel until its axis overflows; the unreserved axis still scrolls\nnatively (wheel, keyboard) but draws no rail.",
10906
10938
  "fieldName": "axis"
10907
10939
  },
10940
+ {
10941
+ "name": "flush",
10942
+ "type": {
10943
+ "text": "boolean"
10944
+ },
10945
+ "default": "false",
10946
+ "description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
10947
+ "fieldName": "flush"
10948
+ },
10949
+ {
10950
+ "name": "corner",
10951
+ "type": {
10952
+ "text": "boolean"
10953
+ },
10954
+ "default": "false",
10955
+ "description": "Reserve the bottom-right corner cell on a single-axis rail: the rail\nstops 15px short of the frame and the viewport spans the rest, leaving\nthe 15×15 cell — with the two dividers a `both` rail's corner carries —\nfor a grow box to land in. A `both` rail always has the cell, so the\nflag changes nothing there. `vf-window[scrollbars resizable]` sets it.",
10956
+ "fieldName": "corner"
10957
+ },
10908
10958
  {
10909
10959
  "name": "label",
10910
10960
  "type": {
@@ -17246,7 +17296,7 @@
17246
17296
  "text": "boolean"
17247
17297
  },
17248
17298
  "default": "false",
17249
- "description": "Remove the default 12px body padding.",
17299
+ "description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
17250
17300
  "attribute": "flush",
17251
17301
  "reflects": true
17252
17302
  },
@@ -17256,7 +17306,7 @@
17256
17306
  "type": {
17257
17307
  "text": "'vertical' | 'horizontal' | 'both' | undefined"
17258
17308
  },
17259
- "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell. Values mirror `vf-scroll-area`'s `axis`; the `heading` names the\nscroll region; the viewport part is re-exported. Implies `flush` (the\nviewport carries its own padding). The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
17309
+ "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nThe body's own padding goes; the viewport keeps its 8px inset unless\n`flush`, which passes through. The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
17260
17310
  "attribute": "scrollbars",
17261
17311
  "reflects": true
17262
17312
  },
@@ -17626,7 +17676,7 @@
17626
17676
  "text": "boolean"
17627
17677
  },
17628
17678
  "default": "false",
17629
- "description": "Remove the default 12px body padding.",
17679
+ "description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
17630
17680
  "fieldName": "flush"
17631
17681
  },
17632
17682
  {
@@ -17634,7 +17684,7 @@
17634
17684
  "type": {
17635
17685
  "text": "'vertical' | 'horizontal' | 'both' | undefined"
17636
17686
  },
17637
- "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell. Values mirror `vf-scroll-area`'s `axis`; the `heading` names the\nscroll region; the viewport part is re-exported. Implies `flush` (the\nviewport carries its own padding). The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
17687
+ "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nThe body's own padding goes; the viewport keeps its 8px inset unless\n`flush`, which passes through. The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
17638
17688
  "fieldName": "scrollbars"
17639
17689
  },
17640
17690
  {
@@ -9,9 +9,9 @@ declare const VfDesktop_base: (new (...args: any[]) => import("../position.js").
9
9
  * `vf-window` children: a `pointerdown` or
10
10
  * `focusin` (keyboard focus) anywhere inside a window brings it to the front
11
11
  * and makes it the single active window. The windows' light-DOM order is kept
12
- * in step with the stacking order (bottom-most first, at pointer-gesture
13
- * ends), so tabbing walks the stack the way the eye does and Shift+Tab is
14
- * its exact mirror.
12
+ * in step with the stacking order (bottom-most first, synced in a task once
13
+ * any pointer gesture has ended), so tabbing walks the stack the way the eye
14
+ * does and Shift+Tab is its exact mirror.
15
15
  *
16
16
  * Utility windows (`vf-window[variant="utility"]`) stack in a floating tier
17
17
  * above every document-tier window, restack only among themselves, and stand
@@ -183,6 +183,8 @@ export declare class VfDesktop extends VfDesktop_base {
183
183
  private _pointerGesture;
184
184
  /** Whether {@link _syncDomOrder} is putting focus back after a node move. */
185
185
  private _restoringFocus;
186
+ /** The pending DOM-order sync task, or 0 (see {@link _requestDomSync}). */
187
+ private _domSyncTimer;
186
188
  /**
187
189
  * Ends the pointer-gesture window that defers DOM reordering. Capture-phase
188
190
  * on the document so a component's `stopPropagation` can't strand the flag,
@@ -206,10 +208,12 @@ export declare class VfDesktop extends VfDesktop_base {
206
208
  * visual stacking and sequential focus order come from independent channels
207
209
  * (z-index vs DOM position), and letting them drift apart is how a desktop
208
210
  * tabs front-to-back one way and back-to-front the other, with widgets
209
- * reachable only travelling backwards. Deferred to the end of any in-flight
210
- * pointer gesture — moving a node clears the pointer capture a title-bar
211
- * drag or grow-box resize holds on it, and a background window must stay
212
- * draggable in the same gesture that raises it.
211
+ * reachable only travelling backwards. The sync runs in a task, and not
212
+ * before any in-flight pointer gesture has ended — moving a node clears the
213
+ * pointer capture a title-bar drag or grow-box resize holds on it (a
214
+ * background window must stay draggable in the same gesture that raises
215
+ * it), and a node moved between a press's release and its `click` costs a
216
+ * control in the raised window that click (see {@link _requestDomSync}).
213
217
  */
214
218
  bringToFront(win: HTMLElement): void;
215
219
  /** The active document-tier window, or null while the tier is deactivated
@@ -226,7 +230,22 @@ export declare class VfDesktop extends VfDesktop_base {
226
230
  clearActive(): void;
227
231
  /** The z/active half of a raise, shared by every path. */
228
232
  private _restack;
229
- /** Sync the DOM order now, or at gesture end if a pointer is down. */
233
+ /**
234
+ * Schedule the DOM-order sync: one task from now, coalesced, and never
235
+ * while a pointer is down — the gesture's end schedules it instead.
236
+ *
237
+ * A task rather than the synchronous move this used to be, because the
238
+ * browser dispatches a release's pointerup, mouseup and click in ONE task,
239
+ * and Chromium drops a click whose mousedown node left the tree before the
240
+ * click was dispatched — and re-inserting a window is a removal. Synced at
241
+ * pointerup, a raise moved the window between the press and its click, so
242
+ * a control in a background window (a checkbox in a palette under another
243
+ * palette) got its press and release but never its click: it looked
244
+ * pressed and never acted. Not a microtask either — that runs between the
245
+ * listeners of the same dispatch, still ahead of the click. A task runs
246
+ * after the whole chain, the shape `deferActivation` (src/events.ts) falls
247
+ * back on for the same reason.
248
+ */
230
249
  private _requestDomSync;
231
250
  /**
232
251
  * Raise the window an event originated in, skipping the restack/activation
@@ -239,8 +258,9 @@ export declare class VfDesktop extends VfDesktop_base {
239
258
  * MUST NOT move nodes: moving the window focus just entered re-orders the
240
259
  * sequence mid-traversal, and a Shift+Tab that raises each window it
241
260
  * enters (pushing it forward in the DOM, back the way the traversal came)
242
- * would revisit it forever. The pointer path syncs at gesture end instead,
243
- * which is also the next safe point after any keyboard-session staleness.
261
+ * would revisit it forever. The pointer path schedules the sync at gesture
262
+ * end instead, which is also the next safe point after any keyboard-session
263
+ * staleness.
244
264
  */
245
265
  private _raise;
246
266
  /**
@@ -251,11 +271,11 @@ export declare class VfDesktop extends VfDesktop_base {
251
271
  */
252
272
  private _onPointerDown;
253
273
  /**
254
- * The press ended (or was cancelled) — sync the DOM order. Unconditional
255
- * rather than only-if-raised: the sync no-ops when order already agrees,
256
- * and running it at every gesture end is what heals the staleness a
257
- * keyboard-only stretch leaves behind (focus-driven raises change z but
258
- * never move nodes — see {@link _raise}).
274
+ * The press ended (or was cancelled) — schedule the DOM-order sync.
275
+ * Unconditional rather than only-if-raised: the sync no-ops when order
276
+ * already agrees, and running it after every gesture is what heals the
277
+ * staleness a keyboard-only stretch leaves behind (focus-driven raises
278
+ * change z but never move nodes — see {@link _raise}).
259
279
  */
260
280
  private _onGestureEnd;
261
281
  /**
@@ -311,8 +331,9 @@ export declare class VfDesktop extends VfDesktop_base {
311
331
  * element drops focus to `<body>`, so it is restored afterwards — behind
312
332
  * `_restoringFocus`, because the restore re-fires focusin (see
313
333
  * {@link _onFocusIn}) on an element that may sit in a background window.
314
- * Only runs between pointer gestures or programmatically, never from a
315
- * focus-driven raise (see {@link _raise} for why).
334
+ * Reached only through {@link _requestDomSync}'s task after a pointer
335
+ * gesture or a programmatic raise, never from a focus-driven one (see
336
+ * {@link _raise} for why).
316
337
  */
317
338
  private _syncDomOrder;
318
339
  protected willUpdate(changed: PropertyValues<this>): void;
@@ -23,7 +23,7 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
23
23
  width: this.width ?? M,
24
24
  height: this.height ?? N
25
25
  })
26
- }), this._zCounter = 0, this._activeWindow = null, this._deactivated = !1, this._awaitingUpgrade = !1, this._pointerGesture = !1, this._restoringFocus = !1, this._gestureEnd = new b(this, () => [[
26
+ }), this._zCounter = 0, this._activeWindow = null, this._deactivated = !1, this._awaitingUpgrade = !1, this._pointerGesture = !1, this._restoringFocus = !1, this._domSyncTimer = 0, this._gestureEnd = new b(this, () => [[
27
27
  document,
28
28
  "pointerup",
29
29
  this._onGestureEnd,
@@ -38,7 +38,7 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
38
38
  let t = this._windowFromEvent(e);
39
39
  t && this._raise(t);
40
40
  }, this._onGestureEnd = () => {
41
- this._gestureEnd.detach(), this._pointerGesture = !1, this._syncDomOrder();
41
+ this._gestureEnd.detach(), this._pointerGesture = !1, this._requestDomSync();
42
42
  }, this._onFocusIn = (e) => {
43
43
  if (this._restoringFocus) return;
44
44
  let t = this._windowFromEvent(e);
@@ -185,7 +185,7 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
185
185
  super.connectedCallback(), this.addEventListener("pointerdown", this._onPointerDown), this.addEventListener("focusin", this._onFocusIn);
186
186
  }
187
187
  disconnectedCallback() {
188
- this.removeEventListener("pointerdown", this._onPointerDown), this.removeEventListener("focusin", this._onFocusIn), this._pointerGesture = !1, super.disconnectedCallback();
188
+ this.removeEventListener("pointerdown", this._onPointerDown), this.removeEventListener("focusin", this._onFocusIn), this._pointerGesture = !1, this._domSyncTimer &&= (clearTimeout(this._domSyncTimer), 0), super.disconnectedCallback();
189
189
  }
190
190
  _isUtility(e) {
191
191
  return e.getAttribute("variant") === "utility";
@@ -204,7 +204,9 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
204
204
  e.style.zIndex = String(++this._zCounter + (t ? A : 0)), t || this._setActive(e);
205
205
  }
206
206
  _requestDomSync() {
207
- this._pointerGesture || this._syncDomOrder();
207
+ this._pointerGesture || this._domSyncTimer || (this._domSyncTimer = window.setTimeout(() => {
208
+ this._domSyncTimer = 0, this._pointerGesture || this._syncDomOrder();
209
+ }, 0));
208
210
  }
209
211
  _raise(e) {
210
212
  let t = this._isUtility(e), n = this._windows.filter((e) => this._isUtility(e) === t);
@@ -18,9 +18,13 @@ declare const VfScrollArea_base: (new (...args: any[]) => import("../position.js
18
18
  * dither and thumb filling in only once that axis overflows (System 7 drew
19
19
  * an active window's no-overflow bar as arrows on a bare channel; driven
20
20
  * by {@link ScrollStateController}). Which rails are reserved is set by
21
- * {@link axis}; when both are reserved the bottom-right corner joins them.
21
+ * {@link axis}; when both are reserved the bottom-right corner joins them,
22
+ * and {@link corner} reserves that cell on a single-axis rail too — the
23
+ * rail stops 15px short of the frame, for a grow box to land in.
22
24
  *
23
- * Size the host (width/height) from the outside; the viewport fills it.
25
+ * Size the host (width/height) from the outside; the viewport fills it. The
26
+ * viewport insets its content 8px; {@link flush} drops that inset so content
27
+ * runs to the frame and the rails.
24
28
  *
25
29
  * @slot - Scrollable content.
26
30
  * @csspart viewport - The inner scrolling container.
@@ -39,6 +43,21 @@ export declare class VfScrollArea extends VfScrollArea_base {
39
43
  * natively (wheel, keyboard) but draws no rail.
40
44
  */
41
45
  axis: 'vertical' | 'horizontal' | 'both';
46
+ /**
47
+ * Drop the viewport's 8px inset: slotted content and the (0,0) of placed
48
+ * children sit at the frame's inner edge, one system px from the frame
49
+ * box (the border-floor compensation stays — see the viewport rule).
50
+ * `vf-window[scrollbars]` forwards its own `flush` here.
51
+ */
52
+ flush: boolean;
53
+ /**
54
+ * Reserve the bottom-right corner cell on a single-axis rail: the rail
55
+ * stops 15px short of the frame and the viewport spans the rest, leaving
56
+ * the 15×15 cell — with the two dividers a `both` rail's corner carries —
57
+ * for a grow box to land in. A `both` rail always has the cell, so the
58
+ * flag changes nothing there. `vf-window[scrollbars resizable]` sets it.
59
+ */
60
+ corner: boolean;
42
61
  /**
43
62
  * Accessible name for the scrolling viewport, applied as its `aria-label`
44
63
  * (an `aria-label` on the host would not reach into the shadow DOM). The
@@ -13,7 +13,7 @@ import { property as h, query as g, state as _ } from "lit/decorators.js";
13
13
  //#region src/components/vf-scroll-area.ts
14
14
  var v = class extends r(d) {
15
15
  constructor(...e) {
16
- super(...e), this.scale = new t(this), this.gridSnap = new s(this), this.axis = "vertical", this.label = "", this.scrollState = new c(this, () => this.viewport, () => this.content, (e) => {
16
+ super(...e), this.scale = new t(this), this.gridSnap = new s(this), this.axis = "vertical", this.flush = !1, this.corner = !1, this.label = "", this.scrollState = new c(this, () => this.viewport, () => this.content, (e) => {
17
17
  this._scrollable = e.x || e.y;
18
18
  }), this.rail = new l(this, {
19
19
  getScroll: () => this.viewport,
@@ -46,11 +46,13 @@ var v = class extends r(d) {
46
46
  }
47
47
  :host(:not([axis])) .box,
48
48
  :host([axis='vertical']) .box,
49
- :host([axis='both']) .box {
49
+ :host([axis='both']) .box,
50
+ :host([corner]) .box {
50
51
  grid-template-columns: 1fr auto;
51
52
  }
52
53
  :host([axis='horizontal']) .box,
53
- :host([axis='both']) .box {
54
+ :host([axis='both']) .box,
55
+ :host([corner]) .box {
54
56
  grid-template-rows: 1fr auto;
55
57
  }
56
58
  .viewport {
@@ -73,6 +75,23 @@ var v = class extends r(d) {
73
75
  var(--vf-scale, 1) * 8px + mod(var(--vf-scale, 1) * 1px, 1px)
74
76
  );
75
77
  }
78
+ /* flush: the inset goes, the border-floor term stays — content and the
79
+ (0,0) of placed children sit at the frame's inner edge, exactly one
80
+ system px from the frame box at every scale. */
81
+ :host([flush]) .viewport {
82
+ padding: mod(var(--vf-scale, 1) * 1px, 1px);
83
+ }
84
+ /* A single-axis rail with a reserved corner: the viewport spans the
85
+ track the missing rail would have taken, up to the corner cell, so
86
+ the rail stops 15px short of the frame. (A both-axes rail has no
87
+ missing track — the flag is inert there.) */
88
+ :host([corner][axis='horizontal']) .viewport {
89
+ grid-column: 1 / 3;
90
+ }
91
+ :host([corner]:not([axis])) .viewport,
92
+ :host([corner][axis='vertical']) .viewport {
93
+ grid-row: 1 / 3;
94
+ }
76
95
  .vf-rail--vertical {
77
96
  grid-area: 1 / 2;
78
97
  }
@@ -100,7 +119,7 @@ var v = class extends r(d) {
100
119
  ];
101
120
  }
102
121
  render() {
103
- let e = this.axis !== "horizontal", t = this.axis === "horizontal" || this.axis === "both";
122
+ let e = this.axis !== "horizontal", t = this.axis === "horizontal" || this.axis === "both", n = e && t || this.corner;
104
123
  return p`
105
124
  <div class="box vf-snap">
106
125
  <div
@@ -114,11 +133,17 @@ var v = class extends r(d) {
114
133
  </div>
115
134
  ${e ? u(this.rail, "vertical") : m}
116
135
  ${t ? u(this.rail, "horizontal") : m}
117
- ${e && t ? p`<div class="vf-rail-corner" aria-hidden="true"></div>` : m}
136
+ ${n ? p`<div class="vf-rail-corner" aria-hidden="true"></div>` : m}
118
137
  </div>
119
138
  `;
120
139
  }
121
140
  };
122
- n([h({ reflect: !0 })], v.prototype, "axis", void 0), n([h()], v.prototype, "label", void 0), n([g(".viewport")], v.prototype, "viewport", void 0), n([g(".content")], v.prototype, "content", void 0), n([_()], v.prototype, "_scrollable", void 0), v = n([e("vf-scroll-area")], v);
141
+ n([h({ reflect: !0 })], v.prototype, "axis", void 0), n([h({
142
+ type: Boolean,
143
+ reflect: !0
144
+ })], v.prototype, "flush", void 0), n([h({
145
+ type: Boolean,
146
+ reflect: !0
147
+ })], v.prototype, "corner", void 0), n([h()], v.prototype, "label", void 0), n([g(".viewport")], v.prototype, "viewport", void 0), n([g(".content")], v.prototype, "content", void 0), n([_()], v.prototype, "_scrollable", void 0), v = n([e("vf-scroll-area")], v);
123
148
  //#endregion
124
149
  export { v as VfScrollArea };
@@ -94,16 +94,24 @@ export declare class VfWindow extends VfWindow_base {
94
94
  movable: boolean;
95
95
  /** Show a grow box at the bottom-right corner for resizing. */
96
96
  resizable: boolean;
97
- /** Remove the default 12px body padding. */
97
+ /**
98
+ * Remove the default 12px body padding. Under `scrollbars` the body has
99
+ * none to remove, so the flag reaches the built-in scroll area instead
100
+ * and drops its viewport's 8px inset: content runs to the frame and the
101
+ * rails, and the (0,0) of placed children is the content region's corner.
102
+ */
98
103
  flush: boolean;
99
104
  /**
100
105
  * Put System 7 scroll rails on the window edge — the classic document
101
106
  * window. The body slot renders inside a built-in `vf-scroll-area` pulled
102
107
  * one system pixel under the frame on every side, so the rails repaint the
103
108
  * border lines and a `resizable` window's grow box lands in the corner
104
- * cell. Values mirror `vf-scroll-area`'s `axis`; the `heading` names the
105
- * scroll region; the viewport part is re-exported. Implies `flush` (the
106
- * viewport carries its own padding). The slotted composition (SPEC §5
109
+ * cell reserved on a single-axis rail too (the area's `corner`), unless
110
+ * a populated status strip holds the grow box, when the rail runs edge to
111
+ * edge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the
112
+ * `heading` names the scroll region; the viewport part is re-exported.
113
+ * The body's own padding goes; the viewport keeps its 8px inset unless
114
+ * `flush`, which passes through. The slotted composition (SPEC §5
107
115
  * vf-scroll-area) still works for windows that want an inset well instead.
108
116
  */
109
117
  scrollbars?: 'vertical' | 'horizontal' | 'both';
@@ -201,7 +201,9 @@ var z = 80, B = 54, V = 24, H = 8, U = class extends E(c(N)) {
201
201
  frame on every side, so its own frame border repaints the window's
202
202
  border lines exactly (no doubled frame), the rails run edge to edge,
203
203
  and a resizable window's grow box (z-index 1) lands over the rail
204
- corner cell. */
204
+ corner cell — which the render reserves on a single-axis rail too
205
+ (the area's corner flag), except when the status strip holds the
206
+ grow box instead. */
205
207
  .edge-scroll {
206
208
  width: calc(100% + var(--vf-scale, 1) * 2px);
207
209
  height: calc(100% + var(--vf-scale, 1) * 2px);
@@ -380,6 +382,8 @@ var z = 80, B = 54, V = 24, H = 8, U = class extends E(c(N)) {
380
382
  <vf-scroll-area
381
383
  class="edge-scroll"
382
384
  axis=${this.scrollbars}
385
+ ?flush=${this.flush}
386
+ ?corner=${this.resizable && !this._hasStatus}
383
387
  label=${this.heading || I}
384
388
  exportparts="viewport"
385
389
  >
@@ -150,11 +150,17 @@ var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u
150
150
  width: calc(var(--vf-scale, 1) * 16px);
151
151
  }
152
152
 
153
- /* The both-axes corner cell. It supplies the two interior dividers
153
+ /* The corner cell: the rails' own 15px in both dimensions, so it sizes its
154
+ grid track even when only one rail is there to size it (vf-scroll-area's
155
+ corner flag on a single-axis rail). It supplies the two interior dividers
154
156
  (against the vertical increment arrow above and the horizontal one
155
- beside it) that those buttons leave to it; a resizable window's grow box
156
- lands exactly over it. */
157
+ beside it) that those buttons leave to it — on a single-axis rail the
158
+ other divider continues the rail's line across the cell, the way
159
+ DrawGrowIcon drew it; a resizable window's grow box lands exactly over
160
+ it. */
157
161
  .vf-rail-corner {
162
+ width: calc(var(--vf-scale, 1) * 15px);
163
+ height: calc(var(--vf-scale, 1) * 15px);
158
164
  background: var(--vf-white, #fff);
159
165
  border-top: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
160
166
  border-left: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
package/docs/SPEC.md CHANGED
@@ -199,7 +199,7 @@ Full-bleed classic desktop container.
199
199
  - **Visual:** `display: block; position: relative;` — the paint lives on an inner screen surface (part `desktop`, `overflow: hidden` — the whole-system-px raster, inset by `bezel` when one is set). Screen surface = the `pattern` (the classic 50% dither, `gray-50`, by default), painted as the screen's own background by the pattern fill (*Patterns* under *Tiled fills*): black ink on the fill's opaque `--vf-white` paper, one whole-surface raster — the authentic black-on-white dither. A consumer `--vf-desktop-pattern` renders instead as a placed tile grid on the token's 30-system-px tile with no paper, so `var(--vf-desktop, #808080)` beneath shows only under a custom tile with transparent cells (or `none`). Forced colors: flat Canvas.
200
200
  - **Slots:** default (menu bar, windows, anything).
201
201
  - **Behavior:** manages stacking of slotted `vf-window` children: `pointerdown` *or `focusin`* on a window brings it to front (incrementing z-index counter) and sets its `active` attribute, clearing `active` on the others — the `focusin` half is the keyboard route to activation: Tab landing anywhere in a background window (its undrawn-but-focusable widgets included) raises it. Listens via delegated pointerdown/focusin listeners + `slotchange`. Windows slotted before `vf-window` is defined are re-normalized once `customElements.whenDefined('vf-window')` settles, since the upgrade reflects each window's `active = true` default back out and upgrading a slotted node doesn't re-fire `slotchange`.
202
- - **DOM order follows z-order** (bottom-most first), so sequential focus order matches the visual stack and Shift+Tab mirrors Tab exactly. The sync runs at pointer-gesture end (a mid-gesture node move would clear the pointer capture a title-bar drag or grow-box resize holds) and on programmatic `bringToFront`, never from a focus-driven raise (moving the window focus just entered would re-order the tab sequence mid-traversal); focus surviving its own window's move is restored without re-raising that window. Non-window children (a menu bar, page content) keep their positions. `npm run verify:window-a11y`.
202
+ - **DOM order follows z-order** (bottom-most first), so sequential focus order matches the visual stack and Shift+Tab mirrors Tab exactly. The sync runs in a task after a pointer gesture ends, or after a programmatic `bringToFront` — never mid-gesture (a node move would clear the pointer capture a title-bar drag or grow-box resize holds), never between a press's release and its `click` (Chromium drops a click whose mousedown node was re-inserted first, which used to cost a control in a background window its click), and never from a focus-driven raise (moving the window focus just entered would re-order the tab sequence mid-traversal); focus surviving its own window's move is restored without re-raising that window. Non-window children (a menu bar, page content) keep their positions. `npm run verify:window-a11y`.
203
203
  - **Floating tier:** `vf-window[variant="utility"]` children stack in a z band `1_000_000` above the document tier (one shared monotonic counter, so a palette stays above every document window), restack only among themselves, and stand outside the single-active invariant both ways: clicking a palette doesn't deactivate the active document window, and activating a document window never clears a palette's `active` — System 7 windoid behavior while the app is frontmost. The tier test reads the `variant` *attribute*, so a not-yet-upgraded element still lands right.
204
204
  - **Menu tier:** a slotted `vf-menu-bar` — or a free-standing `vf-menu` placed on the desktop — is restated at `z-index: 2_000_000` by the desktop's own `::slotted(vf-menu-bar), ::slotted(vf-menu)` rule (an outer-tree `::slotted` declaration beats the bar's inner `:host { z-index: 1000 }`), a band above the floating tier, so a dropped menu paints over palettes and document windows alike — the Menu Manager drew menus over every window. Only the screen-corner mask is in front. The full stack inside the screen's isolated context: consumer tile grid (−1) < document windows (counter) < utility windows (counter + 1_000_000) < menu tier (2_000_000) < corner mask (max). A popup *inside* a window (`vf-select`'s fixed panel) is a different case: it stacks within its window's context, so a palette can still cover it — KNOWN-BUGS.md. `npm run verify:archetypes` (MENU TIER).
205
205
  - **Deactivation:** on a real System 7 machine clicking the desktop clicked the *Finder* — the frontmost application's windows lost their stripes. `clearActive()` is that gesture's handler: it clears `active` from the whole document tier, and **zero active windows is a legal state**, held until a press or `focusin` re-enters a document window or a new one is slotted (opening a window brings its application forward — a newly slotted window ends a deliberate deactivation, where a mere survivor never does: removing a *background* window while deactivated promotes nothing, while removing the holder outside a deactivation promotes the topmost survivor). The desktop never takes the decision itself — a press on its own bare dither changes nothing; desktop furniture is slotted light DOM, so only the page knows which of its children mean "the Finder". Utility windows keep their `active` through a deactivation (their dots stay drawn). Every change of holder — window to window, window to none, none to window — fires `vf-activate`, once per change; re-asserting the current holder is silent. `npm run verify:desktop-activate`.
@@ -208,7 +208,7 @@ Full-bleed classic desktop container.
208
208
 
209
209
  #### `vf-window` (`VfWindow`, vf-window.ts)
210
210
  The desktop-window shell: the classic document window (see DragThing screenshot), parameterized down to the windoid (see the Group A recipe table).
211
- - **Attributes/props:** `heading: string` (title text), `width: number` / `height: number` (**declare them both** — whole system px, so the window keeps its proportion to the chrome inside it at every density. A window is a fixed box in both axes, the way a WIND resource was: one that grows with its body is one the user can neither predict nor control via the grow box. Each missing dimension falls back to something different — width to block layout, height to the content — and the window names whichever are missing, once, in the console), `active: boolean` (default **true**; reflect), `closable: boolean` (default true), `zoomable: boolean` (default false), `movable: boolean` (default false), `resizable: boolean` (default false), `flush: boolean` (default false — removes body padding), `variant?: 'utility'` (the slim windoid chrome), `scrollbars?: 'vertical' | 'horizontal' | 'both'` (edge scroll rails).
211
+ - **Attributes/props:** `heading: string` (title text), `width: number` / `height: number` (**declare them both** — whole system px, so the window keeps its proportion to the chrome inside it at every density. A window is a fixed box in both axes, the way a WIND resource was: one that grows with its body is one the user can neither predict nor control via the grow box. Each missing dimension falls back to something different — width to block layout, height to the content — and the window names whichever are missing, once, in the console), `active: boolean` (default **true**; reflect), `closable: boolean` (default true), `zoomable: boolean` (default false), `movable: boolean` (default false), `resizable: boolean` (default false), `flush: boolean` (default false — removes the body padding; under `scrollbars`, the built-in viewport's 8px inset instead), `variant?: 'utility'` (the slim windoid chrome), `scrollbars?: 'vertical' | 'horizontal' | 'both'` (edge scroll rails).
212
212
  - **Visual:** `vfChromeFrame` + `vfTitleBar` (§4), plus a full-size flex-column layout on the frame. `display: block`. Sets `--vf-surface: var(--vf-white, #fff)` on itself.
213
213
  - Title bar: from `vfTitleBar` — height `var(--vf-titlebar-height, 18px)`, white bg, bottom `1px solid black`, contains `.vf-stripes` layer (only when `active`). `touch-action: none` only when `[movable]`.
214
214
  - Title: centered, bold, on a white patch (`padding: 0 6px`; cap band on interior rows 4..12, 7px of white between ink and stripes — see §4 `vfTitleBar` for the traced geometry and the lattice hold) above the stripes, with `--vf-title-inset: 60px` of clearance so it ellipsizes before reaching the widgets. Inactive: no stripes, widgets undrawn (transparent ink — they keep their tab stops; see A11y below), the grow box's nested squares hidden, and every managed scroll rail inside the window blanked (see "always-a-rail" §5 vf-scroll-area) — but the title text stays black (System 7 never grayed the window title).
@@ -217,7 +217,7 @@ The desktop-window shell: the classic document window (see DragThing screenshot)
217
217
  - Body: `padding: 12px` (0 if `flush` or `scrollbars`), and `overflow: hidden` — the window being a fixed box, content taller than it is clipped at the frame the way the classic content region was, rather than painting out over the desktop; `scrollbars` is how the user reaches the rest. Two deliberate exemptions: `[scrollbars]` restores `overflow: visible`, because the edge-rail composition below pulls the scroll area one system px *outside* the body on every side and clipping would shave exactly that overhang off (the scroll area does its own clipping); and a control's drop-open panel is not clipped — `vf-select`'s list is `position: fixed` off the control's rect precisely to escape clipping ancestors (§5 vf-select), and it still escapes, because nothing between it and the viewport establishes a containing block for fixed descendants (the grid-snap correction is a `position: relative` left/top offset, never a transform — see §7). A `vf-menu` panel is anchored `position: absolute` and *would* clip, but a menu bar belongs to the desktop, not inside a window body.
218
218
  - Grow box (if `resizable`): 15×15 at bottom-right corner, white bg, 1px black top/left borders, containing two overlapping small square outlines. Inactive: the cell and its borders stay, the nested squares go — System 7 drew a deactivated window's size box hollow, with its blanked scroll rails.
219
219
  - Status bar (`status` slot): the classic bottom readout strip ("40px x 40px") — a 1px black rule (`vfRule`'s `.vf-rule-top`) over a white interior, `--vf-status-bar-height` (15px) in all, the grow box's own height, so a `resizable` window's grow box sits flush in the strip's right end (its top/left borders take over the rule there; the strip reserves 21px of right padding under `resizable` so text clears the cell). Slotted content rides the body face's native 12px line (`--vf-line-height`), whole-pixel centered in the 14px interior, `padding-inline: 6px`, nowrap + clipped. Takes **no space** until the slot is populated (the dialog-footer pattern), and collapses again when it empties. Composes with `scrollbars`: the edge rails' bottom overhang lands its frame line exactly on the strip's rule, so the two never double up — though the grow box then sits in the strip rather than the rail corner cell.
220
- - Edge scroll rails (if `scrollbars`): the body slot renders inside a shadow `vf-scroll-area` (its `axis` = the attribute's value, `label` = the heading, `viewport` part re-exported) carrying the TeachText composition internally — `calc(100% + 2px·scale)` with `margin: -1px·scale`, one system px under the frame on every side, so the area's own frame border repaints the window's border lines and a `resizable` window's grow box (z-index 1) lands exactly over the rail-corner cell. Same caveats as the slotted composition (see vf-scroll-area §5), which remains supported for inset wells.
220
+ - Edge scroll rails (if `scrollbars`): the body slot renders inside a shadow `vf-scroll-area` (its `axis` = the attribute's value, `label` = the heading, `viewport` part re-exported) carrying the TeachText composition internally — `calc(100% + 2px·scale)` with `margin: -1px·scale`, one system px under the frame on every side, so the area's own frame border repaints the window's border lines and a `resizable` window's grow box (z-index 1) lands exactly over the rail-corner cell. `flush` passes through to the area (its viewport drops the 8px inset, keeping the border-floor term, so content and the (0,0) of placed children sit at the content region's corner); a `resizable` window sets the area's `corner`, so a single-axis rail stops at the corner cell too — unless the status strip is populated, when the grow box sits in the strip and the rail runs edge to edge onto the strip's rule. Same caveats as the slotted composition (see vf-scroll-area §5), which remains supported for inset wells.
221
221
  - Utility variant (`variant="utility"`): the slim windoid bar traced from `Windows/utility-window.png` — `--vf-titlebar-height-utility` (12px = 11px interior + 1px rule), the `vfDots` dither instead of stripes (flush to the side borders — see §4 vfDots), 7×7 widgets (`top: 2px`; close `left: 7px`, zoom `right: 8px` — the art is asymmetric by that pixel) with a 2px patch ring where the striped bar's is 1px (`--vf-widget-ring`, internal geometry: the windoid sheet clears two px of dither beside its widgets), and the nested zoom square shrunk so its edges land at sprite col/row 3. No title patch: the display face's 16px line box can't sit in an 11px interior, so `.vf-title` is `display: none` under the variant (a retheming consumer can re-show it via `::part(title)`) and the heading names the widgets. A pressed windoid widget inverts whole — black interior under a white (invisible) borderline — rather than flashing the big bar's 9×9 sunburst, which can't land on a 5×5 interior.
222
222
  - **A11y:** the close/zoom `aria-label`s are qualified by the title when there is one (`Close ${heading}` / `Zoom ${heading}`, falling back to bare `Close` / `Zoom`) — several windows are open at once by design, so a bare repeated "Close" gives an AT user no way to tell which window a widget belongs to. The frame is `role="group"`, named by the title patch via `aria-labelledby` when there is a heading (the utility variant's hidden patch still names it — AccName resolves hidden labelledby targets) — `group` rather than `region` deliberately, so a desktop of windows doesn't pollute landmark navigation; the title bar is a `<div>`, never a `<header>`, which would map to an unnamed `banner` landmark even inside the shadow root. An inactive window's widgets stay in the tree and the tab order but paint no ink (transparent border/background/patch ring — the bare System 7 bar): a background window whose body holds nothing focusable is still reachable, activated by `vf-desktop`'s `focusin` raise the moment Tab lands on a widget, and never drops focus to `<body>` when it deactivates. `npm run verify:window-a11y`.
223
223
  - **Behavior:** close box click → `vf-close` (does NOT remove itself; consumer decides). Zoom box click → `vf-zoom`. If `movable`: dragging the title bar moves the window — the drag seeds its origin from the current offset position (once, converting to system px) and then states `left`/`top` via pointer capture, in whole system px like any authored placement. If `resizable`: dragging the grow box states `width`/`height` the same way, and fires `vf-resize` (detail `{ width, height, commit }`, sizes in whole system px) — one event per size the drag actually 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 (release or cancel), only when it changed the size. Window content can follow the grow box from this event alone, no ResizeObserver. Fired by the gesture only: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.
@@ -389,11 +389,11 @@ Classic list box.
389
389
 
390
390
  #### `vf-scroll-area` (`VfScrollArea`, vf-scroll-area.ts)
391
391
  A container whose scrollbars look like System 7.
392
- - **Attributes/props:** `axis: 'vertical' | 'horizontal' | 'both'` (default `'vertical'`, reflected) — which scroll rails to reserve as permanent placeholders (see "always-a-rail" below); `label: string` — accessible name for the viewport (`aria-label` on the viewport, since an `aria-label` on the host cannot reach into the shadow DOM). The viewport is a **Tab stop only while its content actually overflows** — the same state `ScrollStateController` measures for the rails; a fitting scroll area used to be a focusable stop with `role: generic` and no name, a dead Tab press. Whenever it is a stop it carries a role: `role="region"` when `label` names it (a named landmark), `role="group"` when not (an unnamed region is inert, so that role is reserved for the labelled case).
393
- - **Visual:** `display: block`; the snapped wrapper carries a real 1px black frame and a grid reserving each rail as its own edge column/row — `[viewport | vertical rail]` over `[horizontal rail | corner]` — with the white inner viewport padded `8px` plus the border-floor compensation (a `mod()` term restoring what engines floor off the fractional border-width, so slotted content and the (0,0) of placed children sit exactly 9 system px from the frame box at every scale). Consumer sets width/height on host. The rails are the drawn `vfScrollRail` subtree (§4 — the classic 16px cell counting the frame line: divider, 14px channel, 15px arrow cells, the fixed 16px thumb, the `tileRaster` dot-dither trough), rendered as later siblings of the viewport and synced to its native scrolling by `ScrollRailController`; the viewport carries the `vf-scroll` class, which hides the native bar without touching the native scrolling. When both rails are reserved, the corner cell joins them, supplying the interior dividers the adjacent arrow cells leave to it. Every engine renders the same rail; the old `::-webkit-scrollbar` skin, its `.vf-scroll-frame` overlay contortion (WebKit quantized native scrollbar rects to whole CSS px) and the Firefox `scrollbar-color` fallback are all retired.
392
+ - **Attributes/props:** `axis: 'vertical' | 'horizontal' | 'both'` (default `'vertical'`, reflected) — which scroll rails to reserve as permanent placeholders (see "always-a-rail" below); `flush: boolean` (default false, reflected) — drops the viewport's 8px inset, keeping the border-floor term, so content and the (0,0) of placed children sit at the frame's inner edge; `corner: boolean` (default false, reflected) — reserves the bottom-right corner cell on a single-axis rail (the rail stops 15px short of the frame, the viewport spans the rest), for a grow box to land in; inert on `both`, which always has the cell; `label: string` — accessible name for the viewport (`aria-label` on the viewport, since an `aria-label` on the host cannot reach into the shadow DOM). The viewport is a **Tab stop only while its content actually overflows** — the same state `ScrollStateController` measures for the rails; a fitting scroll area used to be a focusable stop with `role: generic` and no name, a dead Tab press. Whenever it is a stop it carries a role: `role="region"` when `label` names it (a named landmark), `role="group"` when not (an unnamed region is inert, so that role is reserved for the labelled case).
393
+ - **Visual:** `display: block`; the snapped wrapper carries a real 1px black frame and a grid reserving each rail as its own edge column/row — `[viewport | vertical rail]` over `[horizontal rail | corner]` — with the white inner viewport padded `8px` plus the border-floor compensation (a `mod()` term restoring what engines floor off the fractional border-width, so slotted content and the (0,0) of placed children sit exactly 9 system px from the frame box at every scale; `flush` keeps only the compensation, and they sit exactly 1). Consumer sets width/height on host. The rails are the drawn `vfScrollRail` subtree (§4 — the classic 16px cell counting the frame line: divider, 14px channel, 15px arrow cells, the fixed 16px thumb, the `tileRaster` dot-dither trough), rendered as later siblings of the viewport and synced to its native scrolling by `ScrollRailController`; the viewport carries the `vf-scroll` class, which hides the native bar without touching the native scrolling. When both rails are reserved, the corner cell joins them, supplying the interior dividers the adjacent arrow cells leave to it; `corner` reserves the same 15×15 cell beside a single rail, its other divider continuing the rail's line across the cell — DrawGrowIcon's form, for the grow box. Every engine renders the same rail; the old `::-webkit-scrollbar` skin, its `.vf-scroll-frame` overlay contortion (WebKit quantized native scrollbar rects to whole CSS px) and the Firefox `scrollbar-color` fallback are all retired.
394
394
  - **Always-a-rail behavior:** each *reserved* axis (per `axis`) renders its rail element as a permanent placeholder — arrow buttons on an empty white channel (dither off, no thumb; System 7 drew the arrows on any bar in an active window, and a scroller outside a window always counts as active) — until the content overflows that axis, when the dither and thumb fill in and the arrows go live (an idle axis's arrows are drawn but inert — the press guard in `ScrollRailController` skips them). `ScrollStateController` (`src/scroll-state.ts`) measures both axes and writes `data-overflow-x` / `data-overflow-y` (`"true"` / `"false"`) on the scroll element; the recipe keys the dither and thumb off those attributes. The unreserved axis still scrolls natively (wheel, keyboard) but draws no rail. Shared by vf-list and vf-text-area; a future `@container scroll-state(scrollable)` query could replace the JS for slotted-content components.
395
395
  - **Inactive-window blanking:** the HIG's non-frontmost window must not display interactive scroll UX, so the controller also finds the nearest `vf-window` up the composed tree (light-DOM ancestor for a slotted scroller, shadow ancestor for `vf-window[scrollbars]`'s own edge rails), watches its reflected `active` attribute, and toggles a presence-only `data-window-inactive` on the scroll element. While present, the recipe empties dither/thumb/arrows on BOTH axes regardless of overflow — the bare channel, arrows included (unlike the idle rail, which keeps them), exactly as System 7 blanked a deactivated window's bars (its List Manager/TextEdit deactivated in-window scrollbars too). No `vf-window` ancestor → the attribute never appears: dialogs have no inactive state and a bare scroll component always draws live. Like the overflow half, this signal could one day go declarative — a custom property cascaded under `vf-window:not([active])` gating the recipe via an `@container style()` query; the rails being ordinary DOM, that migration is a plain selector swap (see the FUTURE note in scroll-state.ts).
396
- - **Document-window (TeachText) composition:** to put the rails on a window's edge rather than inset in its body, slot the scroll area into a `vf-window[flush]` sized `calc(100% + 2px·scale)` with `margin: -1px·scale` — one system pixel under the window frame on every side. Its own frame border then repaints the window's border lines exactly (no doubled frame), and a resizable window's grow box lands exactly over the rail's corner cell, giving the classic System 7 document window. `vf-window[scrollbars]` renders exactly this composition from its own shadow tree, so the one-liner and the slotted form are geometrically identical; slot it yourself when the well should sit *inset* in the body instead (the installer's read-me well).
396
+ - **Document-window (TeachText) composition:** to put the rails on a window's edge rather than inset in its body, slot the scroll area into a `vf-window[flush]` sized `calc(100% + 2px·scale)` with `margin: -1px·scale` — one system pixel under the window frame on every side. Its own frame border then repaints the window's border lines exactly (no doubled frame), and a resizable window's grow box lands exactly over the rail's corner cell, giving the classic System 7 document window. `vf-window[scrollbars]` renders exactly this composition from its own shadow tree (forwarding its `flush`, and `corner` for a resizable window without a status strip), so the one-liner and the slotted form are geometrically identical; slot it yourself when the well should sit *inset* in the body instead (the installer's read-me well).
397
397
  - **Slots:** default. **Parts:** `viewport`.
398
398
 
399
399
  #### `vf-fieldset` (`VfFieldset`, vf-fieldset.ts)
@@ -191,7 +191,7 @@
191
191
  },
192
192
  {
193
193
  "name": "vf-desktop",
194
- "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, at pointer-gesture\nends), so tabbing walks the stack the way the eye does and Shift+Tab is\nits 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 top 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, one whole-surface raster at one image px per system px, 1-bit at\nevery density and zoom (src/pattern-fill.ts).\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 with none 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).",
194
+ "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 top 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, one whole-surface raster at one image px per system px, 1-bit at\nevery density and zoom (src/pattern-fill.ts).\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 with none 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).",
195
195
  "attributes": [
196
196
  {
197
197
  "name": "width",
@@ -932,7 +932,7 @@
932
932
  },
933
933
  {
934
934
  "name": "vf-scroll-area",
935
- "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.\n\nSize the host (width/height) from the outside; the viewport fills it.\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.",
935
+ "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. The\nviewport insets its content 8px; flush drops that inset so content\nruns to the frame and the rails.\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.",
936
936
  "attributes": [
937
937
  {
938
938
  "name": "axis",
@@ -943,6 +943,16 @@
943
943
  { "name": "both" }
944
944
  ]
945
945
  },
946
+ {
947
+ "name": "flush",
948
+ "description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
949
+ "values": []
950
+ },
951
+ {
952
+ "name": "corner",
953
+ "description": "Reserve the bottom-right corner cell on a single-axis rail: the rail\nstops 15px short of the frame and the viewport spans the rest, leaving\nthe 15×15 cell — with the two dividers a `both` rail's corner carries —\nfor a grow box to land in. A `both` rail always has the cell, so the\nflag changes nothing there. `vf-window[scrollbars resizable]` sets it.",
954
+ "values": []
955
+ },
946
956
  {
947
957
  "name": "label",
948
958
  "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.",
@@ -1356,12 +1366,12 @@
1356
1366
  },
1357
1367
  {
1358
1368
  "name": "flush",
1359
- "description": "Remove the default 12px body padding.",
1369
+ "description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
1360
1370
  "values": []
1361
1371
  },
1362
1372
  {
1363
1373
  "name": "scrollbars",
1364
- "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell. Values mirror `vf-scroll-area`'s `axis`; the `heading` names the\nscroll region; the viewport part is re-exported. Implies `flush` (the\nviewport carries its own padding). The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
1374
+ "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nThe body's own padding goes; the viewport keeps its 8px inset unless\n`flush`, which passes through. The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
1365
1375
  "values": [
1366
1376
  { "name": "vertical" },
1367
1377
  { "name": "horizontal" },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "vintage-frames",
4
- "version": "0.5.3",
4
+ "version": "0.5.5",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -457,7 +457,7 @@
457
457
  },
458
458
  {
459
459
  "name": "vf-desktop",
460
- "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, at pointer-gesture\nends), so tabbing walks the stack the way the eye does and Shift+Tab is\nits 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 top 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, one whole-surface raster at one image px per system px, 1-bit at\nevery density and zoom (src/pattern-fill.ts).\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 with none 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).",
460
+ "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 top 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, one whole-surface raster at one image px per system px, 1-bit at\nevery density and zoom (src/pattern-fill.ts).\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 with none 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).",
461
461
  "doc-url": "",
462
462
  "attributes": [
463
463
  {
@@ -2239,7 +2239,7 @@
2239
2239
  },
2240
2240
  {
2241
2241
  "name": "vf-scroll-area",
2242
- "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.\n\nSize the host (width/height) from the outside; the viewport fills it.\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.",
2242
+ "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. The\nviewport insets its content 8px; flush drops that inset so content\nruns to the frame and the rails.\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.",
2243
2243
  "doc-url": "",
2244
2244
  "attributes": [
2245
2245
  {
@@ -2250,6 +2250,16 @@
2250
2250
  "default": "'vertical'"
2251
2251
  }
2252
2252
  },
2253
+ {
2254
+ "name": "flush",
2255
+ "description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
2256
+ "value": { "type": "boolean", "default": "false" }
2257
+ },
2258
+ {
2259
+ "name": "corner",
2260
+ "description": "Reserve the bottom-right corner cell on a single-axis rail: the rail\nstops 15px short of the frame and the viewport spans the rest, leaving\nthe 15×15 cell — with the two dividers a `both` rail's corner carries —\nfor a grow box to land in. A `both` rail always has the cell, so the\nflag changes nothing there. `vf-window[scrollbars resizable]` sets it.",
2261
+ "value": { "type": "boolean", "default": "false" }
2262
+ },
2253
2263
  {
2254
2264
  "name": "label",
2255
2265
  "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.",
@@ -2275,6 +2285,16 @@
2275
2285
  "description": "Which scroll rails to reserve as permanent placeholders: `vertical`\n(default), `horizontal`, or `both`. Each reserved rail shows an empty white\nchannel until its axis overflows; the unreserved axis still scrolls\nnatively (wheel, keyboard) but draws no rail.",
2276
2286
  "type": "'vertical' | 'horizontal' | 'both'"
2277
2287
  },
2288
+ {
2289
+ "name": "flush",
2290
+ "description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
2291
+ "type": "boolean"
2292
+ },
2293
+ {
2294
+ "name": "corner",
2295
+ "description": "Reserve the bottom-right corner cell on a single-axis rail: the rail\nstops 15px short of the frame and the viewport spans the rest, leaving\nthe 15×15 cell — with the two dividers a `both` rail's corner carries —\nfor a grow box to land in. A `both` rail always has the cell, so the\nflag changes nothing there. `vf-window[scrollbars resizable]` sets it.",
2296
+ "type": "boolean"
2297
+ },
2278
2298
  {
2279
2299
  "name": "label",
2280
2300
  "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.",
@@ -3275,12 +3295,12 @@
3275
3295
  },
3276
3296
  {
3277
3297
  "name": "flush",
3278
- "description": "Remove the default 12px body padding.",
3298
+ "description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
3279
3299
  "value": { "type": "boolean", "default": "false" }
3280
3300
  },
3281
3301
  {
3282
3302
  "name": "scrollbars",
3283
- "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell. Values mirror `vf-scroll-area`'s `axis`; the `heading` names the\nscroll region; the viewport part is re-exported. Implies `flush` (the\nviewport carries its own padding). The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
3303
+ "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nThe body's own padding goes; the viewport keeps its 8px inset unless\n`flush`, which passes through. The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
3284
3304
  "value": {
3285
3305
  "type": "'vertical' | 'horizontal' | 'both' | undefined"
3286
3306
  }
@@ -3366,12 +3386,12 @@
3366
3386
  },
3367
3387
  {
3368
3388
  "name": "flush",
3369
- "description": "Remove the default 12px body padding.",
3389
+ "description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
3370
3390
  "type": "boolean"
3371
3391
  },
3372
3392
  {
3373
3393
  "name": "scrollbars",
3374
- "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell. Values mirror `vf-scroll-area`'s `axis`; the `heading` names the\nscroll region; the viewport part is re-exported. Implies `flush` (the\nviewport carries its own padding). The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
3394
+ "description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nThe body's own padding goes; the viewport keeps its 8px inset unless\n`flush`, which passes through. The slotted composition (SPEC §5\nvf-scroll-area) still works for windows that want an inset well instead.",
3375
3395
  "type": "'vertical' | 'horizontal' | 'both' | undefined"
3376
3396
  },
3377
3397
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vintage-frames",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Lit web components that emulate the look and feel of classic Mac OS System 7",
5
5
  "type": "module",
6
6
  "license": "MIT",