vintage-frames 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +1035 -24
- package/dist/components/vf-dialog.d.ts +9 -0
- package/dist/components/vf-number-field.js +1 -1
- package/dist/components/vf-scroll-area.js +9 -1
- package/dist/components/vf-text-field.d.ts +2 -1
- package/dist/components/vf-text-field.js +1 -1
- package/dist/components/vf-window.d.ts +20 -0
- package/dist/components/vf-window.js +43 -3
- package/dist/modal-dialog.d.ts +33 -0
- package/dist/modal-dialog.js +66 -36
- package/dist/position.d.ts +27 -8
- package/dist/position.js +74 -18
- package/dist/text-control.d.ts +8 -1
- package/dist/text-control.js +2 -7
- package/docs/SPEC.md +9 -8
- package/editor/vscode.html-custom-data.json +157 -2
- package/editor/web-types.json +317 -3
- package/package.json +1 -1
package/dist/text-control.d.ts
CHANGED
|
@@ -116,8 +116,15 @@ export declare class VfTextControlBase extends VfShadowRoleControl {
|
|
|
116
116
|
* inner button, which runs the same transient-proxy path a pointer does. A
|
|
117
117
|
* disabled default button means no submission at all, as in HTML; a form
|
|
118
118
|
* with no submit button falls back to the bare call, also as in HTML.
|
|
119
|
+
*
|
|
120
|
+
* Returns whether a form owner took the press — true even when a disabled
|
|
121
|
+
* default button meant no submission, since HTML gave the press to the form
|
|
122
|
+
* either way. The callers cancel the keydown on true, so a modal shell
|
|
123
|
+
* (`VfModalDialog`) sees the press as spoken for and does not also route it
|
|
124
|
+
* to the dialog's default button; with no form the press goes on to the
|
|
125
|
+
* dialog untouched.
|
|
119
126
|
*/
|
|
120
|
-
protected requestImplicitSubmit():
|
|
127
|
+
protected requestImplicitSubmit(): boolean;
|
|
121
128
|
/**
|
|
122
129
|
* Dispatch a bubbling, composed value event (SPEC §4). Text fields use the
|
|
123
130
|
* default `{ value }` detail; `vf-number-field` passes an enriched detail that
|
package/dist/text-control.js
CHANGED
|
@@ -56,14 +56,9 @@ var l = class extends a {
|
|
|
56
56
|
}
|
|
57
57
|
requestImplicitSubmit() {
|
|
58
58
|
let e = this.internals.form;
|
|
59
|
-
if (!e) return;
|
|
59
|
+
if (!e) return !1;
|
|
60
60
|
let t = [...e.elements].find((e) => e instanceof HTMLButtonElement && e.type === "submit" || e instanceof HTMLInputElement && (e.type === "submit" || e.type === "image") || e.localName === "vf-button" && e.type?.toLowerCase() === "submit");
|
|
61
|
-
|
|
62
|
-
if (t.matches(":disabled")) return;
|
|
63
|
-
t.click();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
e.requestSubmit();
|
|
61
|
+
return t ? (t.matches(":disabled") || t.click(), !0) : (e.requestSubmit(), !0);
|
|
67
62
|
}
|
|
68
63
|
emitValue(t, n = { value: this.value }) {
|
|
69
64
|
e(this, t, n);
|
package/docs/SPEC.md
CHANGED
|
@@ -46,7 +46,7 @@ Modern requirements that we deliberately keep (accessibility over purity):
|
|
|
46
46
|
- **The name/description bridge** (`VfShadowRoleControl`, src/form-control.ts). On the controls whose role lives on a shadow-internal node (the three fields, `vf-select`, `vf-swatch`, `vf-button`), a host-level `aria-labelledby`, `aria-label` or associated `<label for>` resolves — in html-aam precedence — to the inner focusable element's `aria-label` whenever the `label` property is empty (`hostLabel`). Those six extend `VfShadowRoleControl`; the host-role controls (`vf-checkbox`, `vf-radio-group`, `vf-slider`) extend the plain `VfFormControl` and so never *carry* the bridge's API at all — an inherited `description` that renders nothing is the advertised-but-inert shape this split removes, and `verify:manifest` enforces it (a tag whose manifest lists `description` must call `renderDescription()`). `vf-button` takes the ARIA half alone (`hostAriaLabel`): a `<button>` is not a labelable element, so no caption names a native one and none names this one either — its slotted content is the name a bridge doesn't override. A `description` property (or, when it's empty, a host-level `aria-describedby`) renders as a hidden span in the control's own shadow root with the inner control's `aria-describedby` pointing at it — the shadow-internal IDREF idiom `vf-dialog`'s title patch uses — and a failing constraint's `validationMessage` joins the same node. Referenced text is flattened at render time, so an edit to a referenced element's *text* lands on the next render rather than instantly — the one divergence from native. Controls whose role sits on the host (the toggles, slider, radio group, bars) need none of this: the platform reads their host attributes directly.
|
|
47
47
|
- **Constraint validation** (`VfFormControl`): a reflected `required` fails validation with `valueMissing` while the control is empty by its own definition (fields/select `value === ''`, checkbox unchecked, radio group unselected — each with its native counterpart's message), through one `syncValidity()` funnel run before each render. The native surface is complete — `checkValidity`/`reportValidity`/`validity`/`validationMessage`/ `willValidate`/`setCustomValidity` — `:invalid` matches on the host, `form.reportValidity()` blocks, and `disabled`/`readonly` bar validation per HTML's own rules. One divergence the platform imposes: `willValidate` is `true` on a `vf-button type="button"`, where a native button is barred from constraint validation. A form-associated custom element cannot opt out of the candidate set, so this is a platform limit. AT wiring is `aria-required`/`aria-invalid` on the inner control (plus internals mirrors for host-role controls) — never a forwarded native `required`, which would put UA `:user-invalid` styling on the artwork. Enter's implicit submission routes through the browser's validation, so it cannot submit past a failing constraint.
|
|
48
48
|
- Components must render nothing surprising outside their box: no margins on `:host` by default.
|
|
49
|
-
- **Explicit placement** (`VfPositioned`, src/position.ts): every component takes `top`/`left` in whole system px — set either and the host is absolutely positioned within its parent (`position: absolute` plus both offsets as live `calc(var(--vf-scale, 1) * Npx)` inline styles; the unset coordinate is 0; `right`/`bottom` released to `auto` and `margin` zeroed). Set neither and the element renders in flow; removing both unwinds every inline declaration. The DITL model: a dialog can be laid out by positioning its items just as validly as by stacking them. **No component is excluded** — the rows a container normally owns (`vf-option`, `vf-menu-item`, `vf-list-item`) and a bar's `vf-menu` take the pair on the same terms, because a consumer may stand one of them up on its own and the kit does not get to rule that out. Placing such a row *inside* its managing parent takes it out of that parent's flow, so the parent stops measuring it (a `vf-select`/`vf-menu` panel is as wide as its widest **flowed** row, and `popup-overflow`'s clamp counts the same set) and the rows after it close the gap — the documented consequence of the placement, not a failure of it; each component's own doc comment states what its container stops doing. Containers are deliberate anchors — the desktop raster, a window's *content region* (the frame's inner edge below the title bar, with no inset of its own — flow content starts there too), a dialog's content area, a stack's box, a fieldset's border interior, a scroll area's scrolled plane. The style writing rides a ReactiveController (`hostUpdated`), not an `updated()` override — component subclasses routinely skip `super.updated()` — and re-applies only when the property values changed, so an unrelated update never re-asserts a coordinate. `npm run verify:position`.
|
|
49
|
+
- **Explicit placement** (`VfPositioned`, src/position.ts): every component takes `top`/`left` in whole system px — set either and the host is absolutely positioned within its parent (`position: absolute` plus both offsets as live `calc(var(--vf-scale, 1) * Npx)` inline styles; the unset coordinate is 0; `right`/`bottom` released to `auto` and `margin` zeroed). Set neither and the element renders in flow; removing both unwinds every inline declaration. The DITL model: a dialog can be laid out by positioning its items just as validly as by stacking them. **No component is excluded** — the rows a container normally owns (`vf-option`, `vf-menu-item`, `vf-list-item`) and a bar's `vf-menu` take the pair on the same terms, because a consumer may stand one of them up on its own and the kit does not get to rule that out. Placing such a row *inside* its managing parent takes it out of that parent's flow, so the parent stops measuring it (a `vf-select`/`vf-menu` panel is as wide as its widest **flowed** row, and `popup-overflow`'s clamp counts the same set) and the rows after it close the gap — the documented consequence of the placement, not a failure of it; each component's own doc comment states what its container stops doing. Containers are deliberate anchors — the desktop raster, a window's *content region* (the frame's inner edge below the title bar, with no inset of its own — flow content starts there too), a dialog's content area, a stack's box, a fieldset's border interior, a scroll area's scrolled plane. The style writing rides a ReactiveController (`hostUpdated`), not an `updated()` override — component subclasses routinely skip `super.updated()` — and re-applies only when the property values changed, so an unrelated update never re-asserts a coordinate. **`fixed`** (bare attribute, reflected) holds the placement against the *visible* region of the nearest scrolling ancestor instead of its scrolled plane: `position: sticky` in place of `absolute`, the same live offsets, the flag alone at (0,0). Sticky boxes stay in flow, so the controller erases the footprint — the host is blockified (the outer display change `absolute` makes on its own: `inline-block` → `block`, `inline-flex` → `flex`, …), `max-width: fit-content`, `z-index: 1` (over the plane's placed children), and a negative right/bottom margin equal to its own border box, kept exact by a ResizeObserver — a 0×0 margin box that flow content lays out as if it weren't there. Two engine facts stated as rules: sticky only ever pushes a box *down* from where the flow put it, so a fixed child precedes the flow content in its parent; and a scroll container that never scrolls (a plain window body is `overflow: hidden`) holds it exactly where placement would. Removing the flag returns the host to ordinary placement with the recipe unwound. `npm run verify:position`.
|
|
50
50
|
- `vf-dialog` takes the pair in **viewport** coordinates: `showModal()` puts the box in the top layer, whose containing block is the viewport rather than the nearest positioned ancestor. Unset means centered — recomputed on open and on every box/viewport/scale change, so a modal whose content upgrades after opening is never stranded; `position` is left to the UA and only the four inset/margin declarations are written.
|
|
51
51
|
- **Gestures write through the same properties** (`PlacementController`): a `vf-window`/`vf-dialog` title-bar drag and a `vf-icon` drag or arrow nudge state `top`/`left`, and `vf-window`'s grow box states `width`/`height` (`VfSized`) — all in whole system px, so a moved element is placed the way an authored one is and holds its spot through a zoom. Writing resolved CSS px instead was the bug: `--vf-scale` moved under the constant and every zoom step re-read it as a different number of system px (by the ratio the scale itself moved — visible even at zoom levels where the target does not change, and nothing else on the page does). Values are snapped to `snapSys` at gesture time and never re-snapped afterwards: re-rounding onto each new lattice compounds (62 → 63 → 64), and whole system px is whole device px at every rung regardless. `npm run verify:zoom` group (e).
|
|
52
52
|
- **The movable contract.** A host that moves under a gesture states its own rectangle, and its positioning parent is a box with a size. Both halves are the consumer's — a component can supply neither for itself — and both used to fail quietly, so each is a one-time `console.warn` (`warnMovableContract`, latched per element like `vf-window`'s size warning). `npm run verify:position` group CONTRACT.
|
|
@@ -208,27 +208,28 @@ 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), `min-width` / `min-height: number` (default 80 / 54) and `max-width` / `max-height: number` (unbounded) — the grow box's sizeRect, whole system px (see Behavior), `variant?: 'utility'` (the slim windoid chrome), `scrollbars?: 'vertical' | 'horizontal' | 'both'` (edge scroll rails).
|
|
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.
|
|
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), `min-width` / `min-height: number` (default 80 / 54) and `max-width` / `max-height: number` (unbounded) — the grow box's sizeRect, whole system px (see Behavior), `variant?: 'utility'` (the slim windoid chrome), `scrollbars?: 'vertical' | 'horizontal' | 'both'` (edge scroll rails), `header-height: number` (`headerHeight` — the header's height in whole system px, rule included; unset, the header is as tall as its content plus the rule).
|
|
212
|
+
- **Visual:** `vfChromeFrame` + `vfTitleBar` (§4), plus a full-size flex-column layout on the frame — title bar, header, body, status strip. `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).
|
|
215
215
|
- Close box: LEFT side, 11×11px, 8px from the inner-left edge, with 3px of clear white above and below it, `1px solid black`, white bg, no bevel, surrounded by a 1px white patch interrupting the stripes. `:active` (pressed) → the interior fills with the classic radiating "go-away" sunburst: black 1-bit spokes on the white face (four orthogonal 3px spokes plus four diagonal 2px ones around an empty center), traced pixel-for-pixel from the UI kit's close-button-active-state sprite.
|
|
216
216
|
- Zoom box: RIGHT side, same box, plus a small box nested in its top-left corner (sharing the widget's own top/left border; only the right and bottom edges are drawn). `:active` (pressed) → shows the identical sunburst as the close box; the nested box gives way to it.
|
|
217
217
|
- Body: no inset of its own — content starts at the content region's corner, the frame's inner edge below the title bar, where a placed child's (0,0) is; an inset is the content's (a `vf-stack pad`) — 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
|
+
- Header (`header` slot): a band between the title bar and the body, the full width of the window — the Finder window's header line — a white interior over a 1px black rule (`vfRule`'s `.vf-rule-bottom`), part `header` (a `<div>`, never a `<header>` element, for the banner-landmark reason the title bar is a `<div>`). Like the body it carries no inset and is a positioning anchor: (0,0) is the header's own corner, flow content starts there, an inset is the content's (a `vf-stack pad`). As tall as its content unless `header-height` states it (rule included, the way every kit bar counts its rule). Clipped like the body; a drop-open panel still escapes. Takes **no space** until the slot is populated, and collapses again when it empties. Composes with `scrollbars`: the edge rails sit in the body below it, the area's top overhang landing its frame line exactly on the header's rule, so the header spans the vertical rail's column and the rail's top arrow begins under it.
|
|
219
220
|
- 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
221
|
- 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. the area's viewport carries no inset (the border-floor term alone), 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).
|
|
221
222
|
- 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
223
|
- **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
224
|
- **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, each axis clamped into the sizeRect (`min-width`/`max-width`, `min-height`/`max-height` — GrowWindow's: the app stated the rectangle of sizes and the Window Manager clamped the drag to it; the mins default to the 80×54 floor a window can still be worked at, the maxes are unbounded, and a min equal to its max locks the axis — the strip that scrolls sideways and never grows taller). The clamp runs after the lattice snap, so a bound lands exactly the way an authored size does even off the lattice (an odd height at 2×); where a min and max cross the max wins, so a window authored under the floor and held there never jumps to it. The rect bounds the gesture only: a size authored outside it renders as declared, and the first grow-box move brings it inside. The drag 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`.
|
|
224
|
-
- **Slots:** default (body content), `status` (the bottom status strip — see Visual).
|
|
225
|
-
- **Parts:** `frame`, `title-bar`, `title`, `close-box`, `zoom-box`, `body`, `status-bar`, `grow-box`, plus `viewport` re-exported from the built-in scroll area when `scrollbars` is set.
|
|
225
|
+
- **Slots:** default (body content), `header` (the header strip between the title bar and the body), `status` (the bottom status strip — see Visual).
|
|
226
|
+
- **Parts:** `frame`, `title-bar`, `title`, `close-box`, `zoom-box`, `header`, `body`, `status-bar`, `grow-box`, plus `viewport` re-exported from the built-in scroll area when `scrollbars` is set.
|
|
226
227
|
- **Events:** `vf-close`, `vf-zoom` (detail `{}`), `vf-resize` (detail `{ width, height, commit }`).
|
|
227
228
|
|
|
228
229
|
#### `vf-dialog` (`VfDialog`, vf-dialog.ts)
|
|
229
230
|
The modal-dialog shell: movable modal by default (the dBoxProc double frame with the striped title bar set into it — traced from a 2× System 7 capture of a movable modal), the modal dialog box with `frame="plain"` (the bare double frame; see the Group A recipe table).
|
|
230
231
|
- **Attributes/props:** `open: boolean` (reflect), `heading: string`, `top: number` / `left: number` (whole system px, in **viewport** coordinates — see §1 Explicit placement; unset means centered), `width: number` / `height: number` (**declare them both** — whole system px, the same fixed box `vf-window` is. A native `<dialog>` is `width: fit-content` measured against the space left beside its own offsets, and stating an offset is how the movable modal is positioned, so an undeclared dialog squeezes itself and reflows as it is dragged toward an edge. The two fall back differently — width to 260 system px, height to the content — and it names whichever are missing, once, on the open that first shows it), `label: string` (accessible name for a dialog with no `heading`), `closable: boolean` (default **false** — the bare movable-modal bar; the close box is opt-in because the HIG's Chapter 6 text denies a movable modal one while its Figure 5-1 grants it — the parameter enables either reading), `light-dismiss: boolean` (`lightDismiss`; default **false** — a click outside the frame closes the dialog with `vf-close` reason `'outside'`. Off by default because the classic modal ignored an outside click, and a dialog that asks a question should keep ignoring it; opt in for the About box and the splash, which the classic Mac did dismiss on a click. Reflected, and read at the release, so it can be toggled on an open dialog), `frame?: 'plain'`.
|
|
231
|
-
- **Implementation:** wraps a native `<dialog>` (for top-layer + focus trap). `show()` → `showModal()`; `close()` closes. Keep `open` attr in sync both directions. Drag the title bar to move it (shared `DragController` with `vf-window`), stating `top`/`left` in system px; drags starting on the close widget are ignored (same composedPath guard as `vf-window`). Escape → close + `vf-close` detail `{ reason: 'escape' }`; close box/programmatic/close() → `{ reason: 'close' }`; a click outside under `light-dismiss` → `{ reason: 'outside' }`. No backdrop dimming: `::backdrop { background: transparent; }`. **Light dismiss is target identity, not geometry** (`VfModalDialog`): the `::backdrop` hit-tests as its `<dialog>`, and the frame fills the dialog's box, so the dialog is a pointer event's *target* exactly when the pointer was outside the frame — the listeners sit on the `<dialog>` itself, attached while open. It takes both halves of the click: a `pointerdown` on the backdrop arms (any other press disarms, so a right-click macOS never releases can't leave a stale arm), and a `pointerup` on the backdrop with the same `pointerId` closes — the platform's own `closedby="any"` two-step, so a press that starts on a control and slides off, or a title-bar drag released past the frame, leaves the dialog open. Deliberately not the `click` event: UI Events dispatches a press-drag-release click at the common ancestor of the two targets, which for a press on the frame released outside is the dialog itself. The platform's `closedby` attribute is not used because its dismissal arrives as `cancel`, which the base already reads as Escape. The press is consumed either way — a modal's backdrop lets nothing beneath it see the click — which is what lets a page dismiss its About box without also selecting the icon under the pointer. `npm run verify:dialog`. **Removal while open is a close path** (`VfModalDialog.disconnectedCallback`): HTML's dialog removing steps skip the close algorithm entirely, which is exactly what a framework unmount does — the teardown routes through the same native-`close` funnel (`vf-close` fires on the removed element; nothing bubbles, it left the tree), `open` and the written origin reconcile so a re-append mounts closed, and focus returns to the element focused at open time. **The placement re-settles while open** whenever the dialog's own box resizes — slotted content upgrading after `showModal()`, `--vf-scale` moving under zoom — the viewport resizes, or the scale changes. What survives depends on how the modal got where it is: an unplaced one re-centers (it never claimed a spot, and the stranded case is exactly this one), while a dragged or authored origin is kept and only re-clamped on screen.
|
|
232
|
+
- **Implementation:** wraps a native `<dialog>` (for top-layer + focus trap). `show()` → `showModal()`; `close()` closes. Keep `open` attr in sync both directions. Drag the title bar to move it (shared `DragController` with `vf-window`), stating `top`/`left` in system px; drags starting on the close widget are ignored (same composedPath guard as `vf-window`). Escape → close + `vf-close` detail `{ reason: 'escape' }`; close box/programmatic/close() → `{ reason: 'close' }`; a click outside under `light-dismiss` → `{ reason: 'outside' }`. No backdrop dimming: `::backdrop { background: transparent; }`. **Light dismiss is target identity, not geometry** (`VfModalDialog`): the `::backdrop` hit-tests as its `<dialog>`, and the frame fills the dialog's box, so the dialog is a pointer event's *target* exactly when the pointer was outside the frame — the listeners sit on the `<dialog>` itself, attached while open. It takes both halves of the click: a `pointerdown` on the backdrop arms (any other press disarms, so a right-click macOS never releases can't leave a stale arm), and a `pointerup` on the backdrop with the same `pointerId` closes — the platform's own `closedby="any"` two-step, so a press that starts on a control and slides off, or a title-bar drag released past the frame, leaves the dialog open. Deliberately not the `click` event: UI Events dispatches a press-drag-release click at the common ancestor of the two targets, which for a press on the frame released outside is the dialog itself. The platform's `closedby` attribute is not used because its dismissal arrives as `cancel`, which the base already reads as Escape. The press is consumed either way — a modal's backdrop lets nothing beneath it see the click — which is what lets a page dismiss its About box without also selecting the icon under the pointer. `npm run verify:dialog`. **Keyboard: the Dialog Manager's two rules** (`VfModalDialog`). *Initial focus:* right after `showModal()`'s own focusing steps, focus is handed to `initialFocusTarget` — a slotted control carrying `autofocus`, else the first enabled text-entry control (`vf-text-field`, `vf-number-field`, `vf-text-area`, or a native text input/textarea), else the default button (`defaultButton`: the first enabled `vf-button[variant="default"]`, looked for among the slotted content and then the shadow tree), else nothing, leaving the browser's pick. The classic dialog put the insertion point in its first text item and gave nothing else focus; the web needs a focused control, and the default button is the one that makes Return and Space work without a Tab. Left alone, `showModal()` focuses the first focusable thing in flat-tree order — Cancel in a Cancel/OK row, or a link in the body — so Return pressed the wrong thing. Both getters are `protected`, for a consumer's own modal to override. *Return/Enter:* a `keydown` listener on the `<dialog>`, bubble phase, attached while open with the light-dismiss trio. An uncancelled, unmodified Enter activates `defaultButton` (its `click()`, so a `type="submit"` default runs its proxy path) and cancels the keydown, which is what keeps a focused Cancel from firing itself — Space presses the focused control, as on the classic Mac. Anything that took Enter for itself cancelled the keydown first and is left alone: `vf-select`'s list, `vf-list`, an icon's rename, and a text field whose form ran its implicit submission (`requestImplicitSubmit` now reports whether a form owner took the press, and the fields cancel on true — so the submission runs once, not once per layer). A link keeps its own Enter. A multi-line editor (`<textarea>`, contenteditable) keeps Return for the newline; only `code === 'NumpadEnter'` fires the button there — the split a TextEdit item drew between the two keys. With no enabled default button the press is left native. Escape is unchanged (the `cancel` path above). **Removal while open is a close path** (`VfModalDialog.disconnectedCallback`): HTML's dialog removing steps skip the close algorithm entirely, which is exactly what a framework unmount does — the teardown routes through the same native-`close` funnel (`vf-close` fires on the removed element; nothing bubbles, it left the tree), `open` and the written origin reconcile so a re-append mounts closed, and focus returns to the element focused at open time. **The placement re-settles while open** whenever the dialog's own box resizes — slotted content upgrading after `showModal()`, `--vf-scale` moving under zoom — the viewport resizes, or the scale changes. What survives depends on how the modal got where it is: an unplaced one re-centers (it never claimed a spot, and the stranded case is exactly this one), while a dragged or authored origin is kept and only re-clamped on screen.
|
|
232
233
|
- **Visual (default chrome):** `vfModalFrame` with `vfTitleBar` set into it (§4) — the bar is literally the recipe `vf-window` uses, so it is identical by construction (stripes + centered title) rather than by matching copies; the frame is the modal double frame, **not** the window's shadowed `vfChromeFrame`. Outside → in: 1px outer rule; the 18px bar directly under it, 2px in at either end with its stripes starting 2px from the rule; the 2px band as the bar's floor (the bar's rule + the inner box's 1px top border); 2px gap + 2px band down the sides and along the bottom; no shadow. The body begins 20px below the dialog's top and 5px inside its other three edges. The bar takes the default `--vf-title-inset` (16px) — 60px when `closable`, the same clearance as `vf-window`, since the centered title needs symmetric room for the widget — and sets `touch-action: none` unconditionally, having no immovable state. `closable` renders the shared close box (`vfWindowWidgets` + `closeBox()` — byte-identical to `vf-window`'s, per the `moveable modal dialog.png` reference). Body is WHITE (`--vf-surface: #fff`), `padding: 16px`. An optional `buttons` slot renders a bottom-right `vf-button-group` footer that only takes space when populated (equal-width, faces aligned). Both chromes are full-height flex columns and the body takes the slack, for the same reason `vf-window`'s frame is: the declared `height` lands on the `<dialog>` (see `dialogSize`), and the recipes are skin only. The frame is the flex child of the `<dialog>` itself (`dialog[open]` is a flex column in `modalDialogStyles` — `[open]`-scoped, or it would out-cascade the UA's `dialog:not([open]) { display: none }`), not a `height: 100%` block: a percentage can't resolve against the undeclared-height dialog that only the UA's `dialog:modal` max-height caps, and that spill was how a viewport-tall modal stranded its buttons off-screen. **The box never grows, but over-stuffed content scrolls instead of clipping**: the body is a flex column of a `.content` scroll region (heading + default slot; part `content`) over the pinned footer. While the content fits, nothing matches — rendering is pixel-identical to the old block flow. Once it overflows (`ScrollStateController`, the always-a-rail machinery), the region reserves the 16px channel as its own right padding, wears the drawn `vfScrollRail` rail (§4) boxed by a 1px `.scroll-frame` overlay, and becomes a keyboard stop (`tabindex="0"`, `role="group"`, the kit's dotted ring) so the copy is scrollable without a pointer. The rail rides the wrapper as an overlay pinned to its right edge, deliberately out of the layout flow: a rail *column*'s two fixed 15px arrow cells would hand the region a 32px minimum height, and a short dialog would then measure as fitting with the rail shown and overflowing without it — flip-flopping forever. The drop-open exemption is unchanged.
|
|
233
234
|
- **The CSS and the controller state the same thing.** `.content` is `overflow-y: hidden` until the controller flags overflow, then `overflow-y: scroll`. It is deliberately never `auto`: the controller ignores the body face's negative half-leading (`LEADING_SPILL_SYS`, src/scroll-state.ts — `vf-paragraph` sets a 12-system-px line box under a 16-system-px em, so the inline box spills 2 inkless system px past the block box and `scrollHeight` counts it), and `auto` has no way to know that. With `auto` the two disagreed, and a fixed info dialog whose copy ends in a `vf-paragraph` rubber-banded under the wheel with no rail shown — 6 CSS px at scale 3. `hidden` still scrolls programmatically, so `scrollIntoView` on a focused control is unaffected. `npm run verify:contract`, OVERFLOW group.
|
|
234
235
|
- **Visual (`frame="plain"`):** the same `vfModalFrame` (§4 — 1px outer, 2px gap, 2px inner band, no shadow, per `Windows/modal dialog.png`) with no title bar, and immovable like the original dBoxProc dialog (nothing renders a drag handle). The body begins 5px inside every edge. A `heading` renders as a centered display-face heading at the top of the body (`margin-bottom: 16px`) — the way those dialogs drew their title in content — and `closable` is ignored, there being no bar to carry the widget.
|
|
@@ -307,7 +308,7 @@ The color-swatch button: a well of solid color — a palette cell.
|
|
|
307
308
|
- **Attributes/props:** `value`, `placeholder`, `disabled`, `readonly`, `type: string` (default `'text'`; pass through to input), `name`. The input-behavior attributes — `autocomplete`, `inputmode`, `enterkeyhint`, `maxlength`, `pattern`, `spellcheck`, `autocapitalize` — are **forwarded verbatim** from the host onto the inner input (observed attributes, not reactive properties: four of them are globals with IDL accessors already on `HTMLElement`, and a Lit `@property` would shadow the platform member — the `align`/`draggable` trap). The platform only honors them on the element that actually takes the input, which is in the shadow root.
|
|
308
309
|
- **Visual:** inner `<input>`: white bg, `1px solid black`, NO radius, height `var(--vf-control-height, 22px)`, `padding: 0 6px`, font tokens but `font-weight: var(--vf-font-weight, 700)`. `user-select: text`. Focus: for a **keyboard** focus, the kit's 1px dashed rule (`vfFocusUnderline`, §4) one blank system px row under the well — no dotted outline and no thickened border. A click leaves it unmarked: the insertion point is already the answer to where focus went (see §4 on why this can't be `:focus-visible`). Disabled: the text dims to gray; the black border stays. Selected text inverts to solid black-on-white (`.vf-field::selection`, using `--vf-highlight`/`--vf-highlight-text`) — the 1-bit System 7 selection, shared by all three editable fields via the `vfField` skin.
|
|
309
310
|
- **Structure:** the `<input>` sits in a `.vf-field-well` wrapper, which is what the focus rule hangs from and what carries `vf-snap`. A replaced element draws no pseudo-element of its own, and the host is never the thing grid snapping moves, so the wrapper is the only box that is both the well's exact shape and on the corrected grid. Same wrapper in all three fields, assembled by `VfTextControlBase.wellClass` so the focus gate can't drift between them. Two consequences when embedding: the rule paints 2 system px **below the host's own box** (`pointer-events: none`, so it never takes a click meant for what sits under it, but a tight `overflow: hidden` ancestor clips it), and a `width` set on `::part(input)` sizes the control without moving the wrapper the rule spans — a field's width belongs on the host or on `--vf-field-width`.
|
|
310
|
-
- **Behavior:** form-associated; syncs `value` on input; `formResetCallback` restores default. A plain Enter runs the form's **implicit submission** the way HTML defines it — activating the form's default button (first submit button in tree order, `vf-button` included), so the submission carries a real `submitter` and that button's `name`/`value`, a disabled default button submits nothing, and only a form with no submit button falls back to a bare `requestSubmit()` (`requestImplicitSubmit`, text-control.ts).
|
|
311
|
+
- **Behavior:** form-associated; syncs `value` on input; `formResetCallback` restores default. A plain Enter runs the form's **implicit submission** the way HTML defines it — activating the form's default button (first submit button in tree order, `vf-button` included), so the submission carries a real `submitter` and that button's `name`/`value`, a disabled default button submits nothing, and only a form with no submit button falls back to a bare `requestSubmit()` (`requestImplicitSubmit`, text-control.ts). When a form owner took the press the keydown is cancelled, so an enclosing `vf-dialog` does not route the same Enter to its default button a second time; with no form the press goes on to the dialog, whose default button it activates.
|
|
311
312
|
- **Parts:** `input`.
|
|
312
313
|
- **Events:** `vf-input` detail `{ value }` on every keystroke; `vf-change` detail `{ value }` on commit (native change). Plus the native pair per §2: the inner input's own composed `input` crosses the boundary itself; `change` is re-dispatched from the host.
|
|
313
314
|
|
|
@@ -390,7 +391,7 @@ Classic list box.
|
|
|
390
391
|
#### `vf-scroll-area` (`VfScrollArea`, vf-scroll-area.ts)
|
|
391
392
|
A container whose scrollbars look like System 7.
|
|
392
393
|
- **Attributes/props:** `axis: 'vertical' | 'horizontal' | 'both'` (default `'vertical'`, reflected) — which scroll rails to reserve as permanent placeholders (see "always-a-rail" below); `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 carrying no inset of its own — only 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 1 system px from the frame box at every scale; an inset is the content's (a `vf-stack pad`). Consumer sets width/height on host. The scrolled plane — the wrapper the slot renders into, and the positioned anchor for placed children — is `width: fit-content; min-width: 100%`: never narrower than the viewport, as wide as content that cannot wrap, so the controllers' ResizeObserver on it sees a row growing sideways the way it sees copy growing down (a block wrapper's auto width was the viewport's whatever the row did, and a row gaining a cell left the thumb and the overflow state stale until something else re-measured); copy wraps as before, and a `position: sticky; left: 0` child holds across the scroll, the plane being its containing block. In the overflowing case a percentage-width or centered block child resolves against the grown plane, not the viewport. A `slotchange` re-measures; `measure()` covers a scroll range that changes with no box changing. 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
|
+
- **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 carrying no inset of its own — only 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 1 system px from the frame box at every scale; an inset is the content's (a `vf-stack pad`). Consumer sets width/height on host. The scrolled plane — the wrapper the slot renders into, and the positioned anchor for placed children — is `width: fit-content; min-width: 100%`: never narrower than the viewport, as wide as content that cannot wrap, so the controllers' ResizeObserver on it sees a row growing sideways the way it sees copy growing down (a block wrapper's auto width was the viewport's whatever the row did, and a row gaining a cell left the thumb and the overflow state stale until something else re-measured); copy wraps as before, and a `position: sticky; left: 0` child holds across the scroll, the plane being its containing block. `min-height: 100%` likewise: a `fixed` child (§2 Explicit placement) is a sticky box the plane contains, and a plane shorter than the child's stated `top` would clamp it to the plane's bottom edge; content that fits still fits. In the overflowing case a percentage-width or centered block child resolves against the grown plane, not the viewport. A `slotchange` re-measures; `measure()` covers a scroll range that changes with no box changing. 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
395
|
- **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
396
|
- **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
397
|
- **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` 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 `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 inside the body instead of on its edge (the installer's read-me well, placed with `top`/`left`).
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"name": "left",
|
|
26
26
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
27
27
|
"values": []
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "fixed",
|
|
31
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
32
|
+
"values": []
|
|
28
33
|
}
|
|
29
34
|
],
|
|
30
35
|
"references": []
|
|
@@ -92,6 +97,11 @@
|
|
|
92
97
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
93
98
|
"values": []
|
|
94
99
|
},
|
|
100
|
+
{
|
|
101
|
+
"name": "fixed",
|
|
102
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
103
|
+
"values": []
|
|
104
|
+
},
|
|
95
105
|
{
|
|
96
106
|
"name": "description",
|
|
97
107
|
"description": "Description for the control — hint text, a format, a unit. A host-level\n`aria-describedby` cannot reach a focusable element inside a shadow root,\nso there was structurally no way to describe a field; this property is\nthat channel. It renders as a hidden span in the control's own shadow root\nwith the inner control's `aria-describedby` pointing at it — the\nshadow-internal IDREF idiom `vf-dialog`'s title patch already uses. A\nhost-level `aria-describedby` is bridged into the same span when this\nproperty is empty, and a failing constraint's validationMessage\njoins it too, so AT hears the error where it hears the hint.\n\nHost-role controls get neither half: their `aria-describedby` already\nworks, and their validation message reaches AT the way a native control's\ndoes — `aria-invalid` plus the browser's own validation UI, not AccName.",
|
|
@@ -139,6 +149,11 @@
|
|
|
139
149
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
140
150
|
"values": []
|
|
141
151
|
},
|
|
152
|
+
{
|
|
153
|
+
"name": "fixed",
|
|
154
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
155
|
+
"values": []
|
|
156
|
+
},
|
|
142
157
|
{
|
|
143
158
|
"name": "disabled",
|
|
144
159
|
"description": "Disables the control: it stops responding and submits no value. Each\ncontrol dims per SPEC §1 (typically the label greys while the 1-bit chrome\nstays black).",
|
|
@@ -185,6 +200,11 @@
|
|
|
185
200
|
"name": "left",
|
|
186
201
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
187
202
|
"values": []
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"name": "fixed",
|
|
206
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
207
|
+
"values": []
|
|
188
208
|
}
|
|
189
209
|
],
|
|
190
210
|
"references": []
|
|
@@ -222,13 +242,18 @@
|
|
|
222
242
|
"name": "left",
|
|
223
243
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
224
244
|
"values": []
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"name": "fixed",
|
|
248
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
249
|
+
"values": []
|
|
225
250
|
}
|
|
226
251
|
],
|
|
227
252
|
"references": []
|
|
228
253
|
},
|
|
229
254
|
{
|
|
230
255
|
"name": "vf-dialog",
|
|
231
|
-
"description": "`<vf-dialog>` — the System 7 modal dialog shell.\n\nTwo chromes, one modal lifecycle (native `<dialog>` for top-layer rendering\nand focus trapping, with a fully transparent backdrop — no dimming). Both\nare the same dBoxProc double frame — 1px outer rule, 2px gap, 2px inner\nband, no shadow (vfModalFrame):\n\n- **Default:** the movable modal (movableDBoxProc) — the striped title bar\n set into the top of that frame, with a centered title over a white body.\n Drag the title bar to move it. `closable` adds the standard close box\n (left of the bar) — the HIG's own figures disagree on whether a movable\n modal carries one (Figure 5-1 says yes, Figure 6-1 and the Chapter 6 text\n say no), so the component enables either reading rather than enforcing\n one.\n- **`frame=\"plain\"`:** the modal dialog box — the bare frame, no title bar —\n and immovable, like the original. A `heading` renders as a centered\n display-face heading at the top of the body (the reference art's \"Dialog\n title\"); `closable` is ignored, there being no bar to carry the widget.\n\nOpen it with `show()` (or set the `open` attribute/property); close with\n`close()`. Escape closes it and fires `vf-close` with\n`{ reason: 'escape' }`; the close box and programmatic closing fire\n`{ reason: 'close' }`. With `light-dismiss`, a click outside the frame\ncloses it too, with `{ reason: 'outside' }` — for the About box; off by\ndefault, since the classic modal ignored an outside click.\n---\n\n\n### **Events:**\n - **vf-close** - Dialog closed. Detail `{ reason: 'escape' | 'close' | 'outside' }` — `'outside'` only under `light-dismiss`.\n\n### **Slots:**\n - _default_ - Default slot: dialog body content.\n- **buttons** - Optional action buttons. Rendered as a bottom-right `vf-button-group` (equal-width, faces aligned); the footer only takes space when the slot is populated.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 tile, one black pixel at the origin (`vfDots`; override the whole pattern like `--vf-desktop-pattern`) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-scrollbar-thumb** - scrollbar thumb/elevator (white) _(default: undefined)_\n- **--vf-scrollbar-track** - the scroll trough's base color under the dot-dither (white) _(default: undefined)_\n\n### **CSS Parts:**\n - **frame** - The outer frame (the double frame's 1px rule; the bar and the inner band sit inside it).\n- **title-bar** - The striped title bar (default chrome only).\n- **title** - The centered title patch (or the plain-frame heading).\n- **close-box** - The close widget (`closable`, default chrome only).\n- **body** - The white content area.\n- **content** - The scrolling region inside the body (heading + slotted content, not the footer). Inert while the content fits; over-stuffed, it scrolls under a System 7 rail and becomes a keyboard stop.\n- **footer** - The action row wrapping the buttons.\n- **buttons** - The button group inside the footer.",
|
|
256
|
+
"description": "`<vf-dialog>` — the System 7 modal dialog shell.\n\nTwo chromes, one modal lifecycle (native `<dialog>` for top-layer rendering\nand focus trapping, with a fully transparent backdrop — no dimming). Both\nare the same dBoxProc double frame — 1px outer rule, 2px gap, 2px inner\nband, no shadow (vfModalFrame):\n\n- **Default:** the movable modal (movableDBoxProc) — the striped title bar\n set into the top of that frame, with a centered title over a white body.\n Drag the title bar to move it. `closable` adds the standard close box\n (left of the bar) — the HIG's own figures disagree on whether a movable\n modal carries one (Figure 5-1 says yes, Figure 6-1 and the Chapter 6 text\n say no), so the component enables either reading rather than enforcing\n one.\n- **`frame=\"plain\"`:** the modal dialog box — the bare frame, no title bar —\n and immovable, like the original. A `heading` renders as a centered\n display-face heading at the top of the body (the reference art's \"Dialog\n title\"); `closable` is ignored, there being no bar to carry the widget.\n\nOpen it with `show()` (or set the `open` attribute/property); close with\n`close()`. Escape closes it and fires `vf-close` with\n`{ reason: 'escape' }`; the close box and programmatic closing fire\n`{ reason: 'close' }`. With `light-dismiss`, a click outside the frame\ncloses it too, with `{ reason: 'outside' }` — for the About box; off by\ndefault, since the classic modal ignored an outside click.\n\nKeyboard, the classic Dialog Manager's two rules (VfModalDialog):\non open, focus goes to the first text-entry control — a slotted control\nwith `autofocus` first — or, with none, to the default button\n(`vf-button variant=\"default\"`). Return or Enter activates the default\nbutton from anywhere in the dialog, a focused Cancel included; Space\npresses the focused control. A link keeps its own Enter, and in a\nmulti-line editor Return inserts the newline while the keypad's Enter\nactivates the button.\n---\n\n\n### **Events:**\n - **vf-close** - Dialog closed. Detail `{ reason: 'escape' | 'close' | 'outside' }` — `'outside'` only under `light-dismiss`.\n\n### **Slots:**\n - _default_ - Default slot: dialog body content.\n- **buttons** - Optional action buttons. Rendered as a bottom-right `vf-button-group` (equal-width, faces aligned); the footer only takes space when the slot is populated.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 tile, one black pixel at the origin (`vfDots`; override the whole pattern like `--vf-desktop-pattern`) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-scrollbar-thumb** - scrollbar thumb/elevator (white) _(default: undefined)_\n- **--vf-scrollbar-track** - the scroll trough's base color under the dot-dither (white) _(default: undefined)_\n\n### **CSS Parts:**\n - **frame** - The outer frame (the double frame's 1px rule; the bar and the inner band sit inside it).\n- **title-bar** - The striped title bar (default chrome only).\n- **title** - The centered title patch (or the plain-frame heading).\n- **close-box** - The close widget (`closable`, default chrome only).\n- **body** - The white content area.\n- **content** - The scrolling region inside the body (heading + slotted content, not the footer). Inert while the content fits; over-stuffed, it scrolls under a System 7 rail and becomes a keyboard stop.\n- **footer** - The action row wrapping the buttons.\n- **buttons** - The button group inside the footer.",
|
|
232
257
|
"attributes": [
|
|
233
258
|
{
|
|
234
259
|
"name": "heading",
|
|
@@ -301,6 +326,11 @@
|
|
|
301
326
|
"name": "left",
|
|
302
327
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
303
328
|
"values": []
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"name": "fixed",
|
|
332
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
333
|
+
"values": []
|
|
304
334
|
}
|
|
305
335
|
],
|
|
306
336
|
"references": []
|
|
@@ -353,6 +383,11 @@
|
|
|
353
383
|
"name": "left",
|
|
354
384
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
355
385
|
"values": []
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"name": "fixed",
|
|
389
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
390
|
+
"values": []
|
|
356
391
|
}
|
|
357
392
|
],
|
|
358
393
|
"references": []
|
|
@@ -420,6 +455,11 @@
|
|
|
420
455
|
"name": "left",
|
|
421
456
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
422
457
|
"values": []
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"name": "fixed",
|
|
461
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
462
|
+
"values": []
|
|
423
463
|
}
|
|
424
464
|
],
|
|
425
465
|
"references": []
|
|
@@ -447,6 +487,11 @@
|
|
|
447
487
|
"name": "left",
|
|
448
488
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
449
489
|
"values": []
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"name": "fixed",
|
|
493
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
494
|
+
"values": []
|
|
450
495
|
}
|
|
451
496
|
],
|
|
452
497
|
"references": []
|
|
@@ -489,6 +534,11 @@
|
|
|
489
534
|
"name": "left",
|
|
490
535
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
491
536
|
"values": []
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
"name": "fixed",
|
|
540
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
541
|
+
"values": []
|
|
492
542
|
}
|
|
493
543
|
],
|
|
494
544
|
"references": []
|
|
@@ -521,6 +571,11 @@
|
|
|
521
571
|
"name": "left",
|
|
522
572
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
523
573
|
"values": []
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
"name": "fixed",
|
|
577
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
578
|
+
"values": []
|
|
524
579
|
}
|
|
525
580
|
],
|
|
526
581
|
"references": []
|
|
@@ -558,6 +613,11 @@
|
|
|
558
613
|
"name": "left",
|
|
559
614
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
560
615
|
"values": []
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
"name": "fixed",
|
|
619
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
620
|
+
"values": []
|
|
561
621
|
}
|
|
562
622
|
],
|
|
563
623
|
"references": []
|
|
@@ -590,6 +650,11 @@
|
|
|
590
650
|
"name": "left",
|
|
591
651
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
592
652
|
"values": []
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
"name": "fixed",
|
|
656
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
657
|
+
"values": []
|
|
593
658
|
}
|
|
594
659
|
],
|
|
595
660
|
"references": []
|
|
@@ -637,6 +702,11 @@
|
|
|
637
702
|
"name": "left",
|
|
638
703
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
639
704
|
"values": []
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
"name": "fixed",
|
|
708
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
709
|
+
"values": []
|
|
640
710
|
}
|
|
641
711
|
],
|
|
642
712
|
"references": []
|
|
@@ -669,6 +739,11 @@
|
|
|
669
739
|
"name": "left",
|
|
670
740
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
671
741
|
"values": []
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
"name": "fixed",
|
|
745
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
746
|
+
"values": []
|
|
672
747
|
}
|
|
673
748
|
],
|
|
674
749
|
"references": []
|
|
@@ -702,6 +777,11 @@
|
|
|
702
777
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
703
778
|
"values": []
|
|
704
779
|
},
|
|
780
|
+
{
|
|
781
|
+
"name": "fixed",
|
|
782
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
783
|
+
"values": []
|
|
784
|
+
},
|
|
705
785
|
{
|
|
706
786
|
"name": "value",
|
|
707
787
|
"description": "Current value. Synced on every keystroke and submitted with forms.",
|
|
@@ -778,6 +858,11 @@
|
|
|
778
858
|
"name": "left",
|
|
779
859
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
780
860
|
"values": []
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
"name": "fixed",
|
|
864
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
865
|
+
"values": []
|
|
781
866
|
}
|
|
782
867
|
],
|
|
783
868
|
"references": []
|
|
@@ -815,6 +900,11 @@
|
|
|
815
900
|
"name": "left",
|
|
816
901
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
817
902
|
"values": []
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
"name": "fixed",
|
|
906
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
907
|
+
"values": []
|
|
818
908
|
}
|
|
819
909
|
],
|
|
820
910
|
"references": []
|
|
@@ -852,6 +942,11 @@
|
|
|
852
942
|
"name": "left",
|
|
853
943
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
854
944
|
"values": []
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
"name": "fixed",
|
|
948
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
949
|
+
"values": []
|
|
855
950
|
}
|
|
856
951
|
],
|
|
857
952
|
"references": []
|
|
@@ -885,6 +980,11 @@
|
|
|
885
980
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
886
981
|
"values": []
|
|
887
982
|
},
|
|
983
|
+
{
|
|
984
|
+
"name": "fixed",
|
|
985
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
986
|
+
"values": []
|
|
987
|
+
},
|
|
888
988
|
{
|
|
889
989
|
"name": "disabled",
|
|
890
990
|
"description": "Disables the control: it stops responding and submits no value. Each\ncontrol dims per SPEC §1 (typically the label greys while the 1-bit chrome\nstays black).",
|
|
@@ -926,6 +1026,11 @@
|
|
|
926
1026
|
"name": "left",
|
|
927
1027
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
928
1028
|
"values": []
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
"name": "fixed",
|
|
1032
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1033
|
+
"values": []
|
|
929
1034
|
}
|
|
930
1035
|
],
|
|
931
1036
|
"references": []
|
|
@@ -962,6 +1067,11 @@
|
|
|
962
1067
|
"name": "left",
|
|
963
1068
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
964
1069
|
"values": []
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
"name": "fixed",
|
|
1073
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1074
|
+
"values": []
|
|
965
1075
|
}
|
|
966
1076
|
],
|
|
967
1077
|
"references": []
|
|
@@ -995,6 +1105,11 @@
|
|
|
995
1105
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
996
1106
|
"values": []
|
|
997
1107
|
},
|
|
1108
|
+
{
|
|
1109
|
+
"name": "fixed",
|
|
1110
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1111
|
+
"values": []
|
|
1112
|
+
},
|
|
998
1113
|
{
|
|
999
1114
|
"name": "description",
|
|
1000
1115
|
"description": "Description for the control — hint text, a format, a unit. A host-level\n`aria-describedby` cannot reach a focusable element inside a shadow root,\nso there was structurally no way to describe a field; this property is\nthat channel. It renders as a hidden span in the control's own shadow root\nwith the inner control's `aria-describedby` pointing at it — the\nshadow-internal IDREF idiom `vf-dialog`'s title patch already uses. A\nhost-level `aria-describedby` is bridged into the same span when this\nproperty is empty, and a failing constraint's validationMessage\njoins it too, so AT hears the error where it hears the hint.\n\nHost-role controls get neither half: their `aria-describedby` already\nworks, and their validation message reaches AT the way a native control's\ndoes — `aria-invalid` plus the browser's own validation UI, not AccName.",
|
|
@@ -1031,6 +1146,11 @@
|
|
|
1031
1146
|
"name": "left",
|
|
1032
1147
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
1033
1148
|
"values": []
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
"name": "fixed",
|
|
1152
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1153
|
+
"values": []
|
|
1034
1154
|
}
|
|
1035
1155
|
],
|
|
1036
1156
|
"references": []
|
|
@@ -1079,6 +1199,11 @@
|
|
|
1079
1199
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
1080
1200
|
"values": []
|
|
1081
1201
|
},
|
|
1202
|
+
{
|
|
1203
|
+
"name": "fixed",
|
|
1204
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1205
|
+
"values": []
|
|
1206
|
+
},
|
|
1082
1207
|
{
|
|
1083
1208
|
"name": "disabled",
|
|
1084
1209
|
"description": "Disables the control: it stops responding and submits no value. Each\ncontrol dims per SPEC §1 (typically the label greys while the 1-bit chrome\nstays black).",
|
|
@@ -1135,6 +1260,11 @@
|
|
|
1135
1260
|
"name": "left",
|
|
1136
1261
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
1137
1262
|
"values": []
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
"name": "fixed",
|
|
1266
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1267
|
+
"values": []
|
|
1138
1268
|
}
|
|
1139
1269
|
],
|
|
1140
1270
|
"references": []
|
|
@@ -1183,6 +1313,11 @@
|
|
|
1183
1313
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
1184
1314
|
"values": []
|
|
1185
1315
|
},
|
|
1316
|
+
{
|
|
1317
|
+
"name": "fixed",
|
|
1318
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1319
|
+
"values": []
|
|
1320
|
+
},
|
|
1186
1321
|
{
|
|
1187
1322
|
"name": "description",
|
|
1188
1323
|
"description": "Description for the control — hint text, a format, a unit. A host-level\n`aria-describedby` cannot reach a focusable element inside a shadow root,\nso there was structurally no way to describe a field; this property is\nthat channel. It renders as a hidden span in the control's own shadow root\nwith the inner control's `aria-describedby` pointing at it — the\nshadow-internal IDREF idiom `vf-dialog`'s title patch already uses. A\nhost-level `aria-describedby` is bridged into the same span when this\nproperty is empty, and a failing constraint's validationMessage\njoins it too, so AT hears the error where it hears the hint.\n\nHost-role controls get neither half: their `aria-describedby` already\nworks, and their validation message reaches AT the way a native control's\ndoes — `aria-invalid` plus the browser's own validation UI, not AccName.",
|
|
@@ -1215,6 +1350,11 @@
|
|
|
1215
1350
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
1216
1351
|
"values": []
|
|
1217
1352
|
},
|
|
1353
|
+
{
|
|
1354
|
+
"name": "fixed",
|
|
1355
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1356
|
+
"values": []
|
|
1357
|
+
},
|
|
1218
1358
|
{
|
|
1219
1359
|
"name": "value",
|
|
1220
1360
|
"description": "Current value. Synced on every keystroke and submitted with forms.",
|
|
@@ -1277,6 +1417,11 @@
|
|
|
1277
1417
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
1278
1418
|
"values": []
|
|
1279
1419
|
},
|
|
1420
|
+
{
|
|
1421
|
+
"name": "fixed",
|
|
1422
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1423
|
+
"values": []
|
|
1424
|
+
},
|
|
1280
1425
|
{
|
|
1281
1426
|
"name": "value",
|
|
1282
1427
|
"description": "Current value. Synced on every keystroke and submitted with forms.",
|
|
@@ -1322,7 +1467,7 @@
|
|
|
1322
1467
|
},
|
|
1323
1468
|
{
|
|
1324
1469
|
"name": "vf-window",
|
|
1325
|
-
"description": "`<vf-window>` — the System 7 desktop-window shell.\n\nStriped title bar with optional close box (left) and zoom box (right), a\nsolid-white frame with a hard offset shadow, an optional grow box for\nresizing, optional edge scroll rails (`scrollbars`), and the slim windoid\nchrome (`variant=\"utility\"`). The HIG's window archetypes are parameter\nrecipes over this shell rather than fixed anatomies — the component enables\nHIG compliance, it doesn't enforce it (see docs/LAYOUT.md \"Window archetypes\"):\nthe full document window is `closable zoomable movable resizable\nscrollbars=\"both\"`, a modeless dialog box is `closable movable`, a utility\nwindow is `variant=\"utility\" movable`. Place inside `<vf-desktop>` to get\nclick-to-front stacking and automatic `active` management (utility windows\nfloat above the document tier).\n\nEvery recipe also declares `width` AND `height` (VfSized), in whole\nsystem px — the art's own unit, so the window keeps its proportions to the\nchrome inside it at every display density (a CSS-px size stays put while\nthe components in it triple). A window is a fixed box in both axes, the way\nthe WIND resource carried it: left to layout it takes whatever its\ncontainer or content hands it, which is how a title bar ends up wider than\nthe screen or a dialog reflows as it moves — and a window that grows with\nits body is one the user can neither predict nor (via the grow box) own.\nContent taller than the declared box is clipped at the frame the way the\nclassic content region was; `scrollbars` lets the user reach the rest.\nUnset, the window still renders — normal block layout, as before — and\nsays so once in the console.\n---\n\n\n### **Events:**\n - **vf-close** - Close box clicked. Detail `{ reason: 'close' }` (shape- compatible with vf-dialog's `vf-close`). The window does NOT remove itself; the consumer decides what closing means.\n- **vf-zoom** - Zoom box clicked. Detail `{}`.\n- **vf-resize** - The grow box resized the window. Detail `{ width, height, commit }`, sizes in whole system px: one event per size the drag writes (`commit: false`), fired after the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles — only when it changed the size. Fired by the gesture alone: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.\n\n### **Slots:**\n - _default_ - Default slot: window body content.\n- **status** - Optional status-bar content — the classic bottom readout strip (\"40px x 40px\"): a 1px rule over a 15px white band under the body, body-face text on its native line. Takes no space until populated; a `resizable` window's grow box sits flush in its right end.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 motif, one black pixel at its origin, on a 30-system-px tile (`vfDots`; override the whole tile like `--vf-desktop-pattern` — consumer art renders as a placed tile grid at that same geometry) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-titlebar-height-utility** - the slim `vf-window[variant=\"utility\"]` (windoid) bar — 11px interior + 1px bottom rule, traced from `Windows/utility-window.png` _(default: 12px)_\n- **--vf-status-bar-height** - the status strip: 1px rule + 14px interior — the grow box's own height, so the two compose flush _(default: 15px)_\n- **--vf-line-height** - the body face's native line, which the status strip's text rides (whole-pixel centered in the 14px interior) _(default: 12px)_\n\n### **CSS Parts:**\n - **frame** - The outer chrome frame.\n- **title-bar** - The striped (or dithered) title bar.\n- **title** - The centered title patch (hidden on the utility bar).\n- **close-box** - The close widget (left).\n- **zoom-box** - The zoom widget (right).\n- **body** - The content area.\n- **status-bar** - The bottom status strip (when the `status` slot is populated).\n- **grow-box** - The resize widget (bottom-right, when `resizable`).\n- **viewport** - The built-in scroll area's viewport (when `scrollbars`; re-exported from vf-scroll-area).",
|
|
1470
|
+
"description": "`<vf-window>` — the System 7 desktop-window shell.\n\nStriped title bar with optional close box (left) and zoom box (right), a\nsolid-white frame with a hard offset shadow, an optional grow box for\nresizing, optional edge scroll rails (`scrollbars`), and the slim windoid\nchrome (`variant=\"utility\"`). The HIG's window archetypes are parameter\nrecipes over this shell rather than fixed anatomies — the component enables\nHIG compliance, it doesn't enforce it (see docs/LAYOUT.md \"Window archetypes\"):\nthe full document window is `closable zoomable movable resizable\nscrollbars=\"both\"`, a modeless dialog box is `closable movable`, a utility\nwindow is `variant=\"utility\" movable`. Place inside `<vf-desktop>` to get\nclick-to-front stacking and automatic `active` management (utility windows\nfloat above the document tier).\n\nEvery recipe also declares `width` AND `height` (VfSized), in whole\nsystem px — the art's own unit, so the window keeps its proportions to the\nchrome inside it at every display density (a CSS-px size stays put while\nthe components in it triple). A window is a fixed box in both axes, the way\nthe WIND resource carried it: left to layout it takes whatever its\ncontainer or content hands it, which is how a title bar ends up wider than\nthe screen or a dialog reflows as it moves — and a window that grows with\nits body is one the user can neither predict nor (via the grow box) own.\nContent taller than the declared box is clipped at the frame the way the\nclassic content region was; `scrollbars` lets the user reach the rest.\nUnset, the window still renders — normal block layout, as before — and\nsays so once in the console.\n---\n\n\n### **Events:**\n - **vf-close** - Close box clicked. Detail `{ reason: 'close' }` (shape- compatible with vf-dialog's `vf-close`). The window does NOT remove itself; the consumer decides what closing means.\n- **vf-zoom** - Zoom box clicked. Detail `{}`.\n- **vf-resize** - The grow box resized the window. Detail `{ width, height, commit }`, sizes in whole system px: one event per size the drag writes (`commit: false`), fired after the new box is applied so a handler that measures reads the resized layout, then a final `commit: true` as the gesture settles — only when it changed the size. Fired by the gesture alone: a programmatic `width`/`height` write fires nothing, the way a value set fires no `vf-change`.\n\n### **Slots:**\n - _default_ - Default slot: window body content.\n- **header** - Optional header content — a band between the title bar and the body, the full width of the window (the Finder window's header line): a white interior over a 1px rule, no inset of its own, a positioning anchor for placed children. As tall as its content unless `header-height` states it. Under `scrollbars` the vertical rail begins below it, so the header spans the rail's column. Takes no space until populated.\n- **status** - Optional status-bar content — the classic bottom readout strip (\"40px x 40px\"): a 1px rule over a 15px white band under the body, body-face text on its native line. Takes no space until populated; a `resizable` window's grow box sits flush in its right end.\n\n### **CSS Properties:**\n - **--vf-dots-pattern** - the windoid bar's dot-grid dither — a 2×2 motif, one black pixel at its origin, on a 30-system-px tile (`vfDots`; override the whole tile like `--vf-desktop-pattern` — consumer art renders as a placed tile grid at that same geometry) _(default: undefined)_\n- **--vf-titlebar-height** - window/dialog title bars _(default: undefined)_\n- **--vf-titlebar-height-utility** - the slim `vf-window[variant=\"utility\"]` (windoid) bar — 11px interior + 1px bottom rule, traced from `Windows/utility-window.png` _(default: 12px)_\n- **--vf-status-bar-height** - the status strip: 1px rule + 14px interior — the grow box's own height, so the two compose flush _(default: 15px)_\n- **--vf-line-height** - the body face's native line, which the status strip's text rides (whole-pixel centered in the 14px interior) _(default: 12px)_\n\n### **CSS Parts:**\n - **frame** - The outer chrome frame.\n- **title-bar** - The striped (or dithered) title bar.\n- **title** - The centered title patch (hidden on the utility bar).\n- **close-box** - The close widget (left).\n- **zoom-box** - The zoom widget (right).\n- **body** - The content area.\n- **header** - The header strip between the title bar and the body (when the `header` slot is populated).\n- **status-bar** - The bottom status strip (when the `status` slot is populated).\n- **grow-box** - The resize widget (bottom-right, when `resizable`).\n- **viewport** - The built-in scroll area's viewport (when `scrollbars`; re-exported from vf-scroll-area).",
|
|
1326
1471
|
"attributes": [
|
|
1327
1472
|
{
|
|
1328
1473
|
"name": "variant",
|
|
@@ -1388,6 +1533,11 @@
|
|
|
1388
1533
|
{ "name": "both" }
|
|
1389
1534
|
]
|
|
1390
1535
|
},
|
|
1536
|
+
{
|
|
1537
|
+
"name": "header-height",
|
|
1538
|
+
"description": "The header's height in whole system px, rule included — the way every\nkit bar counts its rule (an 18px title bar is 17 + 1). Unset, the header\nis as tall as what is slotted into it, plus the rule. Only matters while\nthe `header` slot is populated.",
|
|
1539
|
+
"values": [{ "name": "" }]
|
|
1540
|
+
},
|
|
1391
1541
|
{
|
|
1392
1542
|
"name": "width",
|
|
1393
1543
|
"description": "Width in whole system px. What the number means is the component's own\naffair — the measure a paragraph wraps to, a caption column, a window's\nbox — but the mechanics are shared: the length lands on the host as a\nlive `calc(var(--vf-scale, 1) * Npx)`, so the box scales with the\ndisplay and sits on the device-pixel grid by construction. Remove it\nand the width returns to layout.",
|
|
@@ -1407,6 +1557,11 @@
|
|
|
1407
1557
|
"name": "left",
|
|
1408
1558
|
"description": "Offset from the left of the positioning parent, in whole system px.\nSee top.",
|
|
1409
1559
|
"values": []
|
|
1560
|
+
},
|
|
1561
|
+
{
|
|
1562
|
+
"name": "fixed",
|
|
1563
|
+
"description": "Hold the placement against the visible region of the nearest scrolling\nancestor: the element keeps its stated `top`/`left` while the content\nscrolls under it, and the flag alone places it at (0,0). It comes\nbefore the flow content in its parent. Where nothing scrolls it renders\nexactly as placed.",
|
|
1564
|
+
"values": []
|
|
1410
1565
|
}
|
|
1411
1566
|
],
|
|
1412
1567
|
"references": []
|