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,97 @@
1
+ import { DocumentListenersController as e } from "./document-listeners.js";
2
+ import "./motion.js";
3
+ //#region src/menu-press.ts
4
+ var t = class {
5
+ #e;
6
+ #t;
7
+ #n;
8
+ #r;
9
+ #i;
10
+ #a;
11
+ #o;
12
+ constructor(t, n) {
13
+ this.target = n, this.#e = !1, this.#t = !1, this.#n = !1, this.#r = null, this.#i = 0, this.#a = null, this.onPointerDown = (e) => {
14
+ if (e.button > 0 || !e.isPrimary) return;
15
+ let t = this.#p(e.clientX, e.clientY);
16
+ if (t) {
17
+ if (e.preventDefault(), this.#e = !0, this.#n = !1, this.#r = t.item ?? t.menu, this.#i = e.timeStamp, this.#t = !t.menu.open, this.#o.attach(), t.item) {
18
+ this.#f(t.item);
19
+ return;
20
+ }
21
+ t.menu.focus(), this.#t && this.target.open(t.menu);
22
+ }
23
+ }, this.#s = (e) => {
24
+ if (!this.#e) return;
25
+ e.preventDefault();
26
+ let t = this.#p(e.clientX, e.clientY), n = t ? t.item ?? t.menu : null;
27
+ !this.#n && n !== this.#r && (this.#n = !0), t && !t.item && !t.menu.open && this.target.open(t.menu), this.#f(t?.item ?? null);
28
+ }, this.#c = (e) => {
29
+ if (!this.#e) return;
30
+ let t = this.#p(e.clientX, e.clientY), n = this.#t, r = !this.#n, i = e.timeStamp - this.#i < 200;
31
+ this.#u(), !(n && r && i) && this.#d(t?.item ?? null);
32
+ }, this.#l = () => {
33
+ this.#u(), this.#f(null);
34
+ }, t.addController(this), this.#o = new e(t, () => [
35
+ [
36
+ document,
37
+ "pointermove",
38
+ this.#s,
39
+ !0
40
+ ],
41
+ [
42
+ document,
43
+ "pointerup",
44
+ this.#c,
45
+ !0
46
+ ],
47
+ [
48
+ document,
49
+ "pointercancel",
50
+ this.#l,
51
+ !0
52
+ ]
53
+ ]);
54
+ }
55
+ hostDisconnected() {
56
+ this.#u(), this.#f(null);
57
+ }
58
+ #s;
59
+ #c;
60
+ #l;
61
+ #u() {
62
+ this.#e && (this.#e = !1, this.#r = null, this.#o.detach());
63
+ }
64
+ #d(e) {
65
+ if (!e || e.disabled) {
66
+ this.#f(null), this.target.close();
67
+ return;
68
+ }
69
+ this.#f(e), this.#a = null, e.activate();
70
+ }
71
+ #f(e) {
72
+ let t = e && !e.disabled ? e : null;
73
+ t !== this.#a && (this.#a && (this.#a.active = !1), this.#a = t, t && (t.active = !0));
74
+ }
75
+ #p(e, t) {
76
+ let r = this.target.menus(), i = r.find((e) => e.open);
77
+ if (i) {
78
+ for (let r of i.allItems) if (n(r.getBoundingClientRect(), e, t)) return {
79
+ menu: i,
80
+ item: r
81
+ };
82
+ }
83
+ for (let i of r) {
84
+ let r = i.labelRect;
85
+ if (r && n(r, e, t)) return {
86
+ menu: i,
87
+ item: null
88
+ };
89
+ }
90
+ return null;
91
+ }
92
+ };
93
+ function n(e, t, n) {
94
+ return t >= e.left && t < e.right && n >= e.top && n < e.bottom;
95
+ }
96
+ //#endregion
97
+ export { t as MenuPressController };
@@ -0,0 +1,148 @@
1
+ import { LitElement } from 'lit';
2
+ import type { PropertyValues } from 'lit';
3
+ import { ScaleController } from './scale.js';
4
+ /** Reason a modal closed, carried by the `vf-close` event's detail. */
5
+ export type VfCloseReason = 'escape' | 'close';
6
+ /**
7
+ * The width an undeclared modal falls back to, in system px — a classic
8
+ * dialog's own measure, wide enough for a line of chrome text and a button row.
9
+ * It exists so an unsized modal still renders, and still owns a *definite*
10
+ * width so dragging cannot squeeze it; the console says what to declare.
11
+ */
12
+ export declare const MODAL_FALLBACK_WIDTH = 260;
13
+ /**
14
+ * Shared native-`<dialog>` styles for the modal shells: a chromeless top-layer
15
+ * dialog (the frame is drawn by the subclass) with a fully transparent
16
+ * backdrop — pure System 7, no dimming.
17
+ */
18
+ export declare const modalDialogStyles: import("lit").CSSResult;
19
+ /**
20
+ * Base class for the modal shell `vf-dialog` — and for a consumer's own
21
+ * modal (an alert box, say) authored against the kit.
22
+ *
23
+ * Owns the native `<dialog>` lifecycle every modal shares: `open` sync, `show()` /
24
+ * `close()`, the {@link top}/{@link left} placement (stated or centered) and the
25
+ * single `close` funnel that drops the written origin and fires `vf-close` with
26
+ * the reason. Because every close path — Escape, `close()`, backdrop — routes
27
+ * through the native `close` event, an Escape-close no longer leaves a stale
28
+ * origin behind, so the next open re-derives it.
29
+ *
30
+ * Removing an open modal from the DOM is a close path too. HTML's dialog
31
+ * *removing steps* take the element out of the top layer **without** running
32
+ * the close algorithm — no `close` event, no focus restoration — which is
33
+ * exactly what the standard framework pattern of unmounting a dialog instead
34
+ * of calling `close()` does. `disconnectedCallback` routes that path through
35
+ * the same funnel: the removed element still fires `vf-close` (heard by
36
+ * listeners on the element itself — it has left the tree, so nothing
37
+ * bubbles), `open` and the pinned margins reconcile so a re-append mounts it
38
+ * closed and re-centered, and focus returns to the element that was focused
39
+ * when the modal opened.
40
+ *
41
+ * Subclasses supply only the frame chrome: a `render()` returning
42
+ * `<dialog @cancel=${this._onNativeCancel} @close=${this._onNativeClose}>` with
43
+ * their role/ARIA and body, and {@link modalDialogStyles} in `static styles`.
44
+ *
45
+ * @fires vf-close - The modal closed. `detail: { reason: 'escape' | 'close' }`.
46
+ */
47
+ export declare class VfModalDialog extends LitElement {
48
+ #private;
49
+ /** Default-on display scaling (true 72dpi size); see src/scale.ts. */
50
+ protected readonly scale: ScaleController;
51
+ /** Whether the modal is open. Kept in sync with the native `<dialog>`. */
52
+ open: boolean;
53
+ /**
54
+ * Modal width in whole system px — the art's own unit, so the box holds its
55
+ * proportion to the chrome inside it at every display density.
56
+ *
57
+ * **Declare it.** A modal owns its width; it is not a shape its content
58
+ * happens to fall into. The platform's own default is `fit-content` measured
59
+ * against the space *left over* beside its offsets — and stating an offset is
60
+ * exactly how the box is placed and dragged ({@link top}), so an undeclared
61
+ * modal squeezes itself and reflows its text as it moves toward an edge.
62
+ * Unset, it falls back to {@link MODAL_FALLBACK_WIDTH} and says so once in
63
+ * the console.
64
+ */
65
+ width?: number;
66
+ /**
67
+ * Modal height in whole system px.
68
+ *
69
+ * **Declare it,** with {@link width}. A modal is a fixed box in both axes —
70
+ * the same rule `vf-window` follows, for the same reason: the classic shells
71
+ * carried both dimensions in the resource, and a box that grows with its body
72
+ * is one whose buttons move under the pointer as its text changes. Unset, the
73
+ * body sizes to its content — the old behavior — and it says so once in the
74
+ * console.
75
+ *
76
+ * Content taller than the declared box is clipped at the frame.
77
+ */
78
+ height?: number;
79
+ /**
80
+ * Offset from the top of the **viewport**, in whole system px — the same
81
+ * `top`/`left` pair every other component takes ({@link VfPositioned}), in
82
+ * the same unit, with one difference the platform forces: `showModal()` puts
83
+ * the box in the top layer, whose containing block is the viewport rather
84
+ * than the nearest positioned ancestor. So these coordinates are screen
85
+ * coordinates, not the parent's.
86
+ *
87
+ * Leave both unset and the modal is **centered** — recomputed on open, and
88
+ * again whenever its own box or the viewport changes, which is what keeps a
89
+ * dialog whose slotted content upgrades after `showModal()` from opening at
90
+ * the offset its smaller first render centered at. Dragging the title bar
91
+ * states the pair; setting either back to `null` returns the modal to
92
+ * centering.
93
+ */
94
+ top?: number | null;
95
+ /** Offset from the left of the viewport, in whole system px. See {@link top}. */
96
+ left?: number | null;
97
+ protected _dialog: HTMLDialogElement;
98
+ /**
99
+ * The declared box as inline styles for the subclass's `<dialog>`:
100
+ * `style=${styleMap(this.dialogSize)}`. Width always resolves to something
101
+ * definite — the declaration or the fallback — because that is what keeps a
102
+ * drag from squeezing the box.
103
+ */
104
+ protected get dialogSize(): Record<string, string | null>;
105
+ /** Open the modal (native `showModal()`), pinned onto the device grid. */
106
+ show(): void;
107
+ /** Close the modal. Fires `vf-close` with `{ reason: 'close' }`. */
108
+ close(): void;
109
+ protected updated(changed: PropertyValues<this>): void;
110
+ /**
111
+ * State the origin, in system px — clamped on screen and snapped onto the
112
+ * placement lattice, so what `top`/`left` report is what you can see. This is
113
+ * what a title-bar drag calls; a modal that has been placed this way stays
114
+ * placed, and re-centers only if the pair is cleared.
115
+ */
116
+ protected placeAt(x: number, y: number): void;
117
+ /**
118
+ * Write the origin onto the top-layer box: the stated one, or the centered
119
+ * one while the pair is unset. Both go on as a live `calc()` in system px
120
+ * ({@link sysLength}), so a zoom re-resolves the offset with every other
121
+ * metric instead of leaving the box behind at a stale CSS-px constant — and
122
+ * both are re-clamped here, which is what catches a viewport that shrank
123
+ * (zooming in leaves fewer system px on the screen) without forgetting where
124
+ * the modal was put.
125
+ *
126
+ * `position` is deliberately untouched: the UA already makes an open modal
127
+ * `position: fixed`, and the four inset/margin declarations are what turn its
128
+ * `inset: 0; margin: auto` centering into a stated origin.
129
+ */
130
+ protected settle(): void;
131
+ /**
132
+ * Torn out of the DOM while open (the framework-unmount path — see the
133
+ * class doc). `close()` routes the teardown through the same native-`close`
134
+ * funnel every other path uses; the focus restore is ours to do, since the
135
+ * removing steps already dropped focus to `<body>` before this ran.
136
+ */
137
+ disconnectedCallback(): void;
138
+ /** Native `cancel` (Escape): remember the reason; `close` follows. */
139
+ protected _onNativeCancel(): void;
140
+ /**
141
+ * Native `close` — the single teardown funnel for every close path. Drops
142
+ * the written origin so the next open re-derives it from the box it will
143
+ * actually have (a stated `top`/`left` is the author's or the user's and
144
+ * survives, and is re-applied by that open), syncs `open`, and fires
145
+ * `vf-close` with the reason.
146
+ */
147
+ protected _onNativeClose(): void;
148
+ }
@@ -0,0 +1,155 @@
1
+ import { ScaleController as e, onScaleChange as t, snapSys as n, sysLength as r, toSysExact as i } from "./scale.js";
2
+ import a from "./_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
3
+ import { emit as o } from "./events.js";
4
+ import { LitElement as s, css as c } from "lit";
5
+ import { property as l, query as u } from "lit/decorators.js";
6
+ var d = 24, f = c`
7
+ dialog {
8
+ padding: 0;
9
+ margin: auto;
10
+ border: none;
11
+ background: transparent;
12
+ overflow: visible;
13
+ color: inherit;
14
+ font: inherit;
15
+ /* showModal()'s focusing steps fall back to focusing the dialog itself
16
+ when no descendant is chosen as the focus delegate — WebKit and Firefox
17
+ reach that with the kit's controls hidden inside shadow roots — and
18
+ both then draw their native ring around the top-layer box. The modal
19
+ frame already announces the surface, and focus marks inside it are the
20
+ kit's own (1-bit, on the controls), so the UA ring is suppressed rather
21
+ than restyled. */
22
+ outline: none;
23
+ }
24
+ /* Scoped to [open]: an unqualified display here would out-cascade the UA's
25
+ dialog:not([open]) { display: none } (author origin beats UA origin) and
26
+ paint every closed dialog. Flex, so the frame child can resolve against
27
+ the dialog's real box even when no height is declared and the UA's
28
+ dialog:modal max-height is what caps it — a percentage height resolves
29
+ to auto against that (indefinite) box, which left the frame content-tall
30
+ and spilling past the border with its buttons unreachable; a flex child
31
+ with min-height: 0 shrinks to the capped box instead, and the shells'
32
+ inner scroll region takes over from there. */
33
+ dialog[open] {
34
+ display: flex;
35
+ flex-direction: column;
36
+ }
37
+ dialog::backdrop {
38
+ background: transparent;
39
+ /* The backdrop is top-layer: no page cursor rule can reach it, so the
40
+ kit-wide cursor token (SPEC §3) is read here — \`::backdrop\` inherits
41
+ custom properties from its dialog in current engines; where it doesn't,
42
+ the fallback is the arrow it always showed. */
43
+ cursor: var(--vf-cursor, default);
44
+ }
45
+ `, p = class extends s {
46
+ constructor(...t) {
47
+ super(...t), this.scale = new e(this), this.open = !1, this.#e = !1, this.#n = null, this.#r = null, this.#l = () => this.settle();
48
+ }
49
+ get dialogSize() {
50
+ return {
51
+ width: r(this.width ?? 260),
52
+ height: this.height === void 0 ? null : r(this.height)
53
+ };
54
+ }
55
+ #e;
56
+ #t() {
57
+ if (this.#e || !this.open) return;
58
+ let e = [];
59
+ if (this.width === void 0 && e.push("width (falling back to 260 system px)"), this.height === void 0 && e.push("height (falling back to the content)"), e.length === 0) return;
60
+ this.#e = !0;
61
+ let t = this.getAttribute("heading") ?? this.getAttribute("label") ?? "";
62
+ console.warn(`${this.localName}${t ? ` ("${t}")` : ""}: no ${e.join(", no ")}. A modal owns its whole box — declare both in system px: <${this.localName} width="320" height="140">.`);
63
+ }
64
+ #n;
65
+ #r;
66
+ show() {
67
+ this.open = !0, this.hasUpdated && this.#c();
68
+ }
69
+ close() {
70
+ this.open = !1, this.hasUpdated && this.#c();
71
+ }
72
+ updated(e) {
73
+ e.has("open") && this.#c(), (e.has("top") || e.has("left")) && this.settle(), this.#t();
74
+ }
75
+ #i() {
76
+ let e = document.documentElement;
77
+ return {
78
+ width: i(e.clientWidth || window.innerWidth, this),
79
+ height: i(e.clientHeight || window.innerHeight, this)
80
+ };
81
+ }
82
+ #a(e, t) {
83
+ let n = this.#i(), r = i(this._dialog?.offsetWidth ?? 0, this);
84
+ return {
85
+ x: Math.min(Math.max(e, d - r), n.width - d),
86
+ y: Math.min(Math.max(t, 0), Math.max(0, n.height - d))
87
+ };
88
+ }
89
+ #o() {
90
+ let e = this.#i(), t = this._dialog?.getBoundingClientRect();
91
+ return {
92
+ x: (e.width - i(t?.width ?? 0, this)) / 2,
93
+ y: (e.height - i(t?.height ?? 0, this)) / 2
94
+ };
95
+ }
96
+ placeAt(e, t) {
97
+ let r = this.#a(e, t);
98
+ this.left = n(r.x, this), this.top = n(r.y, this);
99
+ }
100
+ settle() {
101
+ let e = this._dialog;
102
+ if (!e?.open) return;
103
+ let t = this.left != null || this.top != null ? {
104
+ x: this.left ?? 0,
105
+ y: this.top ?? 0
106
+ } : this.#o(), i = this.#a(t.x, t.y);
107
+ e.style.left = r(n(i.x, this)), e.style.top = r(n(i.y, this)), e.style.right = "auto", e.style.bottom = "auto", e.style.margin = "0";
108
+ }
109
+ #s() {
110
+ let e = this._dialog?.style;
111
+ if (e) for (let t of [
112
+ "left",
113
+ "top",
114
+ "right",
115
+ "bottom",
116
+ "margin"
117
+ ]) e.removeProperty(t);
118
+ }
119
+ #c() {
120
+ let e = this._dialog;
121
+ e && (this.open && !e.open ? (this.#r = document.activeElement, e.showModal(), this.settle(), this.#f(e)) : !this.open && e.open && e.close());
122
+ }
123
+ #l;
124
+ #u;
125
+ #d;
126
+ #f(e) {
127
+ typeof ResizeObserver < "u" && !this.#u && (this.#u = new ResizeObserver(this.#l), this.#u.observe(e)), window.addEventListener("resize", this.#l), this.#d ??= t(this.#l);
128
+ }
129
+ #p() {
130
+ this.#u?.disconnect(), this.#u = void 0, window.removeEventListener("resize", this.#l), this.#d?.(), this.#d = void 0;
131
+ }
132
+ disconnectedCallback() {
133
+ super.disconnectedCallback(), this.#p();
134
+ let e = this._dialog;
135
+ if (e?.open) {
136
+ e.close();
137
+ let t = this.#r, n = document.activeElement;
138
+ t instanceof HTMLElement && t.isConnected && (n === null || n === document.body) && t.focus();
139
+ }
140
+ this.#r = null;
141
+ }
142
+ _onNativeCancel() {
143
+ this.#n = "escape";
144
+ }
145
+ _onNativeClose() {
146
+ let e = this.#n ?? "close";
147
+ this.#n = null, this.#r = null, this.#p(), this.open = !1, this.#s(), o(this, "vf-close", { reason: e });
148
+ }
149
+ };
150
+ a([l({
151
+ type: Boolean,
152
+ reflect: !0
153
+ })], p.prototype, "open", void 0), a([l({ type: Number })], p.prototype, "width", void 0), a([l({ type: Number })], p.prototype, "height", void 0), a([l({ type: Number })], p.prototype, "top", void 0), a([l({ type: Number })], p.prototype, "left", void 0), a([u("dialog")], p.prototype, "_dialog", void 0);
154
+ //#endregion
155
+ export { p as VfModalDialog, f as modalDialogStyles };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Accessibility: honor the user's "reduce motion" preference.
3
+ *
4
+ * Components use this to skip non-essential animation — chiefly the classic
5
+ * ~250ms selection "blink" on menu items and popup options — and act instantly
6
+ * instead, so keyboard and assistive-tech users aren't made to wait through an
7
+ * effect they've asked the system to suppress. Guarded for SSR / environments
8
+ * without `matchMedia`, where it reports `false` (no reduction).
9
+ */
10
+ export declare function prefersReducedMotion(): boolean;
11
+ /**
12
+ * How long (ms) an *in-place* press may last and still read as a modern
13
+ * click-to-open tap rather than a completed System 7 press.
14
+ *
15
+ * Shared by the two surfaces that offer the classic press-drag-release gesture
16
+ * — `vf-select`'s popup and the pull-down menus (`src/menu-press.ts`) — so the
17
+ * one timing a user can feel across both can't drift between them. Only an
18
+ * in-place release consults it: a press that travels to another row is a
19
+ * drag-pick regardless of duration.
20
+ */
21
+ export declare const PRESS_HOLD_MS = 200;
22
+ /**
23
+ * How long (ms) a press on a selected icon's name waits before it opens the
24
+ * rename box — long enough that a double-click has already declared itself.
25
+ *
26
+ * The Finder's two pointer gestures on a name are the same press: click once
27
+ * and it renames, click twice and it opens. Only the *second* press tells them
28
+ * apart, so the rename cannot commit to opening until the window for one has
29
+ * passed — which is what makes this a delay rather than an immediate response.
30
+ * A press landing inside the window calls the pending rename off outright, so
31
+ * the number is only an upper bound on how slow a double-click may be and still
32
+ * read as one.
33
+ *
34
+ * No API reports what the *browser* counts as a double-click: it follows a
35
+ * platform setting the user can move (Windows offers up to 900 ms, macOS a
36
+ * comparable slider, both defaulting near 500). Overshooting it costs a wait
37
+ * before a rename box appears; undershooting it renames when the user meant to
38
+ * open. So the number leans long deliberately.
39
+ */
40
+ export declare const RENAME_DELAY_MS = 800;
41
+ /**
42
+ * Milliseconds per row while the pointer rests on a menu scroll arrow — the
43
+ * pace a clipped popup rolls its list through the panel at (~15 rows/s).
44
+ *
45
+ * Deliberately *not* gated on `prefers-reduced-motion`, unlike the blink below.
46
+ * The blink is decoration played at the kit's discretion; this stepping is the
47
+ * interaction itself — user-held, discrete, and stopped the moment the pointer
48
+ * leaves — and suppressing it would leave the clipped rows unreachable by
49
+ * pointer. Same reasoning the drag gestures are ungated.
50
+ */
51
+ export declare const MENU_SCROLL_INTERVAL_MS = 66;
52
+ /** Milliseconds between selection-blink phase flips. */
53
+ export declare const BLINK_INTERVAL_MS = 42;
54
+ /** Phase flips in one selection blink (6 = three off/on cycles, ~250ms). */
55
+ export declare const BLINK_FLIPS = 6;
56
+ /** Handle for an in-flight {@link runSelectionBlink}; {@link cancel} stops it. */
57
+ export interface BlinkHandle {
58
+ cancel(): void;
59
+ }
60
+ /**
61
+ * The classic System 7 selection "blink": the highlighted row inverts on/off a
62
+ * few times (~250ms) before the action commits. Owns the shared timing
63
+ * ({@link BLINK_INTERVAL_MS}/{@link BLINK_FLIPS}) and the reduced-motion
64
+ * short-circuit so `vf-menu-item` and `vf-select` can't drift apart on either.
65
+ *
66
+ * `setPhase(on)` is invoked with the highlight state for each frame — the caller
67
+ * maps it onto its own representation (a CSS class, an option's `active` flag).
68
+ * The blink starts in the OFF phase and finishes by calling `onDone()`; under
69
+ * `prefers-reduced-motion` it skips straight to `onDone()` and returns a no-op
70
+ * handle. `setPhase` is never called after `onDone()`.
71
+ */
72
+ export declare function runSelectionBlink(setPhase: (on: boolean) => void, onDone: () => void): BlinkHandle;
package/dist/motion.js ADDED
@@ -0,0 +1,22 @@
1
+ //#region src/motion.ts
2
+ function e() {
3
+ return typeof window < "u" && typeof window.matchMedia == "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
4
+ }
5
+ var t = 200, n = 800, r = 42, i = 6;
6
+ function a(t, n) {
7
+ if (e()) return n(), { cancel() {} };
8
+ let r = 0;
9
+ t(!1);
10
+ let i = window.setInterval(() => {
11
+ if (r += 1, r >= 6) {
12
+ window.clearInterval(i), n();
13
+ return;
14
+ }
15
+ t(r % 2 == 1);
16
+ }, 42);
17
+ return { cancel() {
18
+ window.clearInterval(i);
19
+ } };
20
+ }
21
+ //#endregion
22
+ export { i as BLINK_FLIPS, r as BLINK_INTERVAL_MS, t as PRESS_HOLD_MS, n as RENAME_DELAY_MS, e as prefersReducedMotion, a as runSelectionBlink };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared numeric helpers for the stepped controls (`vf-number-field`,
3
+ * `vf-slider`), which both snap a value to a `step` grid and then fix the
4
+ * floating-point residue with `toFixed()`.
5
+ */
6
+ /**
7
+ * Fraction digits implied by a `step`, i.e. the precision a stepped value
8
+ * should be rounded to.
9
+ *
10
+ * Naively counting the digits after `String(step)`'s decimal point is wrong for
11
+ * steps the runtime stringifies in exponential notation: `String(1e-7)` is
12
+ * `'1e-7'`, which has no dot at all, so a digit count reports 0 and
13
+ * `toFixed(0)` then snaps every stepped value to a whole number. Steps at or
14
+ * below `1e-7` (and, in the other direction, at or above `1e21`) take that
15
+ * form, so both branches are handled here:
16
+ *
17
+ * 0.25 → 2 1e-7 → 7
18
+ * 1 → 0 1.5e-7 → 8
19
+ * 0.001 → 3 1e21 → 0
20
+ *
21
+ * The result is clamped to `[0, MAX_DECIMALS]` so a subnormal step (down to
22
+ * `5e-324`) can't hand `toFixed()` an out-of-range argument.
23
+ */
24
+ export declare function decimalsOf(step: number): number;
package/dist/number.js ADDED
@@ -0,0 +1,12 @@
1
+ function e(e) {
2
+ if (!Number.isFinite(e)) return 0;
3
+ let t = String(Math.abs(e)), n = t.indexOf("e");
4
+ if (n < 0) {
5
+ let e = t.indexOf(".");
6
+ return e < 0 ? 0 : Math.min(100, t.length - e - 1);
7
+ }
8
+ let r = t.slice(0, n), i = Number(t.slice(n + 1)), a = r.indexOf("."), o = a < 0 ? 0 : r.length - a - 1;
9
+ return Math.min(100, Math.max(0, o - i));
10
+ }
11
+ //#endregion
12
+ export { e as decimalsOf };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The Finder "open" ghost — an icon's art redrawn as outline plus dither,
3
+ * derived from the art itself in the client.
4
+ *
5
+ * When a folder or application is open, the Finder redraws its icon as a
6
+ * ghost: the outline stays, and everything inside it becomes pattern. The kit
7
+ * ships no second raster for that state and asks the consumer for none — a
8
+ * System 7 icon's alpha channel IS its mask resource, so both halves of the
9
+ * ghost are derivable from the art already slotted: the outline is the mask's
10
+ * boundary, the fill is the mask's interior.
11
+ *
12
+ * Everything here is draw-and-composite — never `getImageData`, deliberately.
13
+ * A canvas that has drawn a cross-origin image is *tainted*, which forbids
14
+ * reading pixels back but not drawing or displaying them, so a pipeline built
15
+ * purely from `drawImage` and `globalCompositeOperation` behaves identically
16
+ * for data URIs, same-origin files and CORS-less CDN images. Taint propagates
17
+ * through the layers below and none of it matters: the result is only ever
18
+ * displayed. (`ctx.filter` is avoided the same way — compositing operators
19
+ * are universal where canvas filters are Safari's shaky corner.)
20
+ *
21
+ * The one "smart" step, finding the outline, is a 1px erosion, and erosion is
22
+ * expressible as compositing: `destination-in` keeps the destination only
23
+ * where the source is opaque, so intersecting the silhouette with itself
24
+ * shifted one pixel each way leaves exactly the pixels whose neighbors are
25
+ * all opaque — the interior. The ring the erosion removes is the outline.
26
+ */
27
+ /**
28
+ * Derive the open ghost from an icon's art: a canvas at the art's natural
29
+ * size holding its outline in solid black over an interior of dither on
30
+ * opaque white, with the transparent surround untouched.
31
+ *
32
+ * Opaque white matters as much as the black: it keeps the ghost exactly the
33
+ * shape selection expects — ink and opaque fill on a transparent surround —
34
+ * so `filter: invert(1)` produces the selected-open appearance with no
35
+ * second treatment.
36
+ *
37
+ * Returns null when there is nothing to derive: art with no pixels yet, or
38
+ * an environment refusing a 2d context.
39
+ */
40
+ export declare function deriveOpenArt(art: HTMLImageElement | HTMLCanvasElement): HTMLCanvasElement | null;
@@ -0,0 +1,35 @@
1
+ //#region src/open-art.ts
2
+ var e = (e, t) => {
3
+ let n = document.createElement("canvas");
4
+ n.width = e, n.height = t;
5
+ let r = n.getContext("2d");
6
+ return r ? (r.imageSmoothingEnabled = !1, {
7
+ canvas: n,
8
+ ctx: r
9
+ }) : null;
10
+ }, t = null, n = () => {
11
+ if (!t) {
12
+ let n = e(4, 2);
13
+ if (!n) return null;
14
+ n.ctx.fillStyle = "#000", n.ctx.fillRect(0, 0, 1, 1), n.ctx.fillRect(2, 1, 1, 1), t = n.canvas;
15
+ }
16
+ return t;
17
+ };
18
+ function r(t) {
19
+ let r = t instanceof HTMLImageElement ? t.naturalWidth : t.width, i = t instanceof HTMLImageElement ? t.naturalHeight : t.height;
20
+ if (!r || !i) return null;
21
+ let a = e(r, i), o = e(r, i), s = n();
22
+ if (!a || !o || !s) return null;
23
+ a.ctx.drawImage(t, 0, 0), a.ctx.globalCompositeOperation = "source-in", a.ctx.fillStyle = "#000", a.ctx.fillRect(0, 0, r, i), o.ctx.drawImage(a.canvas, 0, 0), o.ctx.globalCompositeOperation = "destination-in";
24
+ for (let [e, t] of [
25
+ [1, 0],
26
+ [-1, 0],
27
+ [0, 1],
28
+ [0, -1]
29
+ ]) o.ctx.drawImage(a.canvas, e, t);
30
+ o.ctx.globalCompositeOperation = "source-in", o.ctx.fillStyle = "#fff", o.ctx.fillRect(0, 0, r, i);
31
+ let c = o.ctx.createPattern(s, "repeat");
32
+ return c && (o.ctx.globalCompositeOperation = "source-atop", o.ctx.fillStyle = c, o.ctx.fillRect(0, 0, r, i)), a.ctx.globalCompositeOperation = "source-over", a.ctx.drawImage(o.canvas, 0, 0), a.canvas;
33
+ }
34
+ //#endregion
35
+ export { r as deriveOpenArt };