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,243 @@
1
+ import { LitElement, type PropertyValues } from 'lit';
2
+ declare const VfDesktop_base: (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof LitElement;
3
+ /**
4
+ * `<vf-desktop>` — the full-bleed classic desktop container.
5
+ *
6
+ * Renders the 50%-dither gray desktop pattern and manages the stacking order
7
+ * and `active` state of slotted `vf-window` children: a `pointerdown` or
8
+ * `focusin` (keyboard focus) anywhere inside a window brings it to the front
9
+ * and makes it the single active window. The windows' light-DOM order is kept
10
+ * in step with the stacking order (bottom-most first, at pointer-gesture
11
+ * ends), so tabbing walks the stack the way the eye does and Shift+Tab is
12
+ * its exact mirror.
13
+ *
14
+ * Utility windows (`vf-window[variant="utility"]`) stack in a floating tier
15
+ * above every document-tier window, restack only among themselves, and stand
16
+ * outside the single-active invariant entirely — clicking a palette neither
17
+ * deactivates the active document window nor greys the palette, exactly as
18
+ * System 7's floating windoids behaved while their application was frontmost.
19
+ *
20
+ * The desktop is a raster with an explicit size, always: **`width` and
21
+ * `height`**, in system px, the way a WIND resource declared a window's —
22
+ * the host box renders at the declared screen plus `2 × bezel` per axis, a
23
+ * whole number of system pixels by construction (default 512×342, the
24
+ * compact Mac's screen). Pure CSS sizing is not supported; the page sets
25
+ * the numbers — directly, or via {@link VfDesktop.fitWithin} on
26
+ * `resize`/`onScaleChange` for a viewport-filling desktop — and positions
27
+ * the sized box with its own stylesheet, keeping any sub-system-pixel
28
+ * slack on its side. `bezel` (system px) draws the black screen surround —
29
+ * the CRT's unlit margin — around the screen, rounding its top corners
30
+ * with the classic corner mask.
31
+ *
32
+ * Custom properties:
33
+ * - `--vf-desktop-pattern` — the dither's tile art (default a 1-bit 50%
34
+ * checker, opaque black-on-white on a 30-system-px tile). Overriding it
35
+ * renders the token as a placed tile grid at that same 30-px geometry
36
+ * (src/tile-grid.ts); a token swapped at runtime without touching the
37
+ * component wants a `requestUpdate()`.
38
+ * - `--vf-desktop` — base color painted *under* the pattern layer (default
39
+ * `#808080`). The default tile is opaque, so this only becomes visible when
40
+ * `--vf-desktop-pattern` is overridden with a tile that has transparent
41
+ * cells (or with `none`).
42
+ *
43
+ * @slot - Default slot: menu bar, windows, anything.
44
+ * @csspart desktop - The dithered screen surface — the whole-system-px
45
+ * raster (inset by `bezel` when one is set).
46
+ * @cssprop [--vf-desktop=#808080] - base color under the desktop dither —
47
+ * occluded by the default (opaque) tile, so it only shows through a custom
48
+ * `--vf-desktop-pattern`
49
+ * @cssprop --vf-desktop-pattern - the desktop dither's art — a 50% checker
50
+ * drawn as opaque black-on-white rects, on a 30-system-px tile. Override the
51
+ * whole tile; consumer art renders as a placed tile grid at that same
52
+ * geometry (raster art magnifies nearest-neighbor, the `vf-img` idiom)
53
+ */
54
+ export declare class VfDesktop extends VfDesktop_base {
55
+ #private;
56
+ static styles: import("lit").CSSResult[];
57
+ /**
58
+ * Screen width in system px — the raster's own size, the way a WIND
59
+ * resource declared a window's. The host box renders at exactly
60
+ * `width + 2 × bezel` system px, always whole; a desktop is never sized
61
+ * by page CSS — the page sets these numbers (directly or via
62
+ * {@link fitWithin}) and positions the explicitly sized box with its own
63
+ * stylesheet, keeping whatever sub-system-pixel slack its layout has on
64
+ * its side of the fence. Defaults to the compact Mac's 512.
65
+ */
66
+ width: number;
67
+ /** Screen height in system px; see {@link width}. Defaults to 342. */
68
+ height: number;
69
+ /**
70
+ * Width of the black screen bezel, in system px (`0` = none), added onto
71
+ * the declared screen on every side — a `width="502" bezel="5"` desktop
72
+ * renders a 512-system-px host box. The compact Mac's CRT showed an
73
+ * unlit black margin between the desktop's raster and the case; `bezel`
74
+ * draws it around the screen and puts the classic screen-corner mask on
75
+ * the screen's two *top* corners — only the top pair was rounded in the
76
+ * framebuffer. Flow, window coordinates and the drag clip all belong to
77
+ * the screen, so windows crop at its edge. Inside a bezeled desktop a
78
+ * menu bar needs no `rounded` of its own — the desktop's mask lands on
79
+ * the same pixels.
80
+ */
81
+ bezel: number;
82
+ /**
83
+ * Size the screen to the largest whole-system-px raster whose host box —
84
+ * bezel included — fits a CSS-px bound, and return what was set. The
85
+ * page's half of the sizing contract: it owns the viewport, so it
86
+ * measures the box and hands it here on `resize` and `onScaleChange`
87
+ * (zoom and density moves change how many CSS px a system px costs),
88
+ * and the sub-system-pixel remainder stays on the page, behind the
89
+ * bezel. The epsilon forgives float error in the division so an
90
+ * exact-fit bound never drops a whole system pixel.
91
+ */
92
+ fitWithin(maxWidth: number, maxHeight: number): {
93
+ width: number;
94
+ height: number;
95
+ };
96
+ /** Slotted `vf-window` children (direct children only). */
97
+ private _windows;
98
+ /** Default-on display scaling (true 72dpi size); see src/scale.ts. */
99
+ private readonly scale;
100
+ /** Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts. */
101
+ private readonly gridSnap;
102
+ /**
103
+ * The consumer's `--vf-desktop-pattern` override, or `''` for the kit
104
+ * dither — which of the two exact-fill paths render() takes (see
105
+ * src/tile-grid.ts). Re-read every update; a token swapped at runtime
106
+ * without touching the component wants a `requestUpdate()`.
107
+ */
108
+ private _pattern;
109
+ /** Monotonic z-index counter for window stacking. */
110
+ private _zCounter;
111
+ /** Whether a one-shot post-upgrade re-normalization is already pending. */
112
+ private _awaitingUpgrade;
113
+ /** Whether a pointer gesture that began on the desktop is still in flight. */
114
+ private _pointerGesture;
115
+ /** Whether {@link _syncDomOrder} is putting focus back after a node move. */
116
+ private _restoringFocus;
117
+ /**
118
+ * Ends the pointer-gesture window that defers DOM reordering. Capture-phase
119
+ * on the document so a component's `stopPropagation` can't strand the flag,
120
+ * and detached on host disconnect by the controller.
121
+ */
122
+ private readonly _gestureEnd;
123
+ connectedCallback(): void;
124
+ disconnectedCallback(): void;
125
+ /** Whether a slotted window belongs to the floating (utility) tier. The
126
+ * attribute is the source of truth here so a not-yet-upgraded element
127
+ * still lands in the right tier (variant reflects, so an upgraded
128
+ * property-set window agrees). */
129
+ private _isUtility;
130
+ /**
131
+ * Bring a slotted window to the front of its tier. A document-tier window
132
+ * also becomes the single active window (clearing `active` on the other
133
+ * document windows); a utility window restacks within the floating tier
134
+ * and leaves every `active` state alone.
135
+ *
136
+ * The light-DOM order of the slotted windows follows ({@link _syncDomOrder}):
137
+ * visual stacking and sequential focus order come from independent channels
138
+ * (z-index vs DOM position), and letting them drift apart is how a desktop
139
+ * tabs front-to-back one way and back-to-front the other, with widgets
140
+ * reachable only travelling backwards. Deferred to the end of any in-flight
141
+ * pointer gesture — moving a node clears the pointer capture a title-bar
142
+ * drag or grow-box resize holds on it, and a background window must stay
143
+ * draggable in the same gesture that raises it.
144
+ */
145
+ bringToFront(win: HTMLElement): void;
146
+ /** The z/active half of a raise, shared by every path. */
147
+ private _restack;
148
+ /** Sync the DOM order now, or at gesture end if a pointer is down. */
149
+ private _requestDomSync;
150
+ /**
151
+ * Raise the window an event originated in, skipping the restack/activation
152
+ * churn when it is already on top of its own tier (and, for a document
153
+ * window, already active): otherwise every click inside the front window
154
+ * would bump _zCounter and re-run the whole-fleet activation loop for
155
+ * nothing.
156
+ *
157
+ * Restacks z/active only — deliberately no DOM sync. A focus-driven raise
158
+ * MUST NOT move nodes: moving the window focus just entered re-orders the
159
+ * sequence mid-traversal, and a Shift+Tab that raises each window it
160
+ * enters (pushing it forward in the DOM, back the way the traversal came)
161
+ * would revisit it forever. The pointer path syncs at gesture end instead,
162
+ * which is also the next safe point after any keyboard-session staleness.
163
+ */
164
+ private _raise;
165
+ /**
166
+ * Delegated pointerdown: raise the window the event originated in. Any
167
+ * press opens the gesture window that defers DOM reordering (see
168
+ * {@link bringToFront}) — a raise can be requested mid-gesture by this
169
+ * press or by code the press runs.
170
+ */
171
+ private _onPointerDown;
172
+ /**
173
+ * The press ended (or was cancelled) — sync the DOM order. Unconditional
174
+ * rather than only-if-raised: the sync no-ops when order already agrees,
175
+ * and running it at every gesture end is what heals the staleness a
176
+ * keyboard-only stretch leaves behind (focus-driven raises change z but
177
+ * never move nodes — see {@link _raise}).
178
+ */
179
+ private _onGestureEnd;
180
+ /**
181
+ * Delegated focusin: raise the window keyboard focus entered, so tabbing
182
+ * into a background window brings it to front (and reveals its close/zoom
183
+ * widgets) just like a pointerdown would. Ignored while `_syncDomOrder` is
184
+ * putting focus back after a node move — the restore re-fires focusin on
185
+ * an element that may sit in a *background* window, and raising that
186
+ * window would undo the raise that triggered the sync.
187
+ */
188
+ private _onFocusIn;
189
+ /** The slotted window the event originated in, if any. */
190
+ private _windowFromEvent;
191
+ /** Wire up newly slotted windows: seed z-indices, normalize `active`. */
192
+ private _onSlotChange;
193
+ /**
194
+ * Re-assert the single-active-window state after slotted `<vf-window>`
195
+ * elements upgrade. One-shot: the flag collapses repeat slotchanges into a
196
+ * single wait, and once the definition is registered `_onSlotChange` stops
197
+ * calling this at all. If a consumer never imports vf-window the promise
198
+ * simply never settles — those elements render nothing either way.
199
+ */
200
+ private _normalizeAfterUpgrade;
201
+ /** Set `active` on `win` only, clearing it on every other *document-tier*
202
+ * window. Utility windows stand outside the invariant: a palette keeps
203
+ * whatever `active` state it has (true by default, so its dither and
204
+ * widgets stay drawn while document windows trade the highlight). */
205
+ private _setActive;
206
+ /**
207
+ * Set a window's `active` state. Prefers the property (the source of truth
208
+ * on an upgraded `vf-window`, whose reflection then wins over any pending
209
+ * initial-value reflection); falls back to the attribute for not-yet
210
+ * upgraded elements.
211
+ */
212
+ private _setWindowActive;
213
+ /** The window with the highest z-index, or null when there are none. */
214
+ private _topmost;
215
+ /** The innermost focused element, through open shadow roots. */
216
+ private _deepActiveElement;
217
+ /**
218
+ * Re-order the slotted windows in the light DOM to match their z-order
219
+ * (bottom-most first), so sequential focus navigation walks the stack the
220
+ * way the eye does — and Shift+Tab is its exact mirror. The utility band
221
+ * sorts the floating tier after every document window by construction.
222
+ *
223
+ * Minimal-move: windows already in relative order are never touched (the
224
+ * common case — after one raise, one window moves). Non-window siblings
225
+ * (a menu bar, page content) keep their positions; only a window that must
226
+ * cross the stack moves past them. Moving a node containing the focused
227
+ * element drops focus to `<body>`, so it is restored afterwards — behind
228
+ * `_restoringFocus`, because the restore re-fires focusin (see
229
+ * {@link _onFocusIn}) on an element that may sit in a background window.
230
+ * Only runs between pointer gestures or programmatically, never from a
231
+ * focus-driven raise (see {@link _raise} for why).
232
+ */
233
+ private _syncDomOrder;
234
+ protected willUpdate(changed: PropertyValues<this>): void;
235
+ protected updated(changed: Map<PropertyKey, unknown>): void;
236
+ protected render(): unknown;
237
+ }
238
+ declare global {
239
+ interface HTMLElementTagNameMap {
240
+ 'vf-desktop': VfDesktop;
241
+ }
242
+ }
243
+ export {};
@@ -0,0 +1,310 @@
1
+ import { vfElement as e } from "../define.js";
2
+ import { ScaleController as t, effectiveScale as n, sysLength as r } from "../scale.js";
3
+ import i from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
+ import { VfPositioned as a } from "../position.js";
5
+ import { vfBase as o } from "../styles/recipes/host.js";
6
+ import { tileImage as s, tileRaster as c, tileRects as l, tileSpan as u, vfTileSize 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 { DocumentListenersController as _ } from "../document-listeners.js";
10
+ import { SCREEN_CORNER as v, steppedCornerClip as y } from "../pixel-frame.js";
11
+ import { LitElement as b, css as x, html as S, unsafeCSS as C } from "lit";
12
+ import { property as w, queryAssignedElements as T } from "lit/decorators.js";
13
+ //#region src/components/vf-desktop.ts
14
+ var E = 1e6, D = 512, O = 342, k = 2, A = [
15
+ [
16
+ 0,
17
+ 0,
18
+ 2,
19
+ 2,
20
+ "#ffffff"
21
+ ],
22
+ [
23
+ 0,
24
+ 0,
25
+ 1,
26
+ 1,
27
+ "#000000"
28
+ ],
29
+ [
30
+ 1,
31
+ 1,
32
+ 1,
33
+ 1,
34
+ "#000000"
35
+ ]
36
+ ], j = s(k, k, l(A)), M = c(k, k, A), N = u(k), P = class extends a(b) {
37
+ constructor(...e) {
38
+ super(...e), this.width = D, this.height = O, this.bezel = 0, this.scale = new t(this), this.gridSnap = new g(this), this._pattern = "", this.#e = new f(), this._zCounter = 0, this._awaitingUpgrade = !1, this._pointerGesture = !1, this._restoringFocus = !1, this._gestureEnd = new _(this, () => [[
39
+ document,
40
+ "pointerup",
41
+ this._onGestureEnd,
42
+ !0
43
+ ], [
44
+ document,
45
+ "pointercancel",
46
+ this._onGestureEnd,
47
+ !0
48
+ ]]), this._onPointerDown = (e) => {
49
+ this._pointerGesture || (this._pointerGesture = !0, this._gestureEnd.attach());
50
+ let t = this._windowFromEvent(e);
51
+ t && this._raise(t);
52
+ }, this._onGestureEnd = () => {
53
+ this._gestureEnd.detach(), this._pointerGesture = !1, this._syncDomOrder();
54
+ }, this._onFocusIn = (e) => {
55
+ if (this._restoringFocus) return;
56
+ let t = this._windowFromEvent(e);
57
+ t && this._raise(t);
58
+ };
59
+ }
60
+ static {
61
+ this.styles = [
62
+ o,
63
+ h,
64
+ x`
65
+ :host {
66
+ display: block;
67
+ position: relative;
68
+ }
69
+ .desktop {
70
+ position: relative;
71
+ width: 100%;
72
+ height: 100%;
73
+ }
74
+ /* The bezel surface: the unlit black margin a compact Mac's CRT showed
75
+ between the raster and the case. Painted only when bezeled, so an
76
+ unbezeled desktop's paint is exactly the screen's. */
77
+ .desktop.bezeled {
78
+ background: var(--vf-black, #000);
79
+ }
80
+ .screen {
81
+ position: absolute;
82
+ top: 0;
83
+ left: 0;
84
+ width: 100%;
85
+ height: 100%;
86
+ /* Clip here rather than on the host so dragged windows crop at the
87
+ raster's corrected edge, not the host's possibly-fractional one. */
88
+ overflow: hidden;
89
+ /* An isolated stacking context, so the tile grid's z-index: -1 below
90
+ resolves HERE — above this element's own underlay background, under
91
+ every slotted child. Without it the negative z-index would resolve
92
+ against some ancestor stacking context and paint the tiles beneath
93
+ this background. */
94
+ isolation: isolate;
95
+ background-color: var(--vf-desktop, #808080);
96
+ /* Classic 50% checker dither as a crisp 1-bit SVG tile — a 2×2 motif
97
+ painting an opaque white base with two black pixels on the diagonal.
98
+ Black-on-white is the authentic System 7 dither, so the tile is
99
+ deliberately opaque and covers the background-color above (see the
100
+ --vf-desktop note in the class doc). Scaled with --vf-scale so each
101
+ system pixel lands on whole device pixels; unlike a conic-gradient
102
+ (whose hard stops the browser feathers into a blur), the SVG rects are
103
+ pixel-exact.
104
+
105
+ This CSS-repeated layer is the UNDERLAY: what actually shows is the
106
+ whole-surface raster (or, under a consumer pattern token, the
107
+ placed tile grid) rendered over it — see src/tile-grid.ts — which
108
+ is what keeps the dither 1-bit at the zoom-minted scales a
109
+ repeating fill cannot hold. The declaration stays as
110
+ belt-and-braces paint beneath the (opaque) kit fill. Override the
111
+ whole tile via --vf-desktop-pattern — the tile grid renders the
112
+ same token at the same documented geometry. */
113
+ background-image: var(--vf-desktop-pattern, ${C(j)});
114
+ ${d(k)}
115
+ /* Forced colors: the checker is opaque literal white-and-black (a
116
+ preserved url() tile), which ignores a dark theme entirely. A
117
+ backdrop is decoration, and high-contrast mode is a request for
118
+ less of that — so the desktop goes flat Canvas (the forced
119
+ background-color) rather than re-dithering in the user's pair.
120
+ Deliberate; the windoid dot bar, which carries meaning, IS
121
+ re-inked (see vfDots). */
122
+ @media (forced-colors: active) {
123
+ background-image: none;
124
+ }
125
+ }
126
+ /* With a consumer pattern token in play the exact fill is the placed
127
+ tile grid, and the CSS-repeated underlay must not paint: a
128
+ translucent consumer tile would show the underlay's *drifting* copy
129
+ of the same art through itself. The base color stays — that is the
130
+ layer translucent art composites over, as documented. */
131
+ .screen.patterned {
132
+ background-image: none;
133
+ }
134
+ /* The dither's exact fill — the whole-surface raster (kit art) or the
135
+ placed tile grid (consumer art) — kept under every slotted child
136
+ (menu bar, windows) by the negative z-index the .screen isolation
137
+ scopes. The grid's tiles resolve the pattern token stated once here
138
+ rather than per tile. */
139
+ .screen > .vf-tile-grid,
140
+ .screen > .vf-tile-raster {
141
+ z-index: -1;
142
+ /* Forced colors: hidden with the underlay's tile, same posture — the
143
+ desktop goes flat Canvas (a backdrop is decoration). */
144
+ @media (forced-colors: active) {
145
+ display: none;
146
+ }
147
+ }
148
+ .screen > .vf-tile-grid {
149
+ position: absolute;
150
+ inset: 0;
151
+ --_vf-tile-image: var(--vf-desktop-pattern, ${C(M)});
152
+ }
153
+ /* Bezeled, the screen is inset by exactly one bezel width. The host
154
+ is always the declared screen + 2×bezel (written in updated()), so
155
+ this subtraction is exact and the raster is whole by construction —
156
+ a page's sub-system-pixel slack never enters the component. */
157
+ .bezeled .screen {
158
+ top: var(--vf-desktop-bezel, 0px);
159
+ left: var(--vf-desktop-bezel, 0px);
160
+ width: calc(100% - 2 * var(--vf-desktop-bezel, 0px));
161
+ height: calc(100% - 2 * var(--vf-desktop-bezel, 0px));
162
+ }
163
+ /* Slotted windows need a positioning context so z-index applies.
164
+ (An inline position: absolute set by a movable window wins.) */
165
+ ::slotted(vf-window) {
166
+ position: relative;
167
+ }
168
+ /* With a bezel, the screen's top corners wear the SCREEN_CORNER mask,
169
+ rounding into the surrounding black — the top pair only, because the
170
+ classic framebuffer masked only those; the raster's bottom corners
171
+ ran square. Children of .screen, so they anchor to the raster's own
172
+ corners (over a slotted menu bar's, which sit in the same place). The
173
+ hardware mask was in front of every pixel — a window dragged into a
174
+ corner slides under it — hence the maximal z-index. */
175
+ .corner {
176
+ position: absolute;
177
+ top: 0;
178
+ width: calc(var(--vf-scale, 1) * ${v[0]}px);
179
+ height: calc(var(--vf-scale, 1) * ${v.length}px);
180
+ background: var(--vf-black, #000);
181
+ pointer-events: none;
182
+ z-index: 2147483647;
183
+ }
184
+ .corner.tl {
185
+ left: 0;
186
+ clip-path: ${C(y(v, "left"))};
187
+ }
188
+ .corner.tr {
189
+ right: 0;
190
+ clip-path: ${C(y(v, "right"))};
191
+ }
192
+ `
193
+ ];
194
+ }
195
+ fitWithin(e, t) {
196
+ let r = n(this), i = (e) => Math.max(0, Math.floor(e / r + 1e-6) - 2 * this.bezel);
197
+ return this.width = i(e), this.height = i(t), {
198
+ width: this.width,
199
+ height: this.height
200
+ };
201
+ }
202
+ #e;
203
+ connectedCallback() {
204
+ super.connectedCallback(), this.addEventListener("pointerdown", this._onPointerDown), this.addEventListener("focusin", this._onFocusIn);
205
+ }
206
+ disconnectedCallback() {
207
+ this.removeEventListener("pointerdown", this._onPointerDown), this.removeEventListener("focusin", this._onFocusIn), this._pointerGesture = !1, super.disconnectedCallback();
208
+ }
209
+ _isUtility(e) {
210
+ return e.getAttribute("variant") === "utility";
211
+ }
212
+ bringToFront(e) {
213
+ this._restack(e), this._requestDomSync();
214
+ }
215
+ _restack(e) {
216
+ let t = this._isUtility(e);
217
+ e.style.zIndex = String(++this._zCounter + (t ? E : 0)), t || this._setActive(e);
218
+ }
219
+ _requestDomSync() {
220
+ this._pointerGesture || this._syncDomOrder();
221
+ }
222
+ _raise(e) {
223
+ let t = this._isUtility(e), n = this._windows.filter((e) => this._isUtility(e) === t);
224
+ this._topmost(n) === e && (t || e.hasAttribute("active")) || this._restack(e);
225
+ }
226
+ _windowFromEvent(e) {
227
+ let t = this._windows;
228
+ return e.composedPath().find((e) => e instanceof HTMLElement && t.includes(e));
229
+ }
230
+ _onSlotChange() {
231
+ let e = this._windows, t = null;
232
+ for (let n of e) if (!n.style.zIndex) {
233
+ let e = this._isUtility(n);
234
+ n.style.zIndex = String(++this._zCounter + (e ? E : 0)), e || (t = n);
235
+ }
236
+ let n = t ?? this._topmost(e.filter((e) => !this._isUtility(e)));
237
+ n && this._setActive(n), customElements.get("vf-window") || this._normalizeAfterUpgrade();
238
+ }
239
+ _normalizeAfterUpgrade() {
240
+ this._awaitingUpgrade || (this._awaitingUpgrade = !0, customElements.whenDefined("vf-window").then(() => {
241
+ if (this._awaitingUpgrade = !1, !this.isConnected) return;
242
+ let e = this._topmost(this._windows.filter((e) => !this._isUtility(e)));
243
+ e && this._setActive(e);
244
+ }));
245
+ }
246
+ _setActive(e) {
247
+ for (let t of this._windows) this._isUtility(t) || this._setWindowActive(t, t === e);
248
+ }
249
+ _setWindowActive(e, t) {
250
+ if ("active" in e) {
251
+ let n = e;
252
+ n.active !== t && (n.active = t);
253
+ } else e.hasAttribute("active") !== t && e.toggleAttribute("active", t);
254
+ }
255
+ _topmost(e) {
256
+ let t = null, n = -Infinity;
257
+ for (let r of e) {
258
+ let e = Number(r.style.zIndex) || 0;
259
+ e >= n && (n = e, t = r);
260
+ }
261
+ return t;
262
+ }
263
+ _deepActiveElement() {
264
+ let e = document.activeElement;
265
+ for (; e?.shadowRoot?.activeElement;) e = e.shadowRoot.activeElement;
266
+ return e;
267
+ }
268
+ _syncDomOrder() {
269
+ if (!this.isConnected) return;
270
+ let e = this._windows;
271
+ if (e.length < 2) return;
272
+ let t = [...e].sort((e, t) => (Number(e.style.zIndex) || 0) - (Number(t.style.zIndex) || 0)), n = this._deepActiveElement(), r = !1, i = null;
273
+ for (let e of t) i && i.compareDocumentPosition(e) & Node.DOCUMENT_POSITION_PRECEDING && (this.insertBefore(e, i.nextSibling), r = !0), i = e;
274
+ r && n instanceof HTMLElement && n.isConnected && this._deepActiveElement() !== n && (this._restoringFocus = !0, n.focus({ preventScroll: !0 }), this._restoringFocus = !1);
275
+ }
276
+ willUpdate(e) {
277
+ super.willUpdate(e), this._pattern = p(this, "--vf-desktop-pattern");
278
+ }
279
+ updated(e) {
280
+ if (e.has("bezel") && (this.bezel > 0 ? this.style.setProperty("--vf-desktop-bezel", r(this.bezel)) : this.style.removeProperty("--vf-desktop-bezel")), e.has("width") || e.has("height") || e.has("bezel")) {
281
+ let e = this.width ?? D, t = this.height ?? O;
282
+ this.style.width = r(e + 2 * this.bezel), this.style.height = r(t + 2 * this.bezel);
283
+ }
284
+ }
285
+ render() {
286
+ let e = this.width ?? D, t = this.height ?? O, n = Math.ceil(e / N), i = Math.ceil(t / N), a = this._pattern ? S`<div class="vf-tile-grid">
287
+ ${m({
288
+ cols: n,
289
+ rows: i,
290
+ tile: N
291
+ })}
292
+ </div>` : S`<div
293
+ class="vf-tile-raster"
294
+ style="width:${r(n * N)};height:${r(i * N)};background-image:${this.#e.for(k, k, A, n * N, i * N)}"
295
+ ></div>`;
296
+ return S`
297
+ <div class=${this.bezel > 0 ? "desktop vf-snap bezeled" : "desktop vf-snap"}>
298
+ <div class="screen${this._pattern ? " patterned" : ""}" part="desktop">
299
+ ${a}
300
+ <slot @slotchange=${this._onSlotChange}></slot>
301
+ ${this.bezel > 0 ? S`<div class="corner tl"></div>
302
+ <div class="corner tr"></div>` : null}
303
+ </div>
304
+ </div>
305
+ `;
306
+ }
307
+ };
308
+ i([w({ type: Number })], P.prototype, "width", void 0), i([w({ type: Number })], P.prototype, "height", void 0), i([w({ type: Number })], P.prototype, "bezel", void 0), i([T({ selector: "vf-window" })], P.prototype, "_windows", void 0), P = i([e("vf-desktop")], P);
309
+ //#endregion
310
+ export { P as VfDesktop };
@@ -0,0 +1,105 @@
1
+ import { VfModalDialog } from '../modal-dialog.js';
2
+ import './vf-button-group.js';
3
+ /**
4
+ * `<vf-dialog>` — the System 7 modal dialog shell.
5
+ *
6
+ * Two chromes, one modal lifecycle (native `<dialog>` for top-layer rendering
7
+ * and focus trapping, with a fully transparent backdrop — no dimming):
8
+ *
9
+ * - **Default:** a striped title bar with a centered title over a white body —
10
+ * the movable-modal look. Drag the title bar to move it. `closable` adds the
11
+ * standard close box (left of the bar) — the HIG's own figures disagree on
12
+ * whether a movable modal carries one (Figure 5-1 says yes, Figure 6-1 and
13
+ * the Chapter 6 text say no), so the component enables either reading rather
14
+ * than enforcing one.
15
+ * - **`frame="plain"`:** the classic dBoxProc modal-dialog frame — 1px outer
16
+ * border, 2px gap, 2px inner band, no shadow, no title bar — and immovable,
17
+ * like the original. A `heading` renders as a centered display-face heading
18
+ * at the top of the body (the reference art's "Dialog title"); `closable` is
19
+ * ignored, there being no bar to carry the widget.
20
+ *
21
+ * Open it with `show()` (or set the `open` attribute/property); close with
22
+ * `close()`. Escape closes it and fires `vf-close` with
23
+ * `{ reason: 'escape' }`; the close box and programmatic closing fire
24
+ * `{ reason: 'close' }`.
25
+ *
26
+ * @slot - Default slot: dialog body content.
27
+ * @slot buttons - Optional action buttons. Rendered as a bottom-right
28
+ * `vf-button-group` (equal-width, faces aligned); the footer only takes
29
+ * space when the slot is populated.
30
+ * @csspart frame - The outer chrome frame (striped-bar or plain).
31
+ * @csspart title-bar - The striped title bar (default chrome only).
32
+ * @csspart title - The centered title patch (or the plain-frame heading).
33
+ * @csspart close-box - The close widget (`closable`, default chrome only).
34
+ * @csspart body - The white content area.
35
+ * @csspart content - The scrolling region inside the body (heading + slotted
36
+ * content, not the footer). Inert while the content fits; over-stuffed, it
37
+ * scrolls under a System 7 rail and becomes a keyboard stop.
38
+ * @csspart footer - The action row wrapping the buttons.
39
+ * @csspart buttons - The button group inside the footer.
40
+ * @fires vf-close - Dialog closed. Detail `{ reason: 'escape' | 'close' }`.
41
+ * @cssprop --vf-dots-pattern - the windoid bar's dot-grid dither — a 2×2 tile,
42
+ * one black pixel at the origin (`vfDots`; override the whole pattern like
43
+ * `--vf-desktop-pattern`)
44
+ * @cssprop --vf-titlebar-height - window/dialog title bars
45
+ * @cssprop --vf-scrollbar-thumb - scrollbar thumb/elevator (white)
46
+ * @cssprop --vf-scrollbar-track - the scroll trough's base color under the
47
+ * dot-dither (white)
48
+ */
49
+ export declare class VfDialog extends VfModalDialog {
50
+ static styles: import("lit").CSSResult[];
51
+ /**
52
+ * Title-bar drag-to-move (shared with `vf-window` via {@link DragController}).
53
+ * The gesture states the modal's `top`/`left` in system px — viewport
54
+ * coordinates, since `showModal()` puts the box in the top layer — so a
55
+ * dragged dialog is placed exactly the way an authored one is, and holds its
56
+ * spot through a zoom rather than being re-centered by it. Inert with
57
+ * `frame="plain"` — no bar is rendered, so no pointer ever reaches the
58
+ * controller (dBoxProc dialogs don't move).
59
+ */
60
+ private readonly _drag;
61
+ /** Holds the centered title patch on the placement lattice (src/chrome.ts). */
62
+ private readonly _titleCenter;
63
+ /** Title text: the bar's centered patch, or the plain frame's heading. */
64
+ heading: string;
65
+ /**
66
+ * Accessible name for the dialog (`aria-label`), for a dialog with no
67
+ * `heading` — an untitled title bar has no text to be named by. Ignored when
68
+ * empty and a `heading` is set (the title patch names the dialog then);
69
+ * defaults to `'Dialog'` when neither is given.
70
+ */
71
+ label: string;
72
+ /**
73
+ * Show the close box (left side of the title bar). Off by default — the
74
+ * bare movable-modal bar. Ignored with `frame="plain"` (no bar). Clicking
75
+ * it closes the dialog and fires `vf-close` with `{ reason: 'close' }`.
76
+ */
77
+ closable: boolean;
78
+ /**
79
+ * Frame chrome. Omit for the striped title bar (movable modal); `'plain'`
80
+ * for the immovable dBoxProc double frame with no bar (modal dialog box).
81
+ */
82
+ frame?: 'plain';
83
+ /** Whether the `buttons` slot has assigned content (drives the footer). */
84
+ private _hasButtons;
85
+ private _content;
86
+ /** Whether the body content overflows its box (drives the content stop). */
87
+ private _scrollable;
88
+ /**
89
+ * Activates the System 7 rail — and the content region's keyboard stop —
90
+ * once the body content overflows the declared (or viewport-capped) box.
91
+ */
92
+ private readonly _scrollState;
93
+ /** Syncs the drawn rail to the content region and drives its interactions. */
94
+ private readonly _rail;
95
+ /** Content changed under the fixed box — re-measure the overflow. */
96
+ private _onBodySlotChange;
97
+ private _onCloseClick;
98
+ protected render(): unknown;
99
+ private _onButtonsSlotChange;
100
+ }
101
+ declare global {
102
+ interface HTMLElementTagNameMap {
103
+ 'vf-dialog': VfDialog;
104
+ }
105
+ }