vintage-frames 0.5.6 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +24 -43
- package/dist/components/vf-scroll-area.d.ts +19 -10
- package/dist/components/vf-scroll-area.js +31 -21
- package/dist/components/vf-window.d.ts +3 -10
- package/dist/components/vf-window.js +5 -13
- package/dist/scroll-rail.d.ts +10 -0
- package/dist/scroll-rail.js +3 -0
- package/dist/styles/recipes/scroll-rail.js +8 -1
- package/docs/SPEC.md +10 -9
- package/editor/vscode.html-custom-data.json +2 -12
- package/editor/web-types.json +4 -24
- package/package.json +1 -1
package/custom-elements.json
CHANGED
|
@@ -10756,7 +10756,7 @@
|
|
|
10756
10756
|
"declarations": [
|
|
10757
10757
|
{
|
|
10758
10758
|
"kind": "class",
|
|
10759
|
-
"description": "`<vf-scroll-area>` — a container whose scrollbars look like System 7.\n\nWhite box with a 1px black frame, an inner scrolling viewport, and scroll\nrails the kit draws itself as shadow DOM (the shared `vfScrollRail` recipe):\nboxed arrow buttons at each end, a loose 1-bit dot-dither trough, and the\nclassic fixed 16px thumb. The native scrollbar is hidden, never the native\nscrolling — wheel, trackpad momentum, keyboard, touch and assistive-tech\nscrolling stay the platform's, and ScrollRailController keeps the\nrail in sync while driving the classic interactions (thumb drag, trough\npaging, arrow stepping with auto-repeat). Every engine renders the same\nrail — there is no Firefox fallback skin anymore.\n\nEach reserved scroll rail is a permanent placeholder: arrow buttons on an\nempty white channel sit in the gutter even when the content fits, the\ndither and thumb filling in only once that axis overflows (System 7 drew\nan active window's no-overflow bar as arrows on a bare channel; driven\nby ScrollStateController). Which rails are reserved is set by\naxis; when both are reserved the bottom-right corner joins them,\nand corner reserves that cell on a single-axis rail too — the\nrail stops 15px short of the frame, for a grow box to land in.\n\nSize the host (width/height) from the outside; the viewport fills it
|
|
10759
|
+
"description": "`<vf-scroll-area>` — a container whose scrollbars look like System 7.\n\nWhite box with a 1px black frame, an inner scrolling viewport, and scroll\nrails the kit draws itself as shadow DOM (the shared `vfScrollRail` recipe):\nboxed arrow buttons at each end, a loose 1-bit dot-dither trough, and the\nclassic fixed 16px thumb. The native scrollbar is hidden, never the native\nscrolling — wheel, trackpad momentum, keyboard, touch and assistive-tech\nscrolling stay the platform's, and ScrollRailController keeps the\nrail in sync while driving the classic interactions (thumb drag, trough\npaging, arrow stepping with auto-repeat). Every engine renders the same\nrail — there is no Firefox fallback skin anymore.\n\nEach reserved scroll rail is a permanent placeholder: arrow buttons on an\nempty white channel sit in the gutter even when the content fits, the\ndither and thumb filling in only once that axis overflows (System 7 drew\nan active window's no-overflow bar as arrows on a bare channel; driven\nby ScrollStateController). Which rails are reserved is set by\naxis; when both are reserved the bottom-right corner joins them,\nand corner reserves that cell on a single-axis rail too — the\nrail stops 15px short of the frame, for a grow box to land in.\n\nSize the host (width/height) from the outside; the viewport fills it.\nContent runs to the frame and the rails — the area adds no inset of its\nown; an inset is the content's (a `vf-stack pad`).\n\nThe scrolled plane sizes to its content — never narrower than the\nviewport, as wide as content that cannot wrap — so the rails follow a row\nthat grows sideways the way they follow copy that grows down, and a\n`slotchange` re-measures. measure covers a scroll range that\nchanges with no box changing (a placed child moved through `top`/`left`).",
|
|
10760
10760
|
"name": "VfScrollArea",
|
|
10761
10761
|
"cssProperties": [
|
|
10762
10762
|
{
|
|
@@ -10807,17 +10807,6 @@
|
|
|
10807
10807
|
"attribute": "axis",
|
|
10808
10808
|
"reflects": true
|
|
10809
10809
|
},
|
|
10810
|
-
{
|
|
10811
|
-
"kind": "field",
|
|
10812
|
-
"name": "flush",
|
|
10813
|
-
"type": {
|
|
10814
|
-
"text": "boolean"
|
|
10815
|
-
},
|
|
10816
|
-
"default": "false",
|
|
10817
|
-
"description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
|
|
10818
|
-
"attribute": "flush",
|
|
10819
|
-
"reflects": true
|
|
10820
|
-
},
|
|
10821
10810
|
{
|
|
10822
10811
|
"kind": "field",
|
|
10823
10812
|
"name": "corner",
|
|
@@ -10900,6 +10889,27 @@
|
|
|
10900
10889
|
],
|
|
10901
10890
|
"description": "Moves keyboard focus to the scrolling viewport — the focusable element\nlives inside the shadow root, where the platform's `focus()` can't reach\n(no `delegatesFocus`, no host tabindex), so `vf-label for` and consumer\nscripts silently no-opped on the host."
|
|
10902
10891
|
},
|
|
10892
|
+
{
|
|
10893
|
+
"kind": "method",
|
|
10894
|
+
"name": "measure",
|
|
10895
|
+
"return": {
|
|
10896
|
+
"type": {
|
|
10897
|
+
"text": "void"
|
|
10898
|
+
}
|
|
10899
|
+
},
|
|
10900
|
+
"description": "Re-measure overflow and re-sync the rails. The area tracks its content's\nbox and its slot by itself; call this for a scroll range that changes\nwith no box changing anywhere — a placed child moved through\n`top`/`left`, a transform — the way the kit's own fields re-measure on\ninput."
|
|
10901
|
+
},
|
|
10902
|
+
{
|
|
10903
|
+
"kind": "method",
|
|
10904
|
+
"name": "_onSlotChange",
|
|
10905
|
+
"privacy": "private",
|
|
10906
|
+
"return": {
|
|
10907
|
+
"type": {
|
|
10908
|
+
"text": "void"
|
|
10909
|
+
}
|
|
10910
|
+
},
|
|
10911
|
+
"description": "Content added or removed under the fixed box — re-measure."
|
|
10912
|
+
},
|
|
10903
10913
|
{
|
|
10904
10914
|
"kind": "field",
|
|
10905
10915
|
"name": "top",
|
|
@@ -10937,15 +10947,6 @@
|
|
|
10937
10947
|
"description": "Which scroll rails to reserve as permanent placeholders: `vertical`\n(default), `horizontal`, or `both`. Each reserved rail shows an empty white\nchannel until its axis overflows; the unreserved axis still scrolls\nnatively (wheel, keyboard) but draws no rail.",
|
|
10938
10948
|
"fieldName": "axis"
|
|
10939
10949
|
},
|
|
10940
|
-
{
|
|
10941
|
-
"name": "flush",
|
|
10942
|
-
"type": {
|
|
10943
|
-
"text": "boolean"
|
|
10944
|
-
},
|
|
10945
|
-
"default": "false",
|
|
10946
|
-
"description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
|
|
10947
|
-
"fieldName": "flush"
|
|
10948
|
-
},
|
|
10949
10950
|
{
|
|
10950
10951
|
"name": "corner",
|
|
10951
10952
|
"type": {
|
|
@@ -17325,24 +17326,13 @@
|
|
|
17325
17326
|
"description": "See minWidth. Unbounded by default.",
|
|
17326
17327
|
"attribute": "max-height"
|
|
17327
17328
|
},
|
|
17328
|
-
{
|
|
17329
|
-
"kind": "field",
|
|
17330
|
-
"name": "flush",
|
|
17331
|
-
"type": {
|
|
17332
|
-
"text": "boolean"
|
|
17333
|
-
},
|
|
17334
|
-
"default": "false",
|
|
17335
|
-
"description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
|
|
17336
|
-
"attribute": "flush",
|
|
17337
|
-
"reflects": true
|
|
17338
|
-
},
|
|
17339
17329
|
{
|
|
17340
17330
|
"kind": "field",
|
|
17341
17331
|
"name": "scrollbars",
|
|
17342
17332
|
"type": {
|
|
17343
17333
|
"text": "'vertical' | 'horizontal' | 'both' | undefined"
|
|
17344
17334
|
},
|
|
17345
|
-
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\
|
|
17335
|
+
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nContent runs to the frame and the rails; an inset is the content's own.\nThe slotted composition (SPEC §5 vf-scroll-area) still works for a well\nplaced inside the body.",
|
|
17346
17336
|
"attribute": "scrollbars",
|
|
17347
17337
|
"reflects": true
|
|
17348
17338
|
},
|
|
@@ -17738,21 +17728,12 @@
|
|
|
17738
17728
|
"description": "See minWidth. Unbounded by default.",
|
|
17739
17729
|
"fieldName": "maxHeight"
|
|
17740
17730
|
},
|
|
17741
|
-
{
|
|
17742
|
-
"name": "flush",
|
|
17743
|
-
"type": {
|
|
17744
|
-
"text": "boolean"
|
|
17745
|
-
},
|
|
17746
|
-
"default": "false",
|
|
17747
|
-
"description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
|
|
17748
|
-
"fieldName": "flush"
|
|
17749
|
-
},
|
|
17750
17731
|
{
|
|
17751
17732
|
"name": "scrollbars",
|
|
17752
17733
|
"type": {
|
|
17753
17734
|
"text": "'vertical' | 'horizontal' | 'both' | undefined"
|
|
17754
17735
|
},
|
|
17755
|
-
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\
|
|
17736
|
+
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nContent runs to the frame and the rails; an inset is the content's own.\nThe slotted composition (SPEC §5 vf-scroll-area) still works for a well\nplaced inside the body.",
|
|
17756
17737
|
"fieldName": "scrollbars"
|
|
17757
17738
|
},
|
|
17758
17739
|
{
|
|
@@ -22,9 +22,15 @@ declare const VfScrollArea_base: (new (...args: any[]) => import("../position.js
|
|
|
22
22
|
* and {@link corner} reserves that cell on a single-axis rail too — the
|
|
23
23
|
* rail stops 15px short of the frame, for a grow box to land in.
|
|
24
24
|
*
|
|
25
|
-
* Size the host (width/height) from the outside; the viewport fills it.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Size the host (width/height) from the outside; the viewport fills it.
|
|
26
|
+
* Content runs to the frame and the rails — the area adds no inset of its
|
|
27
|
+
* own; an inset is the content's (a `vf-stack pad`).
|
|
28
|
+
*
|
|
29
|
+
* The scrolled plane sizes to its content — never narrower than the
|
|
30
|
+
* viewport, as wide as content that cannot wrap — so the rails follow a row
|
|
31
|
+
* that grows sideways the way they follow copy that grows down, and a
|
|
32
|
+
* `slotchange` re-measures. {@link measure} covers a scroll range that
|
|
33
|
+
* changes with no box changing (a placed child moved through `top`/`left`).
|
|
28
34
|
*
|
|
29
35
|
* @slot - Scrollable content.
|
|
30
36
|
* @csspart viewport - The inner scrolling container.
|
|
@@ -43,13 +49,6 @@ export declare class VfScrollArea extends VfScrollArea_base {
|
|
|
43
49
|
* natively (wheel, keyboard) but draws no rail.
|
|
44
50
|
*/
|
|
45
51
|
axis: 'vertical' | 'horizontal' | 'both';
|
|
46
|
-
/**
|
|
47
|
-
* Drop the viewport's 8px inset: slotted content and the (0,0) of placed
|
|
48
|
-
* children sit at the frame's inner edge, one system px from the frame
|
|
49
|
-
* box (the border-floor compensation stays — see the viewport rule).
|
|
50
|
-
* `vf-window[scrollbars]` forwards its own `flush` here.
|
|
51
|
-
*/
|
|
52
|
-
flush: boolean;
|
|
53
52
|
/**
|
|
54
53
|
* Reserve the bottom-right corner cell on a single-axis rail: the rail
|
|
55
54
|
* stops 15px short of the frame and the viewport spans the rest, leaving
|
|
@@ -83,6 +82,16 @@ export declare class VfScrollArea extends VfScrollArea_base {
|
|
|
83
82
|
* scripts silently no-opped on the host.
|
|
84
83
|
*/
|
|
85
84
|
focus(options?: FocusOptions): void;
|
|
85
|
+
/**
|
|
86
|
+
* Re-measure overflow and re-sync the rails. The area tracks its content's
|
|
87
|
+
* box and its slot by itself; call this for a scroll range that changes
|
|
88
|
+
* with no box changing anywhere — a placed child moved through
|
|
89
|
+
* `top`/`left`, a transform — the way the kit's own fields re-measure on
|
|
90
|
+
* input.
|
|
91
|
+
*/
|
|
92
|
+
measure(): void;
|
|
93
|
+
/** Content added or removed under the fixed box — re-measure. */
|
|
94
|
+
private _onSlotChange;
|
|
86
95
|
static styles: import("lit").CSSResult[];
|
|
87
96
|
protected render(): import("lit-html").TemplateResult<1>;
|
|
88
97
|
}
|
|
@@ -13,7 +13,7 @@ import { property as h, query as g, state as _ } from "lit/decorators.js";
|
|
|
13
13
|
//#region src/components/vf-scroll-area.ts
|
|
14
14
|
var v = class extends r(d) {
|
|
15
15
|
constructor(...e) {
|
|
16
|
-
super(...e), this.scale = new t(this), this.gridSnap = new s(this), this.axis = "vertical", this.
|
|
16
|
+
super(...e), this.scale = new t(this), this.gridSnap = new s(this), this.axis = "vertical", this.corner = !1, this.label = "", this.scrollState = new c(this, () => this.viewport, () => this.content, (e) => {
|
|
17
17
|
this._scrollable = e.x || e.y;
|
|
18
18
|
}), this.rail = new l(this, {
|
|
19
19
|
getScroll: () => this.viewport,
|
|
@@ -23,6 +23,12 @@ var v = class extends r(d) {
|
|
|
23
23
|
focus(e) {
|
|
24
24
|
this.viewport?.focus(e);
|
|
25
25
|
}
|
|
26
|
+
measure() {
|
|
27
|
+
this.scrollState.measure(), this.rail.sync();
|
|
28
|
+
}
|
|
29
|
+
_onSlotChange() {
|
|
30
|
+
this.measure();
|
|
31
|
+
}
|
|
26
32
|
static {
|
|
27
33
|
this.styles = [
|
|
28
34
|
i,
|
|
@@ -62,23 +68,15 @@ var v = class extends r(d) {
|
|
|
62
68
|
recipe (.vf-scroll) and the reservation is the rail element, not a
|
|
63
69
|
native gutter. The unreserved axis still scrolls, railless.
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
fractional frame border to whole CSS px, and the mod()
|
|
67
|
-
exactly what they floored away
|
|
68
|
-
of placed children) sits exactly
|
|
69
|
-
every scale
|
|
70
|
-
|
|
71
|
+
No inset of its own — only the border-floor compensation: engines
|
|
72
|
+
floor the fractional frame border to whole CSS px, and the mod()
|
|
73
|
+
term is exactly what they floored away, so slotted content (and
|
|
74
|
+
the (0,0) of placed children) sits exactly one system px from the
|
|
75
|
+
frame box at every scale (mod is 0 at whole scales). An inset is
|
|
76
|
+
the content's (a vf-stack pad). */
|
|
71
77
|
overflow: auto;
|
|
72
78
|
min-width: 0;
|
|
73
79
|
min-height: 0;
|
|
74
|
-
padding: calc(
|
|
75
|
-
var(--vf-scale, 1) * 8px + mod(var(--vf-scale, 1) * 1px, 1px)
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
/* flush: the inset goes, the border-floor term stays — content and the
|
|
79
|
-
(0,0) of placed children sit at the frame's inner edge, exactly one
|
|
80
|
-
system px from the frame box at every scale. */
|
|
81
|
-
:host([flush]) .viewport {
|
|
82
80
|
padding: mod(var(--vf-scale, 1) * 1px, 1px);
|
|
83
81
|
}
|
|
84
82
|
/* A single-axis rail with a reserved corner: the viewport spans the
|
|
@@ -105,10 +103,23 @@ var v = class extends r(d) {
|
|
|
105
103
|
(src/position.ts). It must be THIS wrapper and not .box: .content
|
|
106
104
|
rides the scroll, so positioned children travel with the content —
|
|
107
105
|
anchored to .box they would hang fixed over the rail while the plane
|
|
108
|
-
moved beneath them.
|
|
109
|
-
|
|
106
|
+
moved beneath them. (0,0) is where flow content starts.
|
|
107
|
+
|
|
108
|
+
Sized to its content: never narrower than the viewport, and as wide
|
|
109
|
+
as content that cannot wrap. A block's auto width is the viewport's
|
|
110
|
+
whatever the row inside does, so the controllers' ResizeObserver on
|
|
111
|
+
this box never saw horizontal growth — a row gaining a cell left the
|
|
112
|
+
thumb and the overflow state stale until something else re-measured.
|
|
113
|
+
fit-content is the shrink-to-fit width, not max-content: copy wraps
|
|
114
|
+
exactly as before, and only unwrappable width (a nowrap row, a
|
|
115
|
+
fixed-width box, a wide image) grows the plane — precisely the
|
|
116
|
+
content that overflows sideways. It also makes this box the
|
|
117
|
+
containing block of a sticky child, so a sticky left: 0 strip holds
|
|
118
|
+
across the scroll. */
|
|
110
119
|
.content {
|
|
111
120
|
position: relative;
|
|
121
|
+
width: fit-content;
|
|
122
|
+
min-width: 100%;
|
|
112
123
|
}
|
|
113
124
|
/* Focusable so keyboard users can scroll; inset ring to stay in-box. */
|
|
114
125
|
.viewport:focus-visible {
|
|
@@ -129,7 +140,9 @@ var v = class extends r(d) {
|
|
|
129
140
|
role=${this.label ? "region" : this._scrollable ? "group" : m}
|
|
130
141
|
aria-label=${this.label || m}
|
|
131
142
|
>
|
|
132
|
-
<div class="content"
|
|
143
|
+
<div class="content">
|
|
144
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
145
|
+
</div>
|
|
133
146
|
</div>
|
|
134
147
|
${e ? u(this.rail, "vertical") : m}
|
|
135
148
|
${t ? u(this.rail, "horizontal") : m}
|
|
@@ -141,9 +154,6 @@ var v = class extends r(d) {
|
|
|
141
154
|
n([h({ reflect: !0 })], v.prototype, "axis", void 0), n([h({
|
|
142
155
|
type: Boolean,
|
|
143
156
|
reflect: !0
|
|
144
|
-
})], v.prototype, "flush", void 0), n([h({
|
|
145
|
-
type: Boolean,
|
|
146
|
-
reflect: !0
|
|
147
157
|
})], v.prototype, "corner", void 0), n([h()], v.prototype, "label", void 0), n([g(".viewport")], v.prototype, "viewport", void 0), n([g(".content")], v.prototype, "content", void 0), n([_()], v.prototype, "_scrollable", void 0), v = n([e("vf-scroll-area")], v);
|
|
148
158
|
//#endregion
|
|
149
159
|
export { v as VfScrollArea };
|
|
@@ -120,13 +120,6 @@ export declare class VfWindow extends VfWindow_base {
|
|
|
120
120
|
maxWidth?: number | null;
|
|
121
121
|
/** See {@link minWidth}. Unbounded by default. */
|
|
122
122
|
maxHeight?: number | null;
|
|
123
|
-
/**
|
|
124
|
-
* Remove the default 12px body padding. Under `scrollbars` the body has
|
|
125
|
-
* none to remove, so the flag reaches the built-in scroll area instead
|
|
126
|
-
* and drops its viewport's 8px inset: content runs to the frame and the
|
|
127
|
-
* rails, and the (0,0) of placed children is the content region's corner.
|
|
128
|
-
*/
|
|
129
|
-
flush: boolean;
|
|
130
123
|
/**
|
|
131
124
|
* Put System 7 scroll rails on the window edge — the classic document
|
|
132
125
|
* window. The body slot renders inside a built-in `vf-scroll-area` pulled
|
|
@@ -136,9 +129,9 @@ export declare class VfWindow extends VfWindow_base {
|
|
|
136
129
|
* a populated status strip holds the grow box, when the rail runs edge to
|
|
137
130
|
* edge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the
|
|
138
131
|
* `heading` names the scroll region; the viewport part is re-exported.
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
132
|
+
* Content runs to the frame and the rails; an inset is the content's own.
|
|
133
|
+
* The slotted composition (SPEC §5 vf-scroll-area) still works for a well
|
|
134
|
+
* placed inside the body.
|
|
142
135
|
*/
|
|
143
136
|
scrollbars?: 'vertical' | 'horizontal' | 'both';
|
|
144
137
|
/** Whether the `status` slot has assigned content (drives the strip). */
|
|
@@ -25,7 +25,7 @@ function V(e, t, n) {
|
|
|
25
25
|
}
|
|
26
26
|
var H = 24, U = 8, W = class extends E(c(N)) {
|
|
27
27
|
constructor(...e) {
|
|
28
|
-
super(...e), this.heading = "", this.active = !0, this.closable = !0, this.zoomable = !1, this.movable = !1, this.resizable = !1, this.
|
|
28
|
+
super(...e), this.heading = "", this.active = !0, this.closable = !0, this.zoomable = !1, this.movable = !1, this.resizable = !1, this._hasStatus = !1, this.scale = new n(this), this.gridSnap = new T(this), this.titleCenter = new O(this), this._dotsPattern = "", this.#e = new x(), this.#t = (e, t, n) => {
|
|
29
29
|
let r = a(this.offsetWidth, this);
|
|
30
30
|
return {
|
|
31
31
|
x: Math.min(Math.max(e, H - r), n.width - H),
|
|
@@ -180,17 +180,13 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
180
180
|
/* The positioning anchor for slotted children placed with top/left
|
|
181
181
|
(src/position.ts): coordinates measure from the content region's
|
|
182
182
|
corner — the frame's inner edge, below the title bar — exactly the
|
|
183
|
-
DITL convention.
|
|
184
|
-
|
|
183
|
+
DITL convention. No inset of its own: the classic content region
|
|
184
|
+
had none, and flow content starts at the same corner a placed
|
|
185
|
+
child does. An inset is the content's (a vf-stack pad). */
|
|
185
186
|
position: relative;
|
|
186
187
|
flex: 1 1 auto;
|
|
187
188
|
min-height: 0;
|
|
188
189
|
overflow: hidden;
|
|
189
|
-
padding: calc(var(--vf-scale, 1) * 12px);
|
|
190
|
-
}
|
|
191
|
-
:host([flush]) .body,
|
|
192
|
-
:host([scrollbars]) .body {
|
|
193
|
-
padding: 0;
|
|
194
190
|
}
|
|
195
191
|
/* The edge-rail composition pulls the scroll area one system px OUT of
|
|
196
192
|
the body on every side so its frame border repaints the window's
|
|
@@ -387,7 +383,6 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
387
383
|
<vf-scroll-area
|
|
388
384
|
class="edge-scroll"
|
|
389
385
|
axis=${this.scrollbars}
|
|
390
|
-
?flush=${this.flush}
|
|
391
386
|
?corner=${this.resizable && !this._hasStatus}
|
|
392
387
|
label=${this.heading || I}
|
|
393
388
|
exportparts="viewport"
|
|
@@ -441,9 +436,6 @@ o([L({ reflect: !0 })], W.prototype, "variant", void 0), o([L()], W.prototype, "
|
|
|
441
436
|
})], W.prototype, "maxWidth", void 0), o([L({
|
|
442
437
|
type: Number,
|
|
443
438
|
attribute: "max-height"
|
|
444
|
-
})], W.prototype, "maxHeight", void 0), o([L({
|
|
445
|
-
type: Boolean,
|
|
446
|
-
reflect: !0
|
|
447
|
-
})], W.prototype, "flush", void 0), o([L({ reflect: !0 })], W.prototype, "scrollbars", void 0), o([R()], W.prototype, "_hasStatus", void 0), W = o([t("vf-window")], W);
|
|
439
|
+
})], W.prototype, "maxHeight", void 0), o([L({ reflect: !0 })], W.prototype, "scrollbars", void 0), o([R()], W.prototype, "_hasStatus", void 0), W = o([t("vf-window")], W);
|
|
448
440
|
//#endregion
|
|
449
441
|
export { W as VfWindow };
|
package/dist/scroll-rail.d.ts
CHANGED
|
@@ -64,6 +64,16 @@ export declare class ScrollRailController implements ReactiveController {
|
|
|
64
64
|
constructor(host: ReactiveControllerHost & HTMLElement & {
|
|
65
65
|
readonly renderRoot: HTMLElement | DocumentFragment;
|
|
66
66
|
}, opts: ScrollRailOptions);
|
|
67
|
+
/**
|
|
68
|
+
* Wire on connect as well as on update. A host the desktop re-inserts (its
|
|
69
|
+
* DOM-order sync moves a raised window, src/components/vf-desktop.ts) is
|
|
70
|
+
* disconnected and reconnected with no update scheduled — Lit's
|
|
71
|
+
* `enableUpdating` is a no-op after the first — so `hostUpdated` alone
|
|
72
|
+
* left the rail with no scroll listener, no observers, and whatever
|
|
73
|
+
* degenerate state it wrote while hidden. Both calls no-op before the
|
|
74
|
+
* first render, when the scroller query is still null.
|
|
75
|
+
*/
|
|
76
|
+
hostConnected(): void;
|
|
67
77
|
hostUpdated(): void;
|
|
68
78
|
hostDisconnected(): void;
|
|
69
79
|
/** (Re-)attach the scroll listener and observers to the current scroller. */
|
package/dist/scroll-rail.js
CHANGED
|
@@ -77,6 +77,9 @@ var h = 16, g = 16, _ = 14, v = 66, y = class {
|
|
|
77
77
|
!t || e.pointerId !== t.pointerId || (this.press = null, this.clearRepeat(), t.button?.removeAttribute("data-pressed"), t.rail.hasPointerCapture(e.pointerId) && t.rail.releasePointerCapture(e.pointerId));
|
|
78
78
|
}, t.addController(this);
|
|
79
79
|
}
|
|
80
|
+
hostConnected() {
|
|
81
|
+
this.wire(), this.sync();
|
|
82
|
+
}
|
|
80
83
|
hostUpdated() {
|
|
81
84
|
this.wire(), this.sync();
|
|
82
85
|
}
|
|
@@ -5,9 +5,16 @@ import { css as a, unsafeCSS as o } from "lit";
|
|
|
5
5
|
var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u)), f = a`
|
|
6
6
|
/* Hide the native bar, keep the native scrolling. Both lines ship:
|
|
7
7
|
scrollbar-width reaches Chromium 121+/Firefox/Safari 18.2+, the pseudo
|
|
8
|
-
covers older WebKit — the last scrollbar pseudo left in the kit.
|
|
8
|
+
covers older WebKit — the last scrollbar pseudo left in the kit.
|
|
9
|
+
|
|
10
|
+
No rubber-band: a bounce moves content the rail cannot express (the
|
|
11
|
+
thumb is clamped to its track), and 1-bit has no smooth motion — System
|
|
12
|
+
7 stopped hard. none rather than contain, which keeps the bounce. The
|
|
13
|
+
cost is scroll chaining, which a page that scrolls restores through the
|
|
14
|
+
scroller's part (the reference page does — SPEC §4 vfScrollRail). */
|
|
9
15
|
.vf-scroll {
|
|
10
16
|
scrollbar-width: none;
|
|
17
|
+
overscroll-behavior: none;
|
|
11
18
|
}
|
|
12
19
|
.vf-scroll::-webkit-scrollbar {
|
|
13
20
|
display: none;
|
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
|
|
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`.
|
|
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.
|
|
@@ -156,7 +156,7 @@ The span construction stays load-bearing for the CSS-repeated underlays beneath
|
|
|
156
156
|
- The title's clearance for anything else in the bar is `--vf-title-inset` (default 16px); `vf-window` sets 60px so an ellipsized title can't run under its close/zoom widgets.
|
|
157
157
|
- `touch-action` is deliberately NOT in the recipe: `vf-dialog`'s bar is always a drag handle, `vf-window`'s only when `[movable]`, and suppressing touch scrolling on a bar that can't be dragged would be a behavior change.
|
|
158
158
|
- The markup and the four DragController pointer bindings that go with it are `chromeTitleBar()` in `src/chrome.ts` (internal — it bakes in the kit's own `part` names, so it is not part of the public toolkit). Its third argument picks the texture layer class: `'vf-stripes'` (default) or `'vf-dots'` (`vf-window variant="utility"`).
|
|
159
|
-
- `vfScrollRail` — the System 7 scroll rail, drawn by the kit as ordinary shadow DOM (`src/scroll-rail.ts` renders the subtree and drives it; this is its skin). The native scrollbar is hidden — `scrollbar-width: none` plus a `::-webkit-scrollbar { display: none }` for older WebKit, the last scrollbar pseudo in the kit — never the native scrolling: wheel, trackpad momentum, keyboard, touch and AT scrolling stay the platform's, and `ScrollRailController` syncs the rail to `scrollTop`/`scrollLeft` while driving the classic interactions (thumb drag; trough paging by a viewport minus one line, repeating until the thumb reaches the pointer; arrow lines with auto-repeat and the hollow→solid pressed glyph). Geometry: the classic 16px cell whose outermost line is the component's own 1px frame border — the rail element is the 15 inside it, a 1px divider on the content side plus the 14px channel; 15px arrow cells whose glyphs are the 16-unit sprites windowed to their 14×14 interior; the **fixed** 16px thumb (System 7's box, never proportional; whole-system-px travel); the 25% dot-dither trough rendered as a whole-surface `tileRaster` (1-bit at every scale, zoom included) on `var(--vf-scrollbar-track)`. States key off the attributes `ScrollStateController` writes — an idle axis keeps its arrows (drawn but inert) and drops the dither and thumb, an inactive window blanks both axes to the bare white channel, arrows included, a degenerate track drops the thumb, then everything (the Control Manager's decision table). The rail subtree is `aria-hidden` and pointer-only; scrolling's keyboard/AT contract stays on the viewport. Every engine renders the same rail — no Firefox fallback — and it is pixel-asserted headless (`npm run verify:scrollbars`), which the `::-webkit-scrollbar` skin it replaced never could be.
|
|
159
|
+
- `vfScrollRail` — the System 7 scroll rail, drawn by the kit as ordinary shadow DOM (`src/scroll-rail.ts` renders the subtree and drives it; this is its skin). The native scrollbar is hidden — `scrollbar-width: none` plus a `::-webkit-scrollbar { display: none }` for older WebKit, the last scrollbar pseudo in the kit — never the native scrolling: wheel, trackpad momentum, keyboard, touch and AT scrolling stay the platform's — minus the rubber-band: the scroller sets `overscroll-behavior: none` (a bounce moves content the rail cannot express; System 7 stopped hard), which also cuts scroll chaining, so a document that scrolls restores it where it wants it through the scroller's part (`viewport`, `textarea`, `list`, `content`; the reference page does, dialogs excepted) — and `ScrollRailController` syncs the rail to `scrollTop`/`scrollLeft` while driving the classic interactions (thumb drag; trough paging by a viewport minus one line, repeating until the thumb reaches the pointer; arrow lines with auto-repeat and the hollow→solid pressed glyph). Geometry: the classic 16px cell whose outermost line is the component's own 1px frame border — the rail element is the 15 inside it, a 1px divider on the content side plus the 14px channel; 15px arrow cells whose glyphs are the 16-unit sprites windowed to their 14×14 interior; the **fixed** 16px thumb (System 7's box, never proportional; whole-system-px travel); the 25% dot-dither trough rendered as a whole-surface `tileRaster` (1-bit at every scale, zoom included) on `var(--vf-scrollbar-track)`. States key off the attributes `ScrollStateController` writes — an idle axis keeps its arrows (drawn but inert) and drops the dither and thumb, an inactive window blanks both axes to the bare white channel, arrows included, a degenerate track drops the thumb, then everything (the Control Manager's decision table). The rail subtree is `aria-hidden` and pointer-only; scrolling's keyboard/AT contract stays on the viewport. Every engine renders the same rail — no Firefox fallback — and it is pixel-asserted headless (`npm run verify:scrollbars`), which the `::-webkit-scrollbar` skin it replaced never could be.
|
|
160
160
|
- `vfFocusRing` / `vfFocus` / `vfFocusUnderline` — the two focus indicators. Neither is a System 7 reproduction: the machine drew no keyboard focus at all (see §1), so both are additions rendered in its vocabulary.
|
|
161
161
|
- `vfFocusRing` is the dotted-outline declaration pair — `outline: var(--vf-focus-outline, 1px dotted currentColor)` plus a scaled `--vf-focus-offset` (default +2px) — interpolated into whatever selector a component focuses on; `vfFocus` wraps it as a `.vf-focus:focus-visible` class for the controls where focus and ring share one element. `currentColor`, not black, for the same reason the underline uses it: the ring must invert with the ink it sits on, or the multi-select keyboard cursor disappears riding a selected list row's black bar.
|
|
162
162
|
- `vfFocusUnderline` is the alternative for a control that can carry the mark on its own face: an `::after` on that element, 1 system px tall, spanning its box, dashed 1px on / 1px off via a `repeating-linear-gradient` in `currentColor` — so it inverts to white with the label on a pressed face. The component suppresses the UA outline in the same rule set, and the underlined element needs `position: relative`. Two placements:
|
|
@@ -208,16 +208,16 @@ 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), `
|
|
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
212
|
- **Visual:** `vfChromeFrame` + `vfTitleBar` (§4), plus a full-size flex-column layout on the frame. `display: block`. Sets `--vf-surface: var(--vf-white, #fff)` on itself.
|
|
213
213
|
- Title bar: from `vfTitleBar` — height `var(--vf-titlebar-height, 18px)`, white bg, bottom `1px solid black`, contains `.vf-stripes` layer (only when `active`). `touch-action: none` only when `[movable]`.
|
|
214
214
|
- Title: centered, bold, on a white patch (`padding: 0 6px`; cap band on interior rows 4..12, 7px of white between ink and stripes — see §4 `vfTitleBar` for the traced geometry and the lattice hold) above the stripes, with `--vf-title-inset: 60px` of clearance so it ellipsizes before reaching the widgets. Inactive: no stripes, widgets undrawn (transparent ink — they keep their tab stops; see A11y below), the grow box's nested squares hidden, and every managed scroll rail inside the window blanked (see "always-a-rail" §5 vf-scroll-area) — but the title text stays black (System 7 never grayed the window title).
|
|
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
|
-
- Body:
|
|
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
219
|
- Status bar (`status` slot): the classic bottom readout strip ("40px x 40px") — a 1px black rule (`vfRule`'s `.vf-rule-top`) over a white interior, `--vf-status-bar-height` (15px) in all, the grow box's own height, so a `resizable` window's grow box sits flush in the strip's right end (its top/left borders take over the rule there; the strip reserves 21px of right padding under `resizable` so text clears the cell). Slotted content rides the body face's native 12px line (`--vf-line-height`), whole-pixel centered in the 14px interior, `padding-inline: 6px`, nowrap + clipped. Takes **no space** until the slot is populated (the dialog-footer pattern), and collapses again when it empties. Composes with `scrollbars`: the edge rails' bottom overhang lands its frame line exactly on the strip's rule, so the two never double up — though the grow box then sits in the strip rather than the rail corner cell.
|
|
220
|
-
- Edge scroll rails (if `scrollbars`): the body slot renders inside a shadow `vf-scroll-area` (its `axis` = the attribute's value, `label` = the heading, `viewport` part re-exported) carrying the TeachText composition internally — `calc(100% + 2px·scale)` with `margin: -1px·scale`, one system px under the frame on every side, so the area's own frame border repaints the window's border lines and a `resizable` window's grow box (z-index 1) lands exactly over the rail-corner cell.
|
|
220
|
+
- Edge scroll rails (if `scrollbars`): the body slot renders inside a shadow `vf-scroll-area` (its `axis` = the attribute's value, `label` = the heading, `viewport` part re-exported) carrying the TeachText composition internally — `calc(100% + 2px·scale)` with `margin: -1px·scale`, one system px under the frame on every side, so the area's own frame border repaints the window's border lines and a `resizable` window's grow box (z-index 1) lands exactly over the rail-corner cell. 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
221
|
- Utility variant (`variant="utility"`): the slim windoid bar traced from `Windows/utility-window.png` — `--vf-titlebar-height-utility` (12px = 11px interior + 1px rule), the `vfDots` dither instead of stripes (flush to the side borders — see §4 vfDots), 7×7 widgets (`top: 2px`; close `left: 7px`, zoom `right: 8px` — the art is asymmetric by that pixel) with a 2px patch ring where the striped bar's is 1px (`--vf-widget-ring`, internal geometry: the windoid sheet clears two px of dither beside its widgets), and the nested zoom square shrunk so its edges land at sprite col/row 3. No title patch: the display face's 16px line box can't sit in an 11px interior, so `.vf-title` is `display: none` under the variant (a retheming consumer can re-show it via `::part(title)`) and the heading names the widgets. A pressed windoid widget inverts whole — black interior under a white (invisible) borderline — rather than flashing the big bar's 9×9 sunburst, which can't land on a 5×5 interior.
|
|
222
222
|
- **A11y:** the close/zoom `aria-label`s are qualified by the title when there is one (`Close ${heading}` / `Zoom ${heading}`, falling back to bare `Close` / `Zoom`) — several windows are open at once by design, so a bare repeated "Close" gives an AT user no way to tell which window a widget belongs to. The frame is `role="group"`, named by the title patch via `aria-labelledby` when there is a heading (the utility variant's hidden patch still names it — AccName resolves hidden labelledby targets) — `group` rather than `region` deliberately, so a desktop of windows doesn't pollute landmark navigation; the title bar is a `<div>`, never a `<header>`, which would map to an unnamed `banner` landmark even inside the shadow root. An inactive window's widgets stay in the tree and the tab order but paint no ink (transparent border/background/patch ring — the bare System 7 bar): a background window whose body holds nothing focusable is still reachable, activated by `vf-desktop`'s `focusin` raise the moment Tab lands on a widget, and never drops focus to `<body>` when it deactivates. `npm run verify:window-a11y`.
|
|
223
223
|
- **Behavior:** close box click → `vf-close` (does NOT remove itself; consumer decides). Zoom box click → `vf-zoom`. If `movable`: dragging the title bar moves the window — the drag seeds its origin from the current offset position (once, converting to system px) and then states `left`/`top` via pointer capture, in whole system px like any authored placement. If `resizable`: dragging the grow box states `width`/`height` the same way, 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`.
|
|
@@ -389,11 +389,12 @@ Classic list box.
|
|
|
389
389
|
|
|
390
390
|
#### `vf-scroll-area` (`VfScrollArea`, vf-scroll-area.ts)
|
|
391
391
|
A container whose scrollbars look like System 7.
|
|
392
|
-
- **Attributes/props:** `axis: 'vertical' | 'horizontal' | 'both'` (default `'vertical'`, reflected) — which scroll rails to reserve as permanent placeholders (see "always-a-rail" below); `
|
|
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
|
|
392
|
+
- **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
394
|
- **Always-a-rail behavior:** each *reserved* axis (per `axis`) renders its rail element as a permanent placeholder — arrow buttons on an empty white channel (dither off, no thumb; System 7 drew the arrows on any bar in an active window, and a scroller outside a window always counts as active) — until the content overflows that axis, when the dither and thumb fill in and the arrows go live (an idle axis's arrows are drawn but inert — the press guard in `ScrollRailController` skips them). `ScrollStateController` (`src/scroll-state.ts`) measures both axes and writes `data-overflow-x` / `data-overflow-y` (`"true"` / `"false"`) on the scroll element; the recipe keys the dither and thumb off those attributes. The unreserved axis still scrolls natively (wheel, keyboard) but draws no rail. Shared by vf-list and vf-text-area; a future `@container scroll-state(scrollable)` query could replace the JS for slotted-content components.
|
|
395
395
|
- **Inactive-window blanking:** the HIG's non-frontmost window must not display interactive scroll UX, so the controller also finds the nearest `vf-window` up the composed tree (light-DOM ancestor for a slotted scroller, shadow ancestor for `vf-window[scrollbars]`'s own edge rails), watches its reflected `active` attribute, and toggles a presence-only `data-window-inactive` on the scroll element. While present, the recipe empties dither/thumb/arrows on BOTH axes regardless of overflow — the bare channel, arrows included (unlike the idle rail, which keeps them), exactly as System 7 blanked a deactivated window's bars (its List Manager/TextEdit deactivated in-window scrollbars too). No `vf-window` ancestor → the attribute never appears: dialogs have no inactive state and a bare scroll component always draws live. Like the overflow half, this signal could one day go declarative — a custom property cascaded under `vf-window:not([active])` gating the recipe via an `@container style()` query; the rails being ordinary DOM, that migration is a plain selector swap (see the FUTURE note in scroll-state.ts).
|
|
396
|
-
- **Document-window (TeachText) composition:** to put the rails on a window's edge rather than inset in its body, slot the scroll area into a `vf-window
|
|
396
|
+
- **Document-window (TeachText) composition:** to put the rails on a window's edge rather than inset in its body, slot the scroll area into a `vf-window` 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`).
|
|
397
|
+
- **Methods:** `measure()` → re-measure overflow and re-sync the rails. The area tracks its content's box and its slot by itself; this is for a scroll range that changes with no box changing anywhere — a placed child moved through `top`/`left`, a transform — the way the kit's own fields re-measure on input.
|
|
397
398
|
- **Slots:** default. **Parts:** `viewport`.
|
|
398
399
|
|
|
399
400
|
#### `vf-fieldset` (`VfFieldset`, vf-fieldset.ts)
|
|
@@ -502,7 +503,7 @@ What the kit keeps as its own demo surface is `index.html`, the component refere
|
|
|
502
503
|
5. An alert: menu item Special → "Erase Disk…" opens a composed alert box — `vf-dialog frame="plain"` with the demo's own 32×32 alert icon (`demo/icons/alert.png`) in a row `vf-stack` via `vf-img` — "Completely erase the disk named 'Macintosh HD'?" with Cancel / Erase buttons (Erase = default variant, closes the dialog). The kit ships no alert component; the showcase demonstrates the recipe.
|
|
503
504
|
6. All windows `movable`; desktop stacking/active management demonstrably works. Every window starts put away and opens from its own desktop launcher icon — a `vf-icon` per demonstration window and dialog, clustered around the top-left so the set stays on-canvas at any viewport size; `vf-open` centers the window on the raster as it currently stands and raises it (no window carries an authored position). Closing a window hides it (listen for `vf-close`, set `hidden`); the close-box-less modal lookalikes dismiss via their own OK/Cancel buttons instead; Special → Show All Windows un-hides everything, cascaded around the center.
|
|
504
505
|
7. **"Page Setup" modal dialog box** — File → Page Setup… opens a `vf-dialog frame="plain"` (dBoxProc double frame, heading drawn in content): Paper radio group in a fieldset, Reduce or Enlarge `vf-number-field`, Cancel / OK (default) buttons.
|
|
505
|
-
8. **"Desk Accessories" utility palette** — `vf-window variant="utility" movable
|
|
506
|
+
8. **"Desk Accessories" utility palette** — `vf-window variant="utility" movable`: a `vf-grid` of 3×3 26px cells holding 16×16 `vf-img` DA icons (one selected, inverted), frameless so the window's own border is the palette's, floating above the document windows on the desktop's utility tier and untouched by their active-state churn — the Group A archetype table's fifth recipe, live.
|
|
506
507
|
9. The **"DragThing Read Me"** window (item 2's copy points at it) carries the document-window archetype at full anatomy: `movable resizable scrollbars="both"`, the rails in the frame and the grow box in the corner cell.
|
|
507
508
|
10. **Finder icons on the desktop** — "Macintosh HD" and "Trash" as `vf-icon`, each slotting its art at both resource sizes, `selectable movable editable`. They sit under the windows the way desktop icons do, taking the column to the right of the launcher cluster — with `left`/`top` in system px, never a `right`/`bottom` anchor, which lands somewhere different at each density since the desktop is the viewport. View → "by Small Icon" drives them (launchers included).
|
|
508
509
|
|
|
@@ -932,7 +932,7 @@
|
|
|
932
932
|
},
|
|
933
933
|
{
|
|
934
934
|
"name": "vf-scroll-area",
|
|
935
|
-
"description": "`<vf-scroll-area>` — a container whose scrollbars look like System 7.\n\nWhite box with a 1px black frame, an inner scrolling viewport, and scroll\nrails the kit draws itself as shadow DOM (the shared `vfScrollRail` recipe):\nboxed arrow buttons at each end, a loose 1-bit dot-dither trough, and the\nclassic fixed 16px thumb. The native scrollbar is hidden, never the native\nscrolling — wheel, trackpad momentum, keyboard, touch and assistive-tech\nscrolling stay the platform's, and ScrollRailController keeps the\nrail in sync while driving the classic interactions (thumb drag, trough\npaging, arrow stepping with auto-repeat). Every engine renders the same\nrail — there is no Firefox fallback skin anymore.\n\nEach reserved scroll rail is a permanent placeholder: arrow buttons on an\nempty white channel sit in the gutter even when the content fits, the\ndither and thumb filling in only once that axis overflows (System 7 drew\nan active window's no-overflow bar as arrows on a bare channel; driven\nby ScrollStateController). Which rails are reserved is set by\naxis; when both are reserved the bottom-right corner joins them,\nand corner reserves that cell on a single-axis rail too — the\nrail stops 15px short of the frame, for a grow box to land in.\n\nSize the host (width/height) from the outside; the viewport fills it
|
|
935
|
+
"description": "`<vf-scroll-area>` — a container whose scrollbars look like System 7.\n\nWhite box with a 1px black frame, an inner scrolling viewport, and scroll\nrails the kit draws itself as shadow DOM (the shared `vfScrollRail` recipe):\nboxed arrow buttons at each end, a loose 1-bit dot-dither trough, and the\nclassic fixed 16px thumb. The native scrollbar is hidden, never the native\nscrolling — wheel, trackpad momentum, keyboard, touch and assistive-tech\nscrolling stay the platform's, and ScrollRailController keeps the\nrail in sync while driving the classic interactions (thumb drag, trough\npaging, arrow stepping with auto-repeat). Every engine renders the same\nrail — there is no Firefox fallback skin anymore.\n\nEach reserved scroll rail is a permanent placeholder: arrow buttons on an\nempty white channel sit in the gutter even when the content fits, the\ndither and thumb filling in only once that axis overflows (System 7 drew\nan active window's no-overflow bar as arrows on a bare channel; driven\nby ScrollStateController). Which rails are reserved is set by\naxis; when both are reserved the bottom-right corner joins them,\nand corner reserves that cell on a single-axis rail too — the\nrail stops 15px short of the frame, for a grow box to land in.\n\nSize the host (width/height) from the outside; the viewport fills it.\nContent runs to the frame and the rails — the area adds no inset of its\nown; an inset is the content's (a `vf-stack pad`).\n\nThe scrolled plane sizes to its content — never narrower than the\nviewport, as wide as content that cannot wrap — so the rails follow a row\nthat grows sideways the way they follow copy that grows down, and a\n`slotchange` re-measures. measure covers a scroll range that\nchanges with no box changing (a placed child moved through `top`/`left`).\n---\n\n\n### **Slots:**\n - _default_ - Scrollable content.\n\n### **CSS Properties:**\n - **--vf-scrollbar-thumb** - scrollbar thumb/elevator (white) _(default: undefined)_\n- **--vf-scrollbar-track** - the scroll trough's base color under the dot-dither (white) _(default: undefined)_\n\n### **CSS Parts:**\n - **viewport** - The inner scrolling container.",
|
|
936
936
|
"attributes": [
|
|
937
937
|
{
|
|
938
938
|
"name": "axis",
|
|
@@ -943,11 +943,6 @@
|
|
|
943
943
|
{ "name": "both" }
|
|
944
944
|
]
|
|
945
945
|
},
|
|
946
|
-
{
|
|
947
|
-
"name": "flush",
|
|
948
|
-
"description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
|
|
949
|
-
"values": []
|
|
950
|
-
},
|
|
951
946
|
{
|
|
952
947
|
"name": "corner",
|
|
953
948
|
"description": "Reserve the bottom-right corner cell on a single-axis rail: the rail\nstops 15px short of the frame and the viewport spans the rest, leaving\nthe 15×15 cell — with the two dividers a `both` rail's corner carries —\nfor a grow box to land in. A `both` rail always has the cell, so the\nflag changes nothing there. `vf-window[scrollbars resizable]` sets it.",
|
|
@@ -1384,14 +1379,9 @@
|
|
|
1384
1379
|
"description": "See minWidth. Unbounded by default.",
|
|
1385
1380
|
"values": []
|
|
1386
1381
|
},
|
|
1387
|
-
{
|
|
1388
|
-
"name": "flush",
|
|
1389
|
-
"description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
|
|
1390
|
-
"values": []
|
|
1391
|
-
},
|
|
1392
1382
|
{
|
|
1393
1383
|
"name": "scrollbars",
|
|
1394
|
-
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\
|
|
1384
|
+
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nContent runs to the frame and the rails; an inset is the content's own.\nThe slotted composition (SPEC §5 vf-scroll-area) still works for a well\nplaced inside the body.",
|
|
1395
1385
|
"values": [
|
|
1396
1386
|
{ "name": "vertical" },
|
|
1397
1387
|
{ "name": "horizontal" },
|
package/editor/web-types.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
|
|
3
3
|
"name": "vintage-frames",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description-markup": "markdown",
|
|
6
6
|
"contributions": {
|
|
7
7
|
"html": {
|
|
@@ -2239,7 +2239,7 @@
|
|
|
2239
2239
|
},
|
|
2240
2240
|
{
|
|
2241
2241
|
"name": "vf-scroll-area",
|
|
2242
|
-
"description": "`<vf-scroll-area>` — a container whose scrollbars look like System 7.\n\nWhite box with a 1px black frame, an inner scrolling viewport, and scroll\nrails the kit draws itself as shadow DOM (the shared `vfScrollRail` recipe):\nboxed arrow buttons at each end, a loose 1-bit dot-dither trough, and the\nclassic fixed 16px thumb. The native scrollbar is hidden, never the native\nscrolling — wheel, trackpad momentum, keyboard, touch and assistive-tech\nscrolling stay the platform's, and ScrollRailController keeps the\nrail in sync while driving the classic interactions (thumb drag, trough\npaging, arrow stepping with auto-repeat). Every engine renders the same\nrail — there is no Firefox fallback skin anymore.\n\nEach reserved scroll rail is a permanent placeholder: arrow buttons on an\nempty white channel sit in the gutter even when the content fits, the\ndither and thumb filling in only once that axis overflows (System 7 drew\nan active window's no-overflow bar as arrows on a bare channel; driven\nby ScrollStateController). Which rails are reserved is set by\naxis; when both are reserved the bottom-right corner joins them,\nand corner reserves that cell on a single-axis rail too — the\nrail stops 15px short of the frame, for a grow box to land in.\n\nSize the host (width/height) from the outside; the viewport fills it
|
|
2242
|
+
"description": "`<vf-scroll-area>` — a container whose scrollbars look like System 7.\n\nWhite box with a 1px black frame, an inner scrolling viewport, and scroll\nrails the kit draws itself as shadow DOM (the shared `vfScrollRail` recipe):\nboxed arrow buttons at each end, a loose 1-bit dot-dither trough, and the\nclassic fixed 16px thumb. The native scrollbar is hidden, never the native\nscrolling — wheel, trackpad momentum, keyboard, touch and assistive-tech\nscrolling stay the platform's, and ScrollRailController keeps the\nrail in sync while driving the classic interactions (thumb drag, trough\npaging, arrow stepping with auto-repeat). Every engine renders the same\nrail — there is no Firefox fallback skin anymore.\n\nEach reserved scroll rail is a permanent placeholder: arrow buttons on an\nempty white channel sit in the gutter even when the content fits, the\ndither and thumb filling in only once that axis overflows (System 7 drew\nan active window's no-overflow bar as arrows on a bare channel; driven\nby ScrollStateController). Which rails are reserved is set by\naxis; when both are reserved the bottom-right corner joins them,\nand corner reserves that cell on a single-axis rail too — the\nrail stops 15px short of the frame, for a grow box to land in.\n\nSize the host (width/height) from the outside; the viewport fills it.\nContent runs to the frame and the rails — the area adds no inset of its\nown; an inset is the content's (a `vf-stack pad`).\n\nThe scrolled plane sizes to its content — never narrower than the\nviewport, as wide as content that cannot wrap — so the rails follow a row\nthat grows sideways the way they follow copy that grows down, and a\n`slotchange` re-measures. measure covers a scroll range that\nchanges with no box changing (a placed child moved through `top`/`left`).\n---\n\n\n### **Slots:**\n - _default_ - Scrollable content.\n\n### **CSS Properties:**\n - **--vf-scrollbar-thumb** - scrollbar thumb/elevator (white) _(default: undefined)_\n- **--vf-scrollbar-track** - the scroll trough's base color under the dot-dither (white) _(default: undefined)_\n\n### **CSS Parts:**\n - **viewport** - The inner scrolling container.",
|
|
2243
2243
|
"doc-url": "",
|
|
2244
2244
|
"attributes": [
|
|
2245
2245
|
{
|
|
@@ -2250,11 +2250,6 @@
|
|
|
2250
2250
|
"default": "'vertical'"
|
|
2251
2251
|
}
|
|
2252
2252
|
},
|
|
2253
|
-
{
|
|
2254
|
-
"name": "flush",
|
|
2255
|
-
"description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
|
|
2256
|
-
"value": { "type": "boolean", "default": "false" }
|
|
2257
|
-
},
|
|
2258
2253
|
{
|
|
2259
2254
|
"name": "corner",
|
|
2260
2255
|
"description": "Reserve the bottom-right corner cell on a single-axis rail: the rail\nstops 15px short of the frame and the viewport spans the rest, leaving\nthe 15×15 cell — with the two dividers a `both` rail's corner carries —\nfor a grow box to land in. A `both` rail always has the cell, so the\nflag changes nothing there. `vf-window[scrollbars resizable]` sets it.",
|
|
@@ -2285,11 +2280,6 @@
|
|
|
2285
2280
|
"description": "Which scroll rails to reserve as permanent placeholders: `vertical`\n(default), `horizontal`, or `both`. Each reserved rail shows an empty white\nchannel until its axis overflows; the unreserved axis still scrolls\nnatively (wheel, keyboard) but draws no rail.",
|
|
2286
2281
|
"type": "'vertical' | 'horizontal' | 'both'"
|
|
2287
2282
|
},
|
|
2288
|
-
{
|
|
2289
|
-
"name": "flush",
|
|
2290
|
-
"description": "Drop the viewport's 8px inset: slotted content and the (0,0) of placed\nchildren sit at the frame's inner edge, one system px from the frame\nbox (the border-floor compensation stays — see the viewport rule).\n`vf-window[scrollbars]` forwards its own `flush` here.",
|
|
2291
|
-
"type": "boolean"
|
|
2292
|
-
},
|
|
2293
2283
|
{
|
|
2294
2284
|
"name": "corner",
|
|
2295
2285
|
"description": "Reserve the bottom-right corner cell on a single-axis rail: the rail\nstops 15px short of the frame and the viewport spans the rest, leaving\nthe 15×15 cell — with the two dividers a `both` rail's corner carries —\nfor a grow box to land in. A `both` rail always has the cell, so the\nflag changes nothing there. `vf-window[scrollbars resizable]` sets it.",
|
|
@@ -3313,14 +3303,9 @@
|
|
|
3313
3303
|
"description": "See minWidth. Unbounded by default.",
|
|
3314
3304
|
"value": { "type": "number | null | undefined" }
|
|
3315
3305
|
},
|
|
3316
|
-
{
|
|
3317
|
-
"name": "flush",
|
|
3318
|
-
"description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
|
|
3319
|
-
"value": { "type": "boolean", "default": "false" }
|
|
3320
|
-
},
|
|
3321
3306
|
{
|
|
3322
3307
|
"name": "scrollbars",
|
|
3323
|
-
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\
|
|
3308
|
+
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nContent runs to the frame and the rails; an inset is the content's own.\nThe slotted composition (SPEC §5 vf-scroll-area) still works for a well\nplaced inside the body.",
|
|
3324
3309
|
"value": {
|
|
3325
3310
|
"type": "'vertical' | 'horizontal' | 'both' | undefined"
|
|
3326
3311
|
}
|
|
@@ -3424,14 +3409,9 @@
|
|
|
3424
3409
|
"description": "See minWidth. Unbounded by default.",
|
|
3425
3410
|
"type": "number | null | undefined"
|
|
3426
3411
|
},
|
|
3427
|
-
{
|
|
3428
|
-
"name": "flush",
|
|
3429
|
-
"description": "Remove the default 12px body padding. Under `scrollbars` the body has\nnone to remove, so the flag reaches the built-in scroll area instead\nand drops its viewport's 8px inset: content runs to the frame and the\nrails, and the (0,0) of placed children is the content region's corner.",
|
|
3430
|
-
"type": "boolean"
|
|
3431
|
-
},
|
|
3432
3412
|
{
|
|
3433
3413
|
"name": "scrollbars",
|
|
3434
|
-
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\
|
|
3414
|
+
"description": "Put System 7 scroll rails on the window edge — the classic document\nwindow. The body slot renders inside a built-in `vf-scroll-area` pulled\none system pixel under the frame on every side, so the rails repaint the\nborder lines and a `resizable` window's grow box lands in the corner\ncell — reserved on a single-axis rail too (the area's `corner`), unless\na populated status strip holds the grow box, when the rail runs edge to\nedge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the\n`heading` names the scroll region; the viewport part is re-exported.\nContent runs to the frame and the rails; an inset is the content's own.\nThe slotted composition (SPEC §5 vf-scroll-area) still works for a well\nplaced inside the body.",
|
|
3435
3415
|
"type": "'vertical' | 'horizontal' | 'both' | undefined"
|
|
3436
3416
|
},
|
|
3437
3417
|
{
|