vintage-frames 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/custom-elements.json +19807 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js +9 -0
  5. package/dist/chrome.d.ts +73 -0
  6. package/dist/chrome.js +94 -0
  7. package/dist/components/vf-button-group.d.ts +50 -0
  8. package/dist/components/vf-button-group.js +68 -0
  9. package/dist/components/vf-button.d.ts +169 -0
  10. package/dist/components/vf-button.js +234 -0
  11. package/dist/components/vf-checkbox.d.ts +60 -0
  12. package/dist/components/vf-checkbox.js +135 -0
  13. package/dist/components/vf-container.d.ts +95 -0
  14. package/dist/components/vf-container.js +86 -0
  15. package/dist/components/vf-desktop.d.ts +243 -0
  16. package/dist/components/vf-desktop.js +310 -0
  17. package/dist/components/vf-dialog.d.ts +105 -0
  18. package/dist/components/vf-dialog.js +280 -0
  19. package/dist/components/vf-fieldset.d.ts +34 -0
  20. package/dist/components/vf-fieldset.js +76 -0
  21. package/dist/components/vf-grid.d.ts +124 -0
  22. package/dist/components/vf-grid.js +133 -0
  23. package/dist/components/vf-icon.d.ts +356 -0
  24. package/dist/components/vf-icon.js +507 -0
  25. package/dist/components/vf-img.d.ts +63 -0
  26. package/dist/components/vf-img.js +65 -0
  27. package/dist/components/vf-label.d.ts +117 -0
  28. package/dist/components/vf-label.js +151 -0
  29. package/dist/components/vf-list-item.d.ts +48 -0
  30. package/dist/components/vf-list-item.js +98 -0
  31. package/dist/components/vf-list.d.ts +77 -0
  32. package/dist/components/vf-list.js +256 -0
  33. package/dist/components/vf-menu-bar.d.ts +64 -0
  34. package/dist/components/vf-menu-bar.js +236 -0
  35. package/dist/components/vf-menu-item.d.ts +91 -0
  36. package/dist/components/vf-menu-item.js +252 -0
  37. package/dist/components/vf-menu.d.ts +95 -0
  38. package/dist/components/vf-menu.js +346 -0
  39. package/dist/components/vf-number-field.d.ts +60 -0
  40. package/dist/components/vf-number-field.js +248 -0
  41. package/dist/components/vf-option.d.ts +49 -0
  42. package/dist/components/vf-option.js +119 -0
  43. package/dist/components/vf-paragraph.d.ts +73 -0
  44. package/dist/components/vf-paragraph.js +66 -0
  45. package/dist/components/vf-progress-bar.d.ts +69 -0
  46. package/dist/components/vf-progress-bar.js +272 -0
  47. package/dist/components/vf-radio-group.d.ts +109 -0
  48. package/dist/components/vf-radio-group.js +105 -0
  49. package/dist/components/vf-radio.d.ts +59 -0
  50. package/dist/components/vf-radio.js +135 -0
  51. package/dist/components/vf-scroll-area.d.ts +75 -0
  52. package/dist/components/vf-scroll-area.js +124 -0
  53. package/dist/components/vf-select.d.ts +334 -0
  54. package/dist/components/vf-select.js +658 -0
  55. package/dist/components/vf-separator.d.ts +38 -0
  56. package/dist/components/vf-separator.js +55 -0
  57. package/dist/components/vf-slider.d.ts +80 -0
  58. package/dist/components/vf-slider.js +255 -0
  59. package/dist/components/vf-stack.d.ts +188 -0
  60. package/dist/components/vf-stack.js +168 -0
  61. package/dist/components/vf-swatch.d.ts +98 -0
  62. package/dist/components/vf-swatch.js +201 -0
  63. package/dist/components/vf-text-area.d.ts +77 -0
  64. package/dist/components/vf-text-area.js +121 -0
  65. package/dist/components/vf-text-field.d.ts +53 -0
  66. package/dist/components/vf-text-field.js +79 -0
  67. package/dist/components/vf-window.d.ts +157 -0
  68. package/dist/components/vf-window.js +377 -0
  69. package/dist/cursor-art.d.ts +37 -0
  70. package/dist/cursor-art.js +41 -0
  71. package/dist/cursor.d.ts +49 -0
  72. package/dist/cursor.js +171 -0
  73. package/dist/define.d.ts +45 -0
  74. package/dist/define.js +15 -0
  75. package/dist/document-listeners.d.ts +62 -0
  76. package/dist/document-listeners.js +29 -0
  77. package/dist/drag.d.ts +49 -0
  78. package/dist/drag.js +30 -0
  79. package/dist/events.d.ts +79 -0
  80. package/dist/events.js +25 -0
  81. package/dist/focus-modality.d.ts +94 -0
  82. package/dist/focus-modality.js +56 -0
  83. package/dist/form-control.d.ts +247 -0
  84. package/dist/form-control.js +127 -0
  85. package/dist/glyphs.d.ts +117 -0
  86. package/dist/glyphs.js +100 -0
  87. package/dist/grid-snap.d.ts +73 -0
  88. package/dist/grid-snap.js +135 -0
  89. package/dist/index.d.ts +145 -0
  90. package/dist/index.js +71 -0
  91. package/dist/menu-press.d.ts +60 -0
  92. package/dist/menu-press.js +97 -0
  93. package/dist/modal-dialog.d.ts +148 -0
  94. package/dist/modal-dialog.js +155 -0
  95. package/dist/motion.d.ts +72 -0
  96. package/dist/motion.js +22 -0
  97. package/dist/number.d.ts +24 -0
  98. package/dist/number.js +12 -0
  99. package/dist/open-art.d.ts +40 -0
  100. package/dist/open-art.js +35 -0
  101. package/dist/pixel-frame.d.ts +102 -0
  102. package/dist/pixel-frame.js +77 -0
  103. package/dist/popup-overflow.d.ts +157 -0
  104. package/dist/popup-overflow.js +45 -0
  105. package/dist/position.d.ts +187 -0
  106. package/dist/position.js +74 -0
  107. package/dist/scale.d.ts +259 -0
  108. package/dist/scale.js +133 -0
  109. package/dist/scroll-rail.d.ts +112 -0
  110. package/dist/scroll-rail.js +194 -0
  111. package/dist/scroll-state.d.ts +113 -0
  112. package/dist/scroll-state.js +55 -0
  113. package/dist/size.d.ts +47 -0
  114. package/dist/size.js +30 -0
  115. package/dist/styles/base.d.ts +38 -0
  116. package/dist/styles/body-font.d.ts +7 -0
  117. package/dist/styles/body-font.js +9 -0
  118. package/dist/styles/display-font.d.ts +7 -0
  119. package/dist/styles/display-font.js +9 -0
  120. package/dist/styles/recipes/body-face.d.ts +14 -0
  121. package/dist/styles/recipes/body-face.js +6 -0
  122. package/dist/styles/recipes/display-face.d.ts +23 -0
  123. package/dist/styles/recipes/display-face.js +10 -0
  124. package/dist/styles/recipes/field.d.ts +20 -0
  125. package/dist/styles/recipes/field.js +68 -0
  126. package/dist/styles/recipes/focus.d.ts +76 -0
  127. package/dist/styles/recipes/focus.js +39 -0
  128. package/dist/styles/recipes/host.d.ts +5 -0
  129. package/dist/styles/recipes/host.js +62 -0
  130. package/dist/styles/recipes/pattern.d.ts +80 -0
  131. package/dist/styles/recipes/pattern.js +82 -0
  132. package/dist/styles/recipes/scroll-rail.d.ts +69 -0
  133. package/dist/styles/recipes/scroll-rail.js +222 -0
  134. package/dist/styles/recipes/shadow.d.ts +13 -0
  135. package/dist/styles/recipes/shadow.js +5 -0
  136. package/dist/styles/recipes/static-text.d.ts +24 -0
  137. package/dist/styles/recipes/static-text.js +17 -0
  138. package/dist/styles/recipes/surface.d.ts +28 -0
  139. package/dist/styles/recipes/surface.js +27 -0
  140. package/dist/styles/recipes/tile.d.ts +122 -0
  141. package/dist/styles/recipes/tile.js +40 -0
  142. package/dist/styles/recipes/title-bar.d.ts +49 -0
  143. package/dist/styles/recipes/title-bar.js +134 -0
  144. package/dist/styles/recipes/toggle.d.ts +10 -0
  145. package/dist/styles/recipes/toggle.js +20 -0
  146. package/dist/styles/register-embedded-font.d.ts +64 -0
  147. package/dist/styles/register-embedded-font.js +30 -0
  148. package/dist/text-control.d.ts +131 -0
  149. package/dist/text-control.js +83 -0
  150. package/dist/tile-grid.d.ts +111 -0
  151. package/dist/tile-grid.js +53 -0
  152. package/dist/toggle-control.d.ts +60 -0
  153. package/dist/toggle-control.js +41 -0
  154. package/dist/track-width.d.ts +42 -0
  155. package/dist/track-width.js +30 -0
  156. package/dist/type-ahead.d.ts +35 -0
  157. package/dist/type-ahead.js +20 -0
  158. package/dist/zoom.d.ts +204 -0
  159. package/dist/zoom.js +105 -0
  160. package/docs/SPEC.md +2471 -0
  161. package/editor/vscode.html-custom-data.json +1320 -0
  162. package/editor/web-types.json +3237 -0
  163. package/package.json +118 -0
@@ -0,0 +1,69 @@
1
+ import { LitElement } from 'lit';
2
+ import type { PropertyValues } from 'lit';
3
+ declare const VfProgressBar_base: (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof LitElement;
4
+ /**
5
+ * `<vf-progress-bar>` — the System 7 progress indicator.
6
+ *
7
+ * A 14px white track (`--vf-progress-track`) with a 1px black border.
8
+ * Determinate mode fills from the left in solid black
9
+ * (`--vf-progress-fill`) with a 1px black leading edge. Indeterminate
10
+ * mode shows chunky, stepped diagonal black/white barber stripes (the classic
11
+ * "busy" bar) animated with `steps()` timing so movement is deliberately
12
+ * steppy, not smooth.
13
+ *
14
+ * Exposes `role="progressbar"` with `aria-valuemin/max` and, when
15
+ * determinate, `aria-valuenow`.
16
+ *
17
+ * @csspart track - The outer bordered track.
18
+ * @csspart fill - The determinate fill or the indeterminate stripe layer.
19
+ * @cssprop [--vf-progress-fill=#000000] - determinate progress fill (solid
20
+ * black)
21
+ * @cssprop --vf-progress-stripes - the indeterminate barber stripes — a 12×12
22
+ * motif drawn as rects so the staircase stays whole system px at any scale,
23
+ * on a 60-system-px tile (override the whole tile — consumer art renders as
24
+ * a placed tile grid at that same geometry)
25
+ * @cssprop [--vf-progress-track=#ffffff] - progress track (white)
26
+ */
27
+ export declare class VfProgressBar extends VfProgressBar_base {
28
+ #private;
29
+ static styles: import("lit").CSSResult[];
30
+ private readonly scale;
31
+ /** Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts. */
32
+ private readonly gridSnap;
33
+ /** Current progress, from 0 to `max`. Clamped for display and ARIA. */
34
+ value: number;
35
+ /** Maximum value (default 100). */
36
+ max: number;
37
+ /** Barber-pole "busy" mode; ignores `value` and omits `aria-valuenow`. */
38
+ indeterminate: boolean;
39
+ /**
40
+ * Accessible name for the bar, applied as the host's `aria-label` (which
41
+ * carries `role="progressbar"`). Especially useful in `indeterminate` mode,
42
+ * where there is no `aria-valuenow` to describe the bar. A consumer-supplied
43
+ * `aria-label`/`aria-labelledby` attribute is left alone.
44
+ */
45
+ label: string;
46
+ private track;
47
+ /** Track the fill area's width so the determinate fill can snap to it. */
48
+ private readonly trackSize;
49
+ /**
50
+ * The consumer's `--vf-progress-stripes` override, or `''` for the kit
51
+ * barber — which exact-fill path the strip takes (src/tile-grid.ts).
52
+ * Re-read every update; a runtime token swap wants a `requestUpdate()`.
53
+ */
54
+ private _pattern;
55
+ constructor();
56
+ /** Effective maximum: a non-positive `max` is treated as the default 100. */
57
+ private get effectiveMax();
58
+ /** `value` clamped to `[0, effectiveMax]`. */
59
+ private get clampedValue();
60
+ protected willUpdate(changed: PropertyValues<this>): void;
61
+ protected updated(changed: PropertyValues<this>): void;
62
+ protected render(): import("lit-html").TemplateResult<1>;
63
+ }
64
+ declare global {
65
+ interface HTMLElementTagNameMap {
66
+ 'vf-progress-bar': VfProgressBar;
67
+ }
68
+ }
69
+ export {};
@@ -0,0 +1,272 @@
1
+ import { vfElement as e } from "../define.js";
2
+ import { ScaleController as t, sys as n, sysLength as r, toSys as i } from "../scale.js";
3
+ import a from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
+ import { VfPositioned as o } from "../position.js";
5
+ import { vfBase as s } from "../styles/recipes/host.js";
6
+ import { tileImage as c, tileRects as l, tileSpan as u, vfTileMaskSize as d } from "../styles/recipes/tile.js";
7
+ import { TileRasterCache as f, patternOverride as p, tileGrid as m, vfTileGrid as h } from "../tile-grid.js";
8
+ import { GridSnapController as g } from "../grid-snap.js";
9
+ import { TrackWidthController as _ } from "../track-width.js";
10
+ import { LitElement as v, css as y, html as b, unsafeCSS as x } from "lit";
11
+ import { property as S, query as C } from "lit/decorators.js";
12
+ //#region src/components/vf-progress-bar.ts
13
+ var w = 12, T = [
14
+ [
15
+ 0,
16
+ 0,
17
+ 6,
18
+ 1
19
+ ],
20
+ [
21
+ 1,
22
+ 1,
23
+ 6,
24
+ 1
25
+ ],
26
+ [
27
+ 2,
28
+ 2,
29
+ 6,
30
+ 1
31
+ ],
32
+ [
33
+ 3,
34
+ 3,
35
+ 6,
36
+ 1
37
+ ],
38
+ [
39
+ 4,
40
+ 4,
41
+ 6,
42
+ 1
43
+ ],
44
+ [
45
+ 5,
46
+ 5,
47
+ 6,
48
+ 1
49
+ ],
50
+ [
51
+ 6,
52
+ 6,
53
+ 6,
54
+ 1
55
+ ],
56
+ [
57
+ 0,
58
+ 7,
59
+ 1,
60
+ 1
61
+ ],
62
+ [
63
+ 7,
64
+ 7,
65
+ 5,
66
+ 1
67
+ ],
68
+ [
69
+ 0,
70
+ 8,
71
+ 2,
72
+ 1
73
+ ],
74
+ [
75
+ 8,
76
+ 8,
77
+ 4,
78
+ 1
79
+ ],
80
+ [
81
+ 0,
82
+ 9,
83
+ 3,
84
+ 1
85
+ ],
86
+ [
87
+ 9,
88
+ 9,
89
+ 3,
90
+ 1
91
+ ],
92
+ [
93
+ 0,
94
+ 10,
95
+ 4,
96
+ 1
97
+ ],
98
+ [
99
+ 10,
100
+ 10,
101
+ 2,
102
+ 1
103
+ ],
104
+ [
105
+ 0,
106
+ 11,
107
+ 5,
108
+ 1
109
+ ],
110
+ [
111
+ 11,
112
+ 11,
113
+ 1,
114
+ 1
115
+ ]
116
+ ], E = c(w, w, l(T)), D = u(w), O = class extends o(v) {
117
+ static {
118
+ this.styles = [
119
+ s,
120
+ h,
121
+ y`
122
+ :host {
123
+ display: block;
124
+ height: calc(var(--vf-scale, 1) * 14px);
125
+ }
126
+ .track {
127
+ position: relative;
128
+ height: 100%;
129
+ background: var(--vf-progress-track, #ffffff);
130
+ border: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
131
+ border-radius: 0;
132
+ overflow: hidden;
133
+ }
134
+ .fill {
135
+ height: 100%;
136
+ background: var(--vf-progress-fill, #000000);
137
+ /* The classic 1px black leading edge. */
138
+ border-right: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
139
+ }
140
+ .fill.empty {
141
+ border-right: none;
142
+ }
143
+ .fill.stripes {
144
+ width: 100%;
145
+ border-right: none;
146
+ /* Chunky 45° barber stripes: a 12×12 system-px cell whose two black
147
+ \ bands are a staircase of axis-aligned 1px rects (genuine pixel
148
+ art — a *diagonal* edge would blur where these stay pixel-exact)
149
+ over this themeable white ground. The art itself rides the
150
+ .vf-tile-strip child (src/tile-grid.ts): the kit's whole-surface
151
+ raster, or a consumer --vf-progress-stripes token's placed tile
152
+ grid at the token's documented 60-px tile geometry. This layer is
153
+ the strip's containing block and its clip. */
154
+ position: relative;
155
+ background-color: var(--vf-white, #fff);
156
+ --_vf-tile-image: var(--vf-progress-stripes, ${x(E)});
157
+ }
158
+ /* The animated strip: rests one 12px cell to the left (fully covering
159
+ the fill), and each cycle advances one whole cell in 4 chunky steps —
160
+ steppy, not smooth, wrapping with zero phase jump because the art's
161
+ period is the cell. The animated value is a layout property, so
162
+ every step is one quantized length paint-snapped like any placed
163
+ box — no CSS length ever accumulates a phase. */
164
+ .vf-tile-strip {
165
+ position: absolute;
166
+ top: 0;
167
+ height: 100%;
168
+ left: calc(var(--vf-scale, 1) * -12px);
169
+ animation: vf-barber 0.4s steps(4, end) infinite;
170
+ }
171
+ @keyframes vf-barber {
172
+ from {
173
+ left: calc(var(--vf-scale, 1) * -12px);
174
+ }
175
+ to {
176
+ left: 0;
177
+ }
178
+ }
179
+ /* Forced colors: the fill and track are the bar's whole reading, and
180
+ both have their own tokens with literal fallbacks the override
181
+ flattens to Canvas — remapped here like the palette tokens in vfBase.
182
+ The strip's raster art would keep its literal black ink (invisible on
183
+ a dark theme), so the strip hides and the layer repaints as a mask
184
+ over the ink token — same tile, same token a consumer overrides —
185
+ with the animation on the mask's position. The span-tiled mask keeps
186
+ the zoom caveat (no mask pipeline rasterizes exactly at a zoom-minted
187
+ scale); forced-colors-plus-zoom stays the accepted residual. */
188
+ @media (forced-colors: active) {
189
+ :host {
190
+ --vf-progress-fill: CanvasText;
191
+ --vf-progress-track: Canvas;
192
+ }
193
+ .fill.stripes {
194
+ background-color: var(--vf-progress-fill, CanvasText);
195
+ mask-image: var(--vf-progress-stripes, ${x(E)});
196
+ ${d(w)}
197
+ animation: vf-barber-mask 0.4s steps(4, end) infinite;
198
+ }
199
+ .fill.stripes .vf-tile-strip {
200
+ display: none;
201
+ }
202
+ }
203
+ @keyframes vf-barber-mask {
204
+ from {
205
+ mask-position: 0 0;
206
+ }
207
+ to {
208
+ mask-position: calc(var(--vf-scale, 1) * 12px) 0;
209
+ }
210
+ }
211
+ @media (prefers-reduced-motion: reduce) {
212
+ .fill.stripes,
213
+ .vf-tile-strip {
214
+ animation: none;
215
+ }
216
+ }
217
+ `
218
+ ];
219
+ }
220
+ #e;
221
+ #t;
222
+ constructor() {
223
+ super(), this.scale = new t(this), this.gridSnap = new g(this), this.value = 0, this.max = 100, this.indeterminate = !1, this.label = "", this.trackSize = new _(this, () => this.track), this._pattern = "", this.#e = new f(), this.#t = this.attachInternals(), this.#t.role = "progressbar", this.#t.ariaValueMin = "0";
224
+ }
225
+ get effectiveMax() {
226
+ return this.max > 0 ? this.max : 100;
227
+ }
228
+ get clampedValue() {
229
+ return Math.min(Math.max(this.value, 0), this.effectiveMax);
230
+ }
231
+ willUpdate(e) {
232
+ super.willUpdate(e), this.indeterminate && (this._pattern = p(this, "--vf-progress-stripes"));
233
+ }
234
+ updated(e) {
235
+ e.has("label") && (this.#t.ariaLabel = this.label || null), e.has("max") && (this.#t.ariaValueMax = String(this.effectiveMax)), (e.has("value") || e.has("max") || e.has("indeterminate")) && (this.#t.ariaValueNow = this.indeterminate ? null : String(this.clampedValue));
236
+ }
237
+ render() {
238
+ if (this.indeterminate) {
239
+ let e = i(this.trackSize.width, this), t = Math.max(D, Math.ceil((e + w) / D) * D), n = this._pattern ? m({
240
+ cols: t / D,
241
+ rows: 1,
242
+ tile: D
243
+ }) : b`<div
244
+ class="vf-tile-raster"
245
+ style="width:${r(t)};height:${r(24)};background-image:${this.#e.for(w, w, T, t, 24)}"
246
+ ></div>`;
247
+ return b`
248
+ <div class="track vf-snap" part="track">
249
+ <div class="fill stripes vf-tile-grid" part="fill">
250
+ <div class="vf-tile-strip">${n}</div>
251
+ </div>
252
+ </div>
253
+ `;
254
+ }
255
+ let e = this.clampedValue / this.effectiveMax, t = i(this.trackSize.width, this), a = t > 0 ? `${n(Math.round(e * t), this)}px` : `${e * 100}%`;
256
+ return b`
257
+ <div class="track vf-snap" part="track">
258
+ <div
259
+ class="fill ${e <= 0 ? "empty" : ""}"
260
+ part="fill"
261
+ style="width: ${a}"
262
+ ></div>
263
+ </div>
264
+ `;
265
+ }
266
+ };
267
+ a([S({ type: Number })], O.prototype, "value", void 0), a([S({ type: Number })], O.prototype, "max", void 0), a([S({
268
+ type: Boolean,
269
+ reflect: !0
270
+ })], O.prototype, "indeterminate", void 0), a([S()], O.prototype, "label", void 0), a([C(".track")], O.prototype, "track", void 0), O = a([e("vf-progress-bar")], O);
271
+ //#endregion
272
+ export { O as VfProgressBar };
@@ -0,0 +1,109 @@
1
+ import type { PropertyValues } from 'lit';
2
+ import { VfFormControl } from '../form-control.js';
3
+ declare const VfRadioGroup_base: (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof VfFormControl;
4
+ /**
5
+ * Groups `vf-radio` children, owning selection, keyboard navigation and the
6
+ * form value. Renders no chrome of its own; directly slotted radios stack
7
+ * vertically with a 6px gap (override with your own layout if needed —
8
+ * arbitrary markup containing radios also works).
9
+ *
10
+ * Form-associated: submits `value` under `name`, restores the initial value
11
+ * on form reset. Keeps children in sync — the child whose `value` matches
12
+ * the group's `value` is checked, all others unchecked.
13
+ *
14
+ * Keyboard (classic Mac behavior): the group is one tab stop (roving
15
+ * tabindex on the selected radio); ArrowUp/ArrowLeft and
16
+ * ArrowDown/ArrowRight move the selection AND select it, wrapping around
17
+ * and skipping disabled radios.
18
+ *
19
+ * @slot - `vf-radio` elements, or arbitrary markup containing them.
20
+ * @fires vf-change - When the selection changes via user interaction. `detail: { value: string }`.
21
+ * @fires input - Native event, dispatched from the group (the form-associated
22
+ * surface) per user pick. A programmatic `value` set fires nothing.
23
+ * @fires change - Native event, dispatched from the group per user pick so
24
+ * form delegation and framework bindings hear it.
25
+ */
26
+ export declare class VfRadioGroup extends VfRadioGroup_base {
27
+ static styles: import("lit").CSSResult[];
28
+ /**
29
+ * The value of the selected radio. An empty string means no selection
30
+ * (and nothing is submitted with the form).
31
+ */
32
+ value: string;
33
+ /** Form field name used when submitting. */
34
+ name: string;
35
+ /**
36
+ * Accessible name for the group, exposed as the `radiogroup`'s `aria-label`.
37
+ * Without it a caption-less group is announced anonymously.
38
+ */
39
+ label: string;
40
+ /** No radio checked — the state `required` calls a missing value. */
41
+ protected get valueMissing(): boolean;
42
+ /** The message a native required radio group reports. */
43
+ protected get valueMissingMessage(): string;
44
+ private readonly scale;
45
+ /**
46
+ * Re-syncs the group when its radios change structurally or by value —
47
+ * covers radios added/removed inside arbitrary wrapper markup (no top-level
48
+ * slotchange fires) and a child radio's value/disabled changing at runtime.
49
+ */
50
+ private mutationObserver;
51
+ constructor();
52
+ connectedCallback(): void;
53
+ disconnectedCallback(): void;
54
+ render(): import("lit-html").TemplateResult<1>;
55
+ protected willUpdate(changed: PropertyValues<this>): void;
56
+ protected updated(changed: PropertyValues<this>): void;
57
+ /** Form-associated lifecycle: restores the initial value. */
58
+ formResetCallback(): void;
59
+ /** All descendant radios belonging to this group (nested groups excluded). */
60
+ private get radios();
61
+ /**
62
+ * Identity of the state {@link syncRadios} pushes down, and the value it
63
+ * last pushed. Everything the children receive derives from these two, so
64
+ * comparing them tells `updated()` whether a sync would be a no-op.
65
+ *
66
+ * It intentionally doesn't track the children themselves: a structural or
67
+ * value/disabled change reaches `syncRadios()` directly (via the mutation
68
+ * observer or slotchange), never through this gate.
69
+ */
70
+ private get syncKey();
71
+ private syncedKey;
72
+ /**
73
+ * Push group state down to the children: checked flags, group-disabled
74
+ * dimming, and the roving tabindex (the checked radio is the tab stop;
75
+ * with no selection, the first enabled radio is).
76
+ */
77
+ private syncRadios;
78
+ /**
79
+ * Moves keyboard focus to the group's roving tab stop — the checked radio,
80
+ * else the first enabled one. Overridden because the platform's `focus()`
81
+ * is a silent no-op on this host (no `delegatesFocus`, no host tabindex),
82
+ * so `vf-label for` (which calls `target.focus()`) never reached the group.
83
+ */
84
+ focus(options?: FocusOptions): void;
85
+ /**
86
+ * Select a radio, sync everything, and fire `vf-change` if value changed.
87
+ *
88
+ * The sync stays synchronous — a `vf-change` listener must see the children
89
+ * already carrying the new selection, not the state of a pending update —
90
+ * and `updated()` then skips its own pass via the sync key.
91
+ */
92
+ private selectRadio;
93
+ /** A child radio was clicked or Space-selected: adopt it. */
94
+ private handleRadioChange;
95
+ /** Arrow keys move the selection AND select (classic Mac behavior). */
96
+ private handleKeydown;
97
+ /**
98
+ * With no value of its own, adopt a pre-checked radio's value — and treat
99
+ * it as the form-reset default.
100
+ */
101
+ private adoptPreChecked;
102
+ private handleSlotChange;
103
+ }
104
+ declare global {
105
+ interface HTMLElementTagNameMap {
106
+ 'vf-radio-group': VfRadioGroup;
107
+ }
108
+ }
109
+ export {};
@@ -0,0 +1,105 @@
1
+ import { vfElement as e } from "../define.js";
2
+ import { ScaleController as t } from "../scale.js";
3
+ import n from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
+ import { VfPositioned as r } from "../position.js";
5
+ import { vfBase as i } from "../styles/recipes/host.js";
6
+ import { emit as a, emitNative as o } from "../events.js";
7
+ import { VfFormControl as s } from "../form-control.js";
8
+ import { VfRadio as c } from "./vf-radio.js";
9
+ import { css as l, html as u } from "lit";
10
+ import { property as d } from "lit/decorators.js";
11
+ //#region src/components/vf-radio-group.ts
12
+ var f = class extends r(s) {
13
+ static {
14
+ this.styles = [i, l`
15
+ :host {
16
+ display: block;
17
+ }
18
+ ::slotted(vf-radio) {
19
+ display: flex;
20
+ }
21
+ ::slotted(vf-radio:not(:first-child)) {
22
+ margin-top: calc(var(--vf-scale, 1) * 6px);
23
+ }
24
+ `];
25
+ }
26
+ get valueMissing() {
27
+ return this.value === "";
28
+ }
29
+ get valueMissingMessage() {
30
+ return "Please select one of these options.";
31
+ }
32
+ constructor() {
33
+ super(), this.value = "", this.name = "", this.label = "", this.scale = new t(this), this.mutationObserver = null, this.syncedKey = null, this.handleRadioChange = (e) => {
34
+ let t = e.target;
35
+ t === this || !(t instanceof c) || t.closest("vf-radio-group") === this && (this.isDisabled || t.disabled || this.selectRadio(t, !1));
36
+ }, this.handleKeydown = (e) => {
37
+ if (this.isDisabled) return;
38
+ let t = e.key, n = 0;
39
+ if (t === "ArrowDown" || t === "ArrowRight") n = 1;
40
+ else if (t === "ArrowUp" || t === "ArrowLeft") n = -1;
41
+ else return;
42
+ e.preventDefault();
43
+ let r = this.radios.filter((e) => !e.disabled);
44
+ if (r.length === 0) return;
45
+ let i = e.target instanceof c ? e.target : this.radios.find((e) => e.checked), a = i ? r.indexOf(i) : -1, o = r[a === -1 ? n > 0 ? 0 : r.length - 1 : (a + n + r.length) % r.length];
46
+ o && this.selectRadio(o, !0);
47
+ }, this.handleSlotChange = () => {
48
+ this.adoptPreChecked(), this.syncRadios();
49
+ }, this.internals.role = "radiogroup", this.addEventListener("keydown", this.handleKeydown), this.addEventListener("vf-change", this.handleRadioChange);
50
+ }
51
+ connectedCallback() {
52
+ super.connectedCallback(), this.value !== "" && this.latchFormDefault(this.value), this.mutationObserver ??= new MutationObserver((e) => {
53
+ e.every((e) => e.type === "attributes" && e.target === this) || this.syncRadios();
54
+ }), this.mutationObserver.observe(this, {
55
+ childList: !0,
56
+ subtree: !0,
57
+ attributes: !0,
58
+ attributeFilter: ["value", "disabled"]
59
+ });
60
+ }
61
+ disconnectedCallback() {
62
+ super.disconnectedCallback(), this.mutationObserver?.disconnect();
63
+ }
64
+ render() {
65
+ return u`<slot @slotchange=${this.handleSlotChange}></slot>`;
66
+ }
67
+ willUpdate(e) {
68
+ this.hasUpdated || this.adoptPreChecked(), super.willUpdate(e);
69
+ }
70
+ updated(e) {
71
+ let t = this.disabledChanged(e);
72
+ (e.has("value") || t) && this.syncFormValue(this.value === "" ? null : this.value), e.has("label") && (this.internals.ariaLabel = this.label || null), t && (this.internals.ariaDisabled = this.isDisabled ? "true" : "false"), this.syncedKey !== this.syncKey && this.syncRadios();
73
+ }
74
+ formResetCallback() {
75
+ this.value = this.formDefault("");
76
+ }
77
+ get radios() {
78
+ return [...this.querySelectorAll("vf-radio")].filter((e) => e.closest("vf-radio-group") === this);
79
+ }
80
+ get syncKey() {
81
+ return `${this.isDisabled ? "d" : "e"}:${this.value}`;
82
+ }
83
+ syncRadios() {
84
+ let e = this.radios, t = this.isDisabled;
85
+ this.syncedKey = this.syncKey;
86
+ let n = this.value === "" ? void 0 : e.find((e) => e.value === this.value), r = n && !n.disabled ? n : void 0;
87
+ r ||= e.find((e) => !e.disabled);
88
+ for (let i of e) i.checked = i === n, i.groupDisabled = t, i.tabIndex = !t && i === r ? 0 : -1;
89
+ }
90
+ focus(e) {
91
+ this.radios.find((e) => e.tabIndex === 0)?.focus(e);
92
+ }
93
+ selectRadio(e, t) {
94
+ let n = this.value !== e.value;
95
+ this.value = e.value, this.syncRadios(), t && e.focus(), n && (a(this, "vf-change", { value: this.value }), o(this, "input"), o(this, "change"));
96
+ }
97
+ adoptPreChecked() {
98
+ if (this.value !== "") return;
99
+ let e = this.radios.find((e) => e.checked);
100
+ e && e.value !== "" && (this.value = e.value, this.latchFormDefault(this.value));
101
+ }
102
+ };
103
+ n([d()], f.prototype, "value", void 0), n([d({ reflect: !0 })], f.prototype, "name", void 0), n([d()], f.prototype, "label", void 0), f = n([e("vf-radio-group")], f);
104
+ //#endregion
105
+ export { f as VfRadioGroup };
@@ -0,0 +1,59 @@
1
+ import { LitElement } from 'lit';
2
+ declare const VfRadio_base: (new (...args: any[]) => import("../position.js").VfPositionedInterface) & (abstract new (...args: any[]) => import("../toggle-control.js").VfToggleControlInterface) & typeof LitElement;
3
+ /**
4
+ * A single System 7 radio button: a 13×13 white circle with the pixel-exact
5
+ * 1-bit ring and centered dot traced from the Classic Macintosh UI Kit sprite
6
+ * (replacing the anti-aliased `border-radius` rendering). The ring thickens
7
+ * while pressed, exactly like the original control.
8
+ *
9
+ * Radios are meant to live inside a `vf-radio-group`, which owns selection
10
+ * state, the form value and the roving tabindex. A `vf-radio` is NOT itself
11
+ * form-associated. Clicking (or pressing Space on) an unselected radio
12
+ * dispatches `vf-change`; the containing group listens, selects it and
13
+ * unselects its siblings.
14
+ *
15
+ * @slot - The label, rendered to the right of the circle with a 6px gap.
16
+ * @csspart circle - The 13×13 radio circle.
17
+ * @csspart label - The label wrapper around the slot.
18
+ * @fires vf-change - When selected by user interaction. `detail: { value: string }`.
19
+ */
20
+ export declare class VfRadio extends VfRadio_base {
21
+ static styles: import("lit").CSSResult[];
22
+ /** Whether this radio is selected. Managed by the containing group. */
23
+ checked: boolean;
24
+ /** Disables this single radio: the label dims to gray; circle and dot stay black. */
25
+ disabled: boolean;
26
+ /** The value the containing `vf-radio-group` reports when selected. */
27
+ value: string;
28
+ /**
29
+ * True while the containing `vf-radio-group` is disabled. Managed by the
30
+ * group — not intended to be set by consumers.
31
+ */
32
+ groupDisabled: boolean;
33
+ private readonly internals;
34
+ constructor();
35
+ /**
36
+ * A radio attaches its own internals: it is deliberately NOT form-associated
37
+ * (the enclosing `vf-radio-group` is the form surface), so there is no
38
+ * `VfFormControl` handle to inherit.
39
+ */
40
+ protected get toggleInternals(): ElementInternals;
41
+ /** This radio's own `disabled`, or its group's. */
42
+ protected get toggleDisabled(): boolean;
43
+ /**
44
+ * Inside a `vf-radio-group` the group is the single source of truth: it runs
45
+ * the roving tabindex and flips `checked` on the whole set. Standalone, this
46
+ * radio owns both — otherwise a bare radio would be keyboard-dead (its Space
47
+ * handler unreachable) and would never visibly toggle.
48
+ */
49
+ protected get externallyCoordinated(): boolean;
50
+ render(): import("lit-html").TemplateResult<1>;
51
+ /** Click/Space on an enabled, unselected radio selects it. */
52
+ protected activate(): void;
53
+ }
54
+ declare global {
55
+ interface HTMLElementTagNameMap {
56
+ 'vf-radio': VfRadio;
57
+ }
58
+ }
59
+ export {};