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,280 @@
1
+ import { vfElement as e } from "../define.js";
2
+ import { snapSys as t, toSysExact as n } from "../scale.js";
3
+ import r from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
+ import { vfDisplayDecls as i } from "../styles/recipes/display-face.js";
5
+ import { vfBase as a } from "../styles/recipes/host.js";
6
+ import { vfStripes as o } from "../styles/recipes/pattern.js";
7
+ import { vfChromeFrame as s, vfModalFrame as c } from "../styles/recipes/surface.js";
8
+ import { vfTitleBar as l, vfWindowWidgets as u } from "../styles/recipes/title-bar.js";
9
+ import { vfFocus as d, vfFocusRing as f } from "../styles/recipes/focus.js";
10
+ import { vfScrollRail as p } from "../styles/recipes/scroll-rail.js";
11
+ import { DragController as m } from "../drag.js";
12
+ import { TitleCenterController as h, chromeTitleBar as g, closeBox as _, widgetLabel as v } from "../chrome.js";
13
+ import { ScrollStateController as y } from "../scroll-state.js";
14
+ import { ScrollRailController as b, renderScrollRail as x } from "../scroll-rail.js";
15
+ import { VfModalDialog as S, modalDialogStyles as C } from "../modal-dialog.js";
16
+ import "./vf-button-group.js";
17
+ import { css as w, html as T, nothing as E } from "lit";
18
+ import { property as D, query as O, state as k } from "lit/decorators.js";
19
+ import { styleMap as A } from "lit/directives/style-map.js";
20
+ //#region src/components/vf-dialog.ts
21
+ var j = class extends S {
22
+ constructor(...e) {
23
+ super(...e), this._drag = new m(this, {
24
+ onDragStart: (e) => {
25
+ if (e.button !== 0 || e.composedPath().some((e) => e instanceof HTMLElement && e.classList.contains("box"))) return null;
26
+ let r = this._dialog;
27
+ if (!r?.open) return null;
28
+ if (this.left != null || this.top != null) return {
29
+ x: this.left ?? 0,
30
+ y: this.top ?? 0
31
+ };
32
+ let i = r.getBoundingClientRect();
33
+ return {
34
+ x: t(n(i.left, this), this),
35
+ y: t(n(i.top, this), this)
36
+ };
37
+ },
38
+ onDrag: (e, t) => this.placeAt(e, t)
39
+ }), this._titleCenter = new h(this), this.heading = "", this.label = "", this.closable = !1, this._hasButtons = !1, this._scrollable = !1, this._scrollState = new y(this, () => this._content, void 0, (e) => {
40
+ this._scrollable = e.y;
41
+ }), this._rail = new b(this, { getScroll: () => this._content });
42
+ }
43
+ static {
44
+ this.styles = [
45
+ a,
46
+ o,
47
+ d,
48
+ s,
49
+ c,
50
+ l,
51
+ u,
52
+ p,
53
+ C,
54
+ w`
55
+ :host {
56
+ display: contents;
57
+ }
58
+ /* A declared height lands on the <dialog> (dialogSize), so the frame has
59
+ to be told to fill it — vfChromeFrame is skin only. Both chromes are
60
+ full-height flex columns for the same reason vf-window's is: the body
61
+ takes the slack the title bar doesn't. The frame is the flex child of
62
+ the <dialog> itself (modalDialogStyles), not a height: 100% block —
63
+ a percentage can't resolve against the undeclared-height dialog that
64
+ only the UA's dialog:modal max-height caps, and that spill was
65
+ exactly how a tall modal used to strand its buttons off-screen. */
66
+ .vf-frame,
67
+ .vf-modal-frame {
68
+ display: flex;
69
+ flex-direction: column;
70
+ flex: 1 1 auto;
71
+ min-height: 0;
72
+ }
73
+ .vf-modal-frame-inner {
74
+ display: flex;
75
+ flex-direction: column;
76
+ flex: 1 1 auto;
77
+ min-height: 0;
78
+ }
79
+ /* The bar is always a drag handle here (a movable modal has no immovable
80
+ state, unlike vf-window's [movable]); keep touch gestures from
81
+ scrolling the page instead of moving the dialog. */
82
+ .vf-title-bar {
83
+ touch-action: none;
84
+ }
85
+ /* Close box clearance: the same 60px vf-window uses, so an ellipsized
86
+ title can't run under the widget (the title is centered, so the inset
87
+ has to cover both sides). */
88
+ :host([closable]) .vf-title {
89
+ --vf-title-inset: 60px;
90
+ }
91
+ /* Takes the slack under the title bar. A modal is a fixed box — the
92
+ frame never grows with its body — but content taller than the box is
93
+ no longer silently clipped: the .content region below scrolls it
94
+ under a System 7 rail, with the footer pinned outside the scroll so
95
+ the action buttons stay reachable. overflow: hidden stays as the
96
+ frame-level backstop. A slotted vf-select's list still escapes: it is
97
+ position:fixed off the control's own rect (see vf-select.ts). */
98
+ .body {
99
+ --vf-surface: var(--vf-white, #ffffff);
100
+ background: var(--vf-white, #ffffff);
101
+ display: flex;
102
+ flex-direction: column;
103
+ flex: 1 1 auto;
104
+ min-height: 0;
105
+ overflow: hidden;
106
+ padding: calc(var(--vf-scale, 1) * 16px);
107
+ }
108
+ /* The scroll region's positioned wrapper (the .scroll-frame overlay and
109
+ the rail overlay anchor against it). Shrink-only (flex-grow 0): with
110
+ slack in the box the footer sits right after the content, exactly
111
+ where block flow put it before this wrapper existed — the wrap only
112
+ gives height back when the content doesn't fit. */
113
+ .content-wrap {
114
+ position: relative;
115
+ display: flex;
116
+ flex-direction: column;
117
+ flex: 0 1 auto;
118
+ min-height: 0;
119
+ }
120
+ /* hidden, not auto, until the controller says otherwise — the two have
121
+ to agree on what "overflowing" means or the dialog scrolls without a
122
+ rail.
123
+ ScrollStateController deliberately ignores the body face's negative
124
+ half-leading (LEADING_SPILL_SYS, scroll-state.ts): vf-paragraph sets
125
+ Geneva's 12-system-px line box under a 16-system-px em, so the inline
126
+ box spills 2 inkless system px past the block box and scrollHeight
127
+ counts it. auto doesn't know that — it saw 6 CSS px of overflow at
128
+ scale 3 and handed the user a fixed info dialog that rubber-banded
129
+ under the wheel with no scrollbar to explain it.
130
+ hidden still scrolls programmatically (scrollIntoView on a focused
131
+ control keeps working); it just refuses to invent a user-facing scroll
132
+ the component has decided doesn't exist. */
133
+ .content {
134
+ flex: 0 1 auto;
135
+ min-height: 0;
136
+ overflow-y: hidden;
137
+ }
138
+ /* Only while genuinely over-stuffed (ScrollStateController's measured
139
+ signal) does the region scroll — reserving the 16px channel as its
140
+ own padding (what the native gutter used to reserve) — and only then
141
+ do the rail and the 1px frame boxing it appear. While the content
142
+ fits, none of this matches and the body renders exactly as it always
143
+ has. */
144
+ .content[data-overflow-y='true'] {
145
+ overflow-y: scroll;
146
+ padding-right: calc(var(--vf-scale, 1) * 16px);
147
+ }
148
+ /* The rail rides the wrapper as an OVERLAY pinned to its right edge,
149
+ inside the boxing frame's lines (the 1px insets; the fourth side is
150
+ its own divider) — deliberately out of the layout flow: a rail
151
+ column's two fixed 15px arrow cells would hand the region a 32px
152
+ minimum height, and a short dialog would then measure as fitting
153
+ with the rail shown and overflowing without it, flip-flopping
154
+ forever. An overlay cannot move the box; the channel the content
155
+ pays for is the padding above. Hidden until the region actually
156
+ scrolls. */
157
+ .content-wrap .vf-rail {
158
+ position: absolute;
159
+ top: calc(var(--vf-scale, 1) * 1px);
160
+ right: calc(var(--vf-scale, 1) * 1px);
161
+ bottom: calc(var(--vf-scale, 1) * 1px);
162
+ display: none;
163
+ }
164
+ .content[data-overflow-y='true'] ~ .vf-rail {
165
+ display: grid;
166
+ }
167
+ /* The 1px frame boxing the scrolling region and its rail — an overlay
168
+ (spanning both grid columns via inset 0) so the fitting dialog keeps
169
+ no reserved line. */
170
+ .scroll-frame {
171
+ position: absolute;
172
+ inset: 0;
173
+ border: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
174
+ pointer-events: none;
175
+ display: none;
176
+ }
177
+ .content[data-overflow-y='true'] ~ .scroll-frame {
178
+ display: block;
179
+ }
180
+ /* A scrollable region is a keyboard stop (tabindex in the template);
181
+ mark it with the kit's dotted ring, inset to stay in-box — the same
182
+ treatment as vf-scroll-area's viewport. */
183
+ .content:focus-visible {
184
+ --vf-focus-offset: -2px;
185
+ ${f}
186
+ }
187
+ /* The plain frame's heading: centered chrome type at the top of the
188
+ body, the way dBoxProc dialogs drew their title in content (see
189
+ Windows/modal dialog.png). No patch, no ellipsis — it's body-top
190
+ text, not a bar. */
191
+ .plain-heading {
192
+ ${i}
193
+ display: block;
194
+ text-align: center;
195
+ margin-bottom: calc(var(--vf-scale, 1) * 16px);
196
+ }
197
+ /* Optional action row (slot="buttons"): a right-aligned vf-button-group
198
+ that only takes space when populated. */
199
+ .footer {
200
+ display: flex;
201
+ justify-content: flex-end;
202
+ margin-top: calc(var(--vf-scale, 1) * 16px);
203
+ }
204
+ .footer.empty {
205
+ display: none;
206
+ }
207
+ `
208
+ ];
209
+ }
210
+ _onBodySlotChange() {
211
+ this._scrollState.measure(), this._rail.sync();
212
+ }
213
+ _onCloseClick() {
214
+ this.close();
215
+ }
216
+ render() {
217
+ let e = !this.label && this.heading !== "", t = this.frame === "plain", n = T`
218
+ <div class="body" part="body">
219
+ <div class="content-wrap">
220
+ <div
221
+ class="content vf-scroll"
222
+ part="content"
223
+ tabindex=${this._scrollable ? "0" : E}
224
+ role=${this._scrollable ? "group" : E}
225
+ >
226
+ ${t && this.heading !== "" ? T`<span class="plain-heading" part="title" id="title"
227
+ >${this.heading}</span
228
+ >` : E}
229
+ <slot @slotchange=${this._onBodySlotChange}></slot>
230
+ </div>
231
+ ${x(this._rail, "vertical")}
232
+ <div class="scroll-frame" aria-hidden="true"></div>
233
+ </div>
234
+ <div class="footer ${this._hasButtons ? "" : "empty"}" part="footer">
235
+ <vf-button-group class="buttons" part="buttons">
236
+ <slot
237
+ name="buttons"
238
+ @slotchange=${this._onButtonsSlotChange}
239
+ ></slot>
240
+ </vf-button-group>
241
+ </div>
242
+ </div>
243
+ `;
244
+ return T`
245
+ <dialog
246
+ style=${A(this.dialogSize)}
247
+ aria-labelledby=${e ? "title" : E}
248
+ aria-label=${e ? E : this.label || "Dialog"}
249
+ @cancel=${this._onNativeCancel}
250
+ @close=${this._onNativeClose}
251
+ >
252
+ ${t ? T`
253
+ <div class="vf-modal-frame" part="frame">
254
+ <div class="vf-modal-frame-inner">${n}</div>
255
+ </div>
256
+ ` : T`
257
+ <div class="vf-frame" part="frame">
258
+ ${g(this._drag, T`
259
+ ${this.closable ? _(v("Close", this.heading), this._onCloseClick) : E}
260
+ <span class="vf-title" part="title" id="title"
261
+ >${this.heading}</span
262
+ >
263
+ `)}
264
+ ${n}
265
+ </div>
266
+ `}
267
+ </dialog>
268
+ `;
269
+ }
270
+ _onButtonsSlotChange(e) {
271
+ let t = e.target;
272
+ this._hasButtons = t.assignedElements().length > 0;
273
+ }
274
+ };
275
+ r([D()], j.prototype, "heading", void 0), r([D()], j.prototype, "label", void 0), r([D({
276
+ type: Boolean,
277
+ reflect: !0
278
+ })], j.prototype, "closable", void 0), r([D({ reflect: !0 })], j.prototype, "frame", void 0), r([k()], j.prototype, "_hasButtons", void 0), r([O(".content")], j.prototype, "_content", void 0), r([k()], j.prototype, "_scrollable", void 0), j = r([e("vf-dialog")], j);
279
+ //#endregion
280
+ export { j as VfDialog };
@@ -0,0 +1,34 @@
1
+ import { LitElement } from 'lit';
2
+ declare const VfFieldset_base: (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof LitElement;
3
+ /**
4
+ * `<vf-fieldset>` — the classic "Install Location" group box.
5
+ *
6
+ * A 1px black rectangle whose bold legend sits on the top border, punching a
7
+ * gap through it with a `var(--vf-surface, ...)` background patch — so it
8
+ * matches whatever surface sits behind it.
9
+ *
10
+ * @slot - Group contents.
11
+ * @slot legend - Rich legend content; overrides the `legend` attribute.
12
+ * @csspart fieldset - The bordered box.
13
+ * @csspart legend - The legend patch on the top border.
14
+ * @cssprop [--vf-surface=...] - bg behind legends/label patches; `vf-window`
15
+ * and `vf-dialog` both set it to white
16
+ */
17
+ export declare class VfFieldset extends VfFieldset_base {
18
+ #private;
19
+ static styles: import("lit").CSSResult[];
20
+ private readonly scale;
21
+ /** Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts. */
22
+ private readonly gridSnap;
23
+ /** Legend text shown on the top border. */
24
+ legend: string;
25
+ /** Whether the named `legend` slot has assigned content. */
26
+ private _hasSlottedLegend;
27
+ protected render(): import("lit-html").TemplateResult<1>;
28
+ }
29
+ declare global {
30
+ interface HTMLElementTagNameMap {
31
+ 'vf-fieldset': VfFieldset;
32
+ }
33
+ }
34
+ export {};
@@ -0,0 +1,76 @@
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 { vfDisplayDecls as i } from "../styles/recipes/display-face.js";
6
+ import { vfBase as a } from "../styles/recipes/host.js";
7
+ import { GridSnapController as o } from "../grid-snap.js";
8
+ import { LitElement as s, css as c, html as l, nothing as u } from "lit";
9
+ import { property as d, state as f } from "lit/decorators.js";
10
+ import { classMap as p } from "lit/directives/class-map.js";
11
+ //#region src/components/vf-fieldset.ts
12
+ var m = class extends r(s) {
13
+ constructor(...e) {
14
+ super(...e), this.scale = new t(this), this.gridSnap = new o(this), this.legend = "", this._hasSlottedLegend = !1;
15
+ }
16
+ static {
17
+ this.styles = [a, c`
18
+ :host {
19
+ display: block;
20
+ }
21
+ .fieldset {
22
+ position: relative;
23
+ border: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
24
+ padding: calc(var(--vf-scale, 1) * 14px) calc(var(--vf-scale, 1) * 12px)
25
+ calc(var(--vf-scale, 1) * 10px);
26
+ margin-top: calc(var(--vf-scale, 1) * 8px);
27
+ }
28
+ .legend {
29
+ /* Chicago-style legend (chrome); grouped content keeps the body face. */
30
+ ${i}
31
+ position: absolute;
32
+ top: -0.7em;
33
+ left: calc(var(--vf-scale, 1) * 8px);
34
+ padding: 0 calc(var(--vf-scale, 1) * 5px);
35
+ /* Punches through the border, matching the surface behind it. */
36
+ background: var(--vf-surface, var(--vf-white, #fff));
37
+ white-space: nowrap;
38
+ }
39
+ .legend.empty {
40
+ display: none;
41
+ }
42
+ `];
43
+ }
44
+ render() {
45
+ let e = !this.legend && !this._hasSlottedLegend;
46
+ return l`
47
+ <div
48
+ class="fieldset vf-snap"
49
+ part="fieldset"
50
+ role="group"
51
+ aria-labelledby=${e ? u : "legend"}
52
+ >
53
+ <span
54
+ id="legend"
55
+ class=${p({
56
+ legend: !0,
57
+ empty: e
58
+ })}
59
+ part="legend"
60
+ >
61
+ <slot name="legend" @slotchange=${this.#e}>
62
+ ${this.legend}
63
+ </slot>
64
+ </span>
65
+ <slot></slot>
66
+ </div>
67
+ `;
68
+ }
69
+ #e(e) {
70
+ let t = e.target;
71
+ this._hasSlottedLegend = t.assignedNodes({ flatten: !0 }).length > 0;
72
+ }
73
+ };
74
+ n([d()], m.prototype, "legend", void 0), n([f()], m.prototype, "_hasSlottedLegend", void 0), m = n([e("vf-fieldset")], m);
75
+ //#endregion
76
+ export { m as VfFieldset };
@@ -0,0 +1,124 @@
1
+ import { LitElement } from 'lit';
2
+ /** How `vf-grid` draws a cell boundary. */
3
+ export type VfGridRules = 'solid' | 'dashed' | 'none';
4
+ declare const VfGrid_base: (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof LitElement;
5
+ /**
6
+ * `<vf-grid>` — a lattice of equal cells separated by 1px rules.
7
+ *
8
+ * The reference screens are full of these: the Figure 5-6 tool palette's 3×3
9
+ * of desk accessories, a color picker's swatch table, a pattern or icon
10
+ * chooser. They are all the same drawing — fixed-size cells butted together
11
+ * with a hairline between them — and every one of them used to be hand-rolled
12
+ * page CSS: a `display: grid` with a 1px gap over a black background, so the
13
+ * background showed through as the rules. That trick only draws *solid*
14
+ * interior rules, has to restate the cell size in three places, and puts the
15
+ * kit's own artwork in the consumer's stylesheet.
16
+ *
17
+ * This draws it instead. State the cell count and cell size in whole system px
18
+ * and slot the cells in; the component owns the track geometry and the rules:
19
+ *
20
+ * ```html
21
+ * <vf-grid columns="3" rows="3" cell-width="26" cell-height="26">
22
+ * <button>…</button> <!-- nine of them, one per cell -->
23
+ * </vf-grid>
24
+ * ```
25
+ *
26
+ * **The rules are a masked lattice, not gaps.** The grid's 1px gaps make the
27
+ * room; a single overlay paints `--vf-black` through two tiled masks — one
28
+ * column of pixels at the horizontal pitch, one row at the vertical — so the
29
+ * pen switches from `solid` to `dashed` (1 on, 1 off) without the layout
30
+ * moving a pixel. It paints *under* the cells (`z-index: -1` inside the grid's
31
+ * own stacking context, above its surface), so an item that spans two cells
32
+ * covers the boundary it swallowed, and a cell's own ink — a pressed face, a
33
+ * focus rule, a hard shadow — is never crossed by a line.
34
+ *
35
+ * **Cells are centered in their wells.** What a palette holds is normally
36
+ * smaller than the well it sits in — a 16px icon in a 26px cell — so a slotted
37
+ * item is centered on both axes rather than parked in the corner, which is
38
+ * where grid's own `stretch` default leaves anything carrying its own size. A
39
+ * cell that means to fill its well — a tool button that inverts black when
40
+ * selected, a tile whose border merges with the lattice — says
41
+ * `place-self: stretch` and gets the whole track. (Under `collapse` that is
42
+ * already the default: those cells exist to meet the lines.)
43
+ *
44
+ * **The perimeter is part of the lattice**, and drawn by default: the box
45
+ * carries the 1px the outer lines need, and one uninterrupted pitch runs from
46
+ * edge to edge. `frameless` drops it, for a grid inside something that already
47
+ * draws that line — the desk-accessory palette is a `vf-window[flush]`, whose
48
+ * frame *is* the grid's outer border, and a doubled one would read as 2px.
49
+ *
50
+ * **`collapse` puts an item's own border on the rule.** A cell that draws its
51
+ * own 1px border — a bordered tile, a 16×16 swatch — otherwise sets that border
52
+ * *beside* the lattice line, and every boundary reads 2px thick. `collapse`
53
+ * pulls each cell back one pixel on all four sides, so the two become one line:
54
+ * the `border-collapse: collapse` of a table, in system px. Size the cells at
55
+ * *item − 2px* — `cell-width="14"` for a 16px item — and each item then spans
56
+ * lattice line to lattice line, sharing one with each neighbor:
57
+ *
58
+ * ```html
59
+ * <vf-grid columns="8" rows="4" cell-width="14" cell-height="14" collapse>
60
+ * ```
61
+ *
62
+ * The frame is what gives the outermost borders a line of their own to land on,
63
+ * and it ends the box exactly at the last item's outer edge; go `frameless` and
64
+ * they paint a pixel outside the grid's box instead.
65
+ *
66
+ * `rules="none"` is the pen, not the lattice: nothing is drawn, the gaps close
67
+ * up, and the cells butt directly together (there is then no perimeter and
68
+ * nothing for `collapse` to collapse onto, so it goes inert).
69
+ *
70
+ * **Semantics are the consumer's.** The grid is layout — it takes no role, no
71
+ * keyboard behavior and no selection, so what it holds decides what it is: put
72
+ * `role="group"`/`aria-label` on the host for a tool palette, `role="radiogroup"`
73
+ * for a picker, or nothing at all for a plain tiling. Slotted cells stay in the
74
+ * light DOM with their own semantics intact.
75
+ *
76
+ * @slot - The cells, in order — one element per cell.
77
+ * @csspart grid - The grid box itself (surface, tracks, frame padding).
78
+ * @csspart rules - The lattice overlay painting the rules.
79
+ * @cssprop --vf-surface - The surface behind the cells (default white).
80
+ */
81
+ export declare class VfGrid extends VfGrid_base {
82
+ static styles: import("lit").CSSResult[];
83
+ /** Cells across the x axis. */
84
+ columns: number;
85
+ /**
86
+ * Cells down the y axis. Left unset the grid takes as many rows as the
87
+ * slotted cells need; set, it reserves that many — an unfilled cell still
88
+ * gets its rules, the way a palette keeps its empty wells.
89
+ */
90
+ rows?: number;
91
+ /** Cell width in whole system px. */
92
+ cellWidth: number;
93
+ /** Cell height in whole system px. */
94
+ cellHeight: number;
95
+ /**
96
+ * The pen every rule is drawn with: `solid` (default), `dashed` (1 system px
97
+ * on, 1 off), or `none` — no rules at all, and the cells butt together.
98
+ */
99
+ rules: VfGridRules;
100
+ /**
101
+ * Drop the outer border, leaving only the boundaries between cells. For a
102
+ * grid inside something that already draws that line — a `vf-window[flush]`,
103
+ * a `vf-fieldset` — where a second one would read as 2px.
104
+ */
105
+ frameless: boolean;
106
+ /**
107
+ * Collapse each cell's own 1px border onto the grid's rules instead of
108
+ * setting it beside them (see the class doc). Size the cells at *item − 2px*;
109
+ * the frame is what gives the outermost borders a line to land on, so a
110
+ * `frameless` grid lets them paint a pixel outside its box.
111
+ */
112
+ collapse: boolean;
113
+ /** Default-on display scaling (true 72dpi size); see src/scale.ts. */
114
+ private readonly scale;
115
+ /** Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts. */
116
+ private readonly gridSnap;
117
+ protected render(): import("lit-html").TemplateResult<1>;
118
+ }
119
+ declare global {
120
+ interface HTMLElementTagNameMap {
121
+ 'vf-grid': VfGrid;
122
+ }
123
+ }
124
+ export {};
@@ -0,0 +1,133 @@
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 { GridSnapController as a } from "../grid-snap.js";
7
+ import { LitElement as o, css as s, html as c, nothing as l } from "lit";
8
+ import { property as u } from "lit/decorators.js";
9
+ import { styleMap as d } from "lit/directives/style-map.js";
10
+ //#region src/components/vf-grid.ts
11
+ var f = 1, p = 2, m = (e, t, n) => `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='${e}' height='${t}' shape-rendering='crispEdges'%3E${n.map(([e, t]) => `%3Crect x='${e}' y='${t}' width='1' height='1'/%3E`).join("")}%3C/svg%3E")`, h = (e, t, n) => ({
12
+ x: m(2 * e, n, [[0, 0], [e, e % n]]),
13
+ y: m(n, 2 * t, [[0, 0], [t % n, t]])
14
+ }), g = (e, t) => {
15
+ let n = Math.trunc(e);
16
+ return Number.isFinite(n) && n > 0 ? n : t;
17
+ }, _ = (e) => `calc(var(--vf-scale, 1) * ${e}px)`, v = class extends r(o) {
18
+ constructor(...e) {
19
+ super(...e), this.columns = 1, this.cellWidth = 16, this.cellHeight = 16, this.rules = "solid", this.frameless = !1, this.collapse = !1, this.scale = new t(this), this.gridSnap = new a(this);
20
+ }
21
+ static {
22
+ this.styles = [i, s`
23
+ :host {
24
+ display: block;
25
+ }
26
+ .grid {
27
+ display: grid;
28
+ /* A cell is a fixed box and its contents are usually smaller than it —
29
+ a 16px icon in a 26px well is the palette this component was drawn
30
+ for — so center them. Grid's own default (stretch) behaves as
31
+ start for anything with a size of its own, which parks every icon in
32
+ the top-left corner of its cell. A cell that wants the whole well
33
+ (a tool button that inverts when selected, a bordered tile under
34
+ collapse) asks for it with place-self: stretch. */
35
+ place-items: center;
36
+ /* Shrink-wrap to the cells. The host is block-level (a palette sits in
37
+ normal flow, and an inline-level box would hang a line-box descender
38
+ under it inside a flush window), so without this a wider parent would
39
+ stretch the grid and the lattice would tile on across the empty
40
+ space. */
41
+ width: max-content;
42
+ background: var(--vf-surface, var(--vf-white, #fff));
43
+ /* .vf-snap already makes this the positioned ancestor; isolating it
44
+ makes it the stacking context too, so the rule overlay's negative
45
+ z-index lands above this surface and below the cells rather than
46
+ behind the whole component. */
47
+ isolation: isolate;
48
+ }
49
+ /* The perimeter needs a pixel of its own to land on. */
50
+ :host(:not([frameless]):not([rules='none'])) .grid {
51
+ padding: calc(var(--vf-scale, 1) * ${f}px);
52
+ }
53
+ /* The cells are the grid's items, not the slot. */
54
+ slot {
55
+ display: contents;
56
+ }
57
+ /* collapse: every cell pulled back onto the lattice, so a cell drawing
58
+ its own 1px border lands it ON the rule rather than beside it (see the
59
+ class doc). A consumer's own margin still wins, since a light-DOM
60
+ declaration beats a ::slotted one. With no rules there is nothing to
61
+ collapse onto, so it stays inert.
62
+
63
+ These cells are the ones that mean to fill their well, so they keep
64
+ grid's stretch rather than the centering above: a sized tile lands
65
+ where it did before (stretch is start for a definite size, and the
66
+ -1px margins already center it), and an auto-sized one is resized to
67
+ the well — a 14px track with -1px margins makes a 16px item. */
68
+ :host([collapse]:not([rules='none'])) ::slotted(*) {
69
+ margin: calc(var(--vf-scale, 1) * -${f}px);
70
+ place-self: stretch;
71
+ }
72
+ .rules {
73
+ position: absolute;
74
+ inset: 0;
75
+ /* Under the cells, above the surface — see the class doc. */
76
+ z-index: -1;
77
+ background: var(--vf-black, #000);
78
+ mask-image: var(--_rules-x), var(--_rules-y);
79
+ mask-size: var(--_rules-size-x), var(--_rules-size-y);
80
+ mask-repeat: repeat;
81
+ /* Framed (the default): the lattice starts at the box's own edge, so
82
+ the perimeter is simply its first and last lines — one pitch,
83
+ unbroken, all the way across. */
84
+ mask-position: 0 0;
85
+ pointer-events: none;
86
+ }
87
+ /* frameless: interior boundaries only. Pulling the lattice back one pixel
88
+ puts its first line on the first gap instead of the left/top edge, and
89
+ drops the last one just outside the box. */
90
+ :host([frameless]) .rules {
91
+ mask-position: calc(var(--vf-scale, 1) * -${f}px)
92
+ calc(var(--vf-scale, 1) * -${f}px);
93
+ }
94
+ `];
95
+ }
96
+ render() {
97
+ let e = g(this.columns, 1), t = g(this.cellWidth, 16), n = g(this.cellHeight, 16), r = this.rows === void 0 ? 0 : g(this.rows, 0), i = this.rules !== "none", a = i ? f : 0, o = t + a, s = n + a, u = this.rules === "dashed" ? p : f, m = h(o, s, u);
98
+ return c`
99
+ <div
100
+ class="grid vf-snap"
101
+ part="grid"
102
+ style=${d({
103
+ "grid-template-columns": `repeat(${e}, ${_(t)})`,
104
+ "grid-template-rows": r ? `repeat(${r}, ${_(n)})` : null,
105
+ "grid-auto-rows": _(n),
106
+ gap: _(a),
107
+ "--_rules-x": m.x,
108
+ "--_rules-y": m.y,
109
+ "--_rules-size-x": `${_(2 * o)} ${_(u)}`,
110
+ "--_rules-size-y": `${_(u)} ${_(2 * s)}`
111
+ })}
112
+ >
113
+ ${i ? c`<div class="rules" part="rules"></div>` : l}
114
+ <slot></slot>
115
+ </div>
116
+ `;
117
+ }
118
+ };
119
+ n([u({ type: Number })], v.prototype, "columns", void 0), n([u({ type: Number })], v.prototype, "rows", void 0), n([u({
120
+ type: Number,
121
+ attribute: "cell-width"
122
+ })], v.prototype, "cellWidth", void 0), n([u({
123
+ type: Number,
124
+ attribute: "cell-height"
125
+ })], v.prototype, "cellHeight", void 0), n([u({ reflect: !0 })], v.prototype, "rules", void 0), n([u({
126
+ type: Boolean,
127
+ reflect: !0
128
+ })], v.prototype, "frameless", void 0), n([u({
129
+ type: Boolean,
130
+ reflect: !0
131
+ })], v.prototype, "collapse", void 0), v = n([e("vf-grid")], v);
132
+ //#endregion
133
+ export { v as VfGrid };