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,37 @@
1
+ /**
2
+ * The embedded System 7 pointer set — GENERATED by `npm run embed:cursors`
3
+ * from `cursors/*.png`; edit the art there (or the hotspot/XOR manifest in
4
+ * `scripts/embed-cursor-art.mjs`) and re-run rather than editing this file.
5
+ *
6
+ * Like the bitmap faces, the art ships base64-embedded so the package needs
7
+ * no asset files: these are the defaults `applyCursor()` uses, exported so a
8
+ * consumer can pass one explicitly, remap it to another kind, or mix the set
9
+ * with art of their own. XOR art carries a `staticSrc` variant — the same
10
+ * ink under a one-pixel white halo — for the engines whose top layer cannot
11
+ * blend; supply `cursors/<name>-static.png` to replace the generated halo
12
+ * with hand-drawn art.
13
+ */
14
+ import type { VfCursorArt } from './cursor.js';
15
+ /**
16
+ * The System 7 arrow. Hotspot on the black tip at (1,1), inside the
17
+ * white outline whose corner is (0,0); the outline is what keeps it legible
18
+ * on any background, so it draws as-is rather than with the XOR pen.
19
+ */
20
+ export declare const CURSOR_ARROW: VfCursorArt;
21
+ /**
22
+ * The text-entry I-beam. Hotspot mid-stem, per the classic CURS
23
+ * resource; draws with the XOR pen, as System 7 drew it, with a white-halo
24
+ * static variant for the engines that cannot (see `staticSrc`).
25
+ */
26
+ export declare const CURSOR_I_BEAM: VfCursorArt;
27
+ /**
28
+ * The crosshair. Hotspot on the intersection; draws with the XOR pen,
29
+ * as System 7 drew it, with a white-halo static variant for the engines
30
+ * that cannot (see `staticSrc`).
31
+ */
32
+ export declare const CURSOR_CROSSHAIR: VfCursorArt;
33
+ /**
34
+ * The wristwatch, 8 frames of turning hand. Opaque white face, so it
35
+ * reads on any background drawn as-is.
36
+ */
37
+ export declare const CURSOR_WAIT: VfCursorArt;
@@ -0,0 +1,41 @@
1
+ //#region src/cursor-art.ts
2
+ var e = {
3
+ src: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAQCAYAAADAvYV+AAAAS0lEQVR42q2SMQ4AMAgC/f+n20WTliowlNW7RA2xMuEkQU8o2BJOWAoIU6GDR2GCW4HBj6DgS2A7Y7o/6wNrSAXshoS/VcAuGK64ASXf4R9SU/euAAAAAElFTkSuQmCC",
4
+ width: 11,
5
+ height: 16,
6
+ hotspotX: 1,
7
+ hotspotY: 1
8
+ }, t = {
9
+ src: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAASCAYAAACJgPRIAAAAJElEQVR42mNgIAH8h2JCYnAJBlyS2BSOKhpaivBGMElJBSsAAPlIF+m2BB32AAAAAElFTkSuQmCC",
10
+ staticSrc: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAASCAYAAACJgPRIAAAAOklEQVR42mP4jwYYGBgYMMSgEv/R5ZDFGNAlkGlk0xmQrUBWwIANoJnGMKqIlorwRgtREUxyUsGV6AB8JyboJ0V9MQAAAABJRU5ErkJggg==",
11
+ width: 9,
12
+ height: 18,
13
+ hotspotX: 4,
14
+ hotspotY: 8,
15
+ invert: !0
16
+ }, n = {
17
+ src: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVR42mNgoDH4P2oAHQ34TyYeDcSRYQBBAAA+6BrmCTIofgAAAABJRU5ErkJggg==",
18
+ staticSrc: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMElEQVR42mNgwAH+IwEGcgBU46gBxBjwHw8gIA1RA1NIDoYbQJELRqNxsBtATHYGAOqhCgWyPk7UAAAAAElFTkSuQmCC",
19
+ width: 16,
20
+ height: 16,
21
+ hotspotX: 7,
22
+ hotspotY: 7,
23
+ invert: !0
24
+ }, r = {
25
+ src: [
26
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAANElEQVR42mNgwA7+48BEgyFuwH9cgBhDsGoixRC6GfCfoAHIgUeWAeQGJMUGUByNIzEvAAD+3h7wGXnswAAAAABJRU5ErkJggg==",
27
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOUlEQVR42mNgwA7+48BEgyFuwH9cgBhD/hMChAzBq5FsA5DFCYUHNoXYvPCf/mFAlZggKS2MoLwAABZGIe0u7mf5AAAAAElFTkSuQmCC",
28
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR42mNgwA7+48BEgyFuwH9cgBhD/hMChAyhmwH/CRqALRBJMmDAwoDiaByJeQEAXRsh7d6Ez7cAAAAASUVORK5CYII=",
29
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAANElEQVR42mNgwA7+48BEgyFuwH9cgBhD/hMChAyhmwH/CRqAHHhkGYCuiW5hQHE0jsS8AAC0GCTqSlaRPQAAAABJRU5ErkJggg==",
30
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAANklEQVR42mNgwA7+48BEgyFuwH9cgBhD/hMChAyhmwH/CRqAHHhkGYCsgSIvkGoAxdE4EvMCAIAUIe3WmwVMAAAAAElFTkSuQmCC",
31
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOUlEQVR42mNgwA7+48BEgyFuwH9cgBhD/hMChAyhmwH/CRqAHHhkGQBTTLYXcHmHopggKS2MoLwAAFw4Ie372GT7AAAAAElFTkSuQmCC",
32
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR42mNgwA7+48BEgyFuwH9cgBhD/hMChAyhmwH/8RqAHngkGzCgYUBxNI7EvAAAwfwk6qpQGA8AAAAASUVORK5CYII=",
33
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOElEQVR42mNgwA7+48BEgyFuwH9cgBhD/hMChAxBUUiRAbgMIRQe2BSSZwDFYUCTmCApLYygvAAAzn8h7Z3ZHmwAAAAASUVORK5CYII="
34
+ ],
35
+ width: 16,
36
+ height: 16,
37
+ hotspotX: 8,
38
+ hotspotY: 8
39
+ };
40
+ //#endregion
41
+ export { e as CURSOR_ARROW, n as CURSOR_CROSSHAIR, t as CURSOR_I_BEAM, r as CURSOR_WAIT };
@@ -0,0 +1,49 @@
1
+ export interface VfCursorArt {
2
+ /** Frame URL(s), drawn at one image px = one system px; several animate. */
3
+ src: string | string[];
4
+ /** Art width in image px. */
5
+ width: number;
6
+ /** Art height in image px. */
7
+ height: number;
8
+ /** The art pixel that lands on the cell under the pointer (tip, stem, …). */
9
+ hotspotX?: number;
10
+ hotspotY?: number;
11
+ /** Draw with the XOR pen — per-pixel inversion of whatever is beneath. */
12
+ invert?: boolean;
13
+ /**
14
+ * The variant an engine that cannot XOR draws as-is (stable Safari — see
15
+ * `XOR_UNAVAILABLE`): same box and hotspot as `src`, but carrying its own
16
+ * legibility, e.g. a white outline. Only consulted when `invert` is set;
17
+ * without one, such an engine draws `src` itself, un-inverted.
18
+ */
19
+ staticSrc?: string | string[];
20
+ }
21
+ export type VfCursorKind = 'arrow' | 'text' | 'crosshair' | 'wait';
22
+ export interface VfCursorOptions {
23
+ /** The default pointer. Defaults to the embedded System 7 arrow. */
24
+ arrow?: VfCursorArt;
25
+ /** Over editable text. Defaults to the embedded I-beam; `null` disables. */
26
+ text?: VfCursorArt | null;
27
+ /** For `data-vf-cursor="crosshair"` claims. Defaults to the embedded
28
+ * crosshair; `null` disables. */
29
+ crosshair?: VfCursorArt | null;
30
+ /** While `aria-busy`. Defaults to the embedded wristwatch; `null`
31
+ * disables. */
32
+ wait?: VfCursorArt | null;
33
+ /**
34
+ * Element whose top-left corner anchors the system-pixel lattice the
35
+ * cursor snaps to. Defaults to the page's `vf-desktop` (else the document
36
+ * root), so on a faux desktop the cursor's pixels land on the same grid
37
+ * as the raster's dither.
38
+ */
39
+ anchor?: HTMLElement;
40
+ /** ms per `wait` frame (default 250 — a full turn of 8 hands in 2s). */
41
+ waitFrameMs?: number;
42
+ }
43
+ /**
44
+ * Replace the native pointer with the kit's system-pixel-locked cursor art
45
+ * (or the consumer's own, per kind). Strictly opt-in — call it once from
46
+ * your app. Returns a cleanup function that removes the overlay and
47
+ * restores the native pointer.
48
+ */
49
+ export declare function applyCursor(options?: VfCursorOptions): () => void;
package/dist/cursor.js ADDED
@@ -0,0 +1,171 @@
1
+ import { truePixelRatio as e } from "./zoom.js";
2
+ import { effectiveScale as t, onScaleChange as n } from "./scale.js";
3
+ import { prefersReducedMotion as r } from "./motion.js";
4
+ import { VfModalDialog as i } from "./modal-dialog.js";
5
+ import { CURSOR_ARROW as a, CURSOR_CROSSHAIR as o, CURSOR_I_BEAM as s, CURSOR_WAIT as c } from "./cursor-art.js";
6
+ //#region src/cursor.ts
7
+ var l = [
8
+ "arrow",
9
+ "text",
10
+ "crosshair",
11
+ "wait"
12
+ ], u = typeof navigator < "u" && (navigator.vendor ?? "").startsWith("Apple"), d = /* @__PURE__ */ new Set([
13
+ "text",
14
+ "password",
15
+ "search",
16
+ "email",
17
+ "url",
18
+ "tel",
19
+ "number"
20
+ ]);
21
+ function f(e) {
22
+ return e instanceof HTMLTextAreaElement ? !e.disabled : e instanceof HTMLInputElement ? !e.disabled && d.has(e.type) : e instanceof HTMLElement && e.isContentEditable;
23
+ }
24
+ function p(e, t) {
25
+ let n = document.elementFromPoint(e, t);
26
+ for (; n?.shadowRoot;) {
27
+ let r = n.shadowRoot.elementFromPoint(e, t);
28
+ if (!r || r === n) break;
29
+ n = r;
30
+ }
31
+ return n;
32
+ }
33
+ function m(e) {
34
+ let t = [];
35
+ for (; e;) {
36
+ t.push(e);
37
+ let n = e.getRootNode();
38
+ e = e.parentElement ?? (n instanceof ShadowRoot ? n.host : null);
39
+ }
40
+ return t;
41
+ }
42
+ function h() {
43
+ for (let e of document.querySelectorAll("[aria-busy=\"true\"]")) if (e instanceof i && e.open) return !0;
44
+ return !1;
45
+ }
46
+ function g(d = {}) {
47
+ let { anchor: g = document.querySelector("vf-desktop") ?? document.documentElement, waitFrameMs: _ = 250, arrow: v = a, text: y = s, crosshair: b = o, wait: x = c } = d, S = {
48
+ arrow: v,
49
+ text: y,
50
+ crosshair: b,
51
+ wait: x
52
+ }, C = document.documentElement, w = new AbortController(), { signal: T } = w, E = document.createElement("style");
53
+ E.textContent = "* { cursor: none !important }";
54
+ let D = document.createElement("img");
55
+ D.alt = "", D.setAttribute("aria-hidden", "true");
56
+ let O = "showPopover" in D;
57
+ O && D.setAttribute("popover", "manual"), Object.assign(D.style, {
58
+ position: "fixed",
59
+ inset: "0 auto auto 0",
60
+ margin: "0",
61
+ padding: "0",
62
+ border: "0",
63
+ background: "transparent",
64
+ overflow: "visible",
65
+ pointerEvents: "none",
66
+ imageRendering: "pixelated",
67
+ visibility: "hidden",
68
+ zIndex: "2147483647"
69
+ });
70
+ let k = /* @__PURE__ */ new Map();
71
+ for (let e of l) {
72
+ let t = S[e];
73
+ if (!t) continue;
74
+ let n = t.invert && u ? t.staticSrc ?? t.src : t.src, r = Array.isArray(n) ? n : [n], i = r.map((e) => {
75
+ let t = new Image();
76
+ return t.src = e, t;
77
+ });
78
+ k.set(e, {
79
+ art: t,
80
+ frames: r,
81
+ xor: !!t.invert && !u,
82
+ images: i,
83
+ ready: !1
84
+ });
85
+ }
86
+ let A = (e) => Promise.all(e.images.map((e) => e.decode())).then(() => {
87
+ e.ready = !0;
88
+ }), j = 1, M = 3, N = 0, P = 0, F = -1, I = -1, L = "arrow", R = k.get("arrow"), z = 0, B = 0, V = () => {
89
+ if (F < 0) return;
90
+ let { hotspotX: e = 0, hotspotY: t = 0 } = R.art, n = Math.floor((F * j - N) / M), r = Math.floor((I * j - P) / M), i = (N + (n - e) * M) / j, a = (P + (r - t) * M) / j;
91
+ D.style.transform = `translate(${i}px, ${a}px)`;
92
+ }, H = () => {
93
+ j = e() || 1, M = Math.max(1, Math.round(t(g) * j));
94
+ let n = g.getBoundingClientRect();
95
+ N = Math.round(n.left * j), P = Math.round(n.top * j), D.style.width = `${R.art.width * M / j}px`, D.style.height = `${R.art.height * M / j}px`, V();
96
+ }, U = (e) => {
97
+ let t = k.get(e), n = t?.ready ? t : k.get("arrow");
98
+ (e !== L || n !== R) && (L = e, R = n, D.dataset.kind = e, window.clearInterval(B), z = 0, D.src = R.frames[0], D.style.filter = R.xor ? "invert(1)" : "", D.style.mixBlendMode = R.xor ? "difference" : "", R.frames.length > 1 && !r() && (B = window.setInterval(() => {
99
+ z = (z + 1) % R.frames.length, D.src = R.frames[z];
100
+ }, _)), H());
101
+ }, W = () => {
102
+ if (h()) return "wait";
103
+ let e = m(p(F, I));
104
+ if (e.some((e) => e.getAttribute("aria-busy") === "true")) return "wait";
105
+ for (let t of e) {
106
+ let e = t.getAttribute("data-vf-cursor");
107
+ if (e && l.includes(e)) return e;
108
+ }
109
+ return e[0] && f(e[0]) ? "text" : "arrow";
110
+ }, G = () => {
111
+ F < 0 || U(W());
112
+ }, K = 0, q = () => {
113
+ cancelAnimationFrame(K), K = requestAnimationFrame(() => {
114
+ K = requestAnimationFrame(H);
115
+ });
116
+ }, J = () => {
117
+ if (!(!O || !D.isConnected)) try {
118
+ D.hidePopover(), D.showPopover();
119
+ } catch {}
120
+ }, Y = new MutationObserver((e) => {
121
+ for (let t of e) {
122
+ let e = t.target;
123
+ if (t.attributeName === "open" && e instanceof i && e.open) {
124
+ J();
125
+ break;
126
+ }
127
+ }
128
+ G();
129
+ }), X = () => {
130
+ D.style.visibility = "hidden";
131
+ }, Z = (e) => {
132
+ if (e.pointerType === "touch") {
133
+ X();
134
+ return;
135
+ }
136
+ F = e.clientX, I = e.clientY, G(), V(), D.style.visibility = "visible";
137
+ }, Q = !1, $ = () => {
138
+ if (Q) return;
139
+ document.body.append(D), O && D.showPopover(), D.dataset.kind = "arrow", H(), document.head.append(E), C.style.setProperty("--vf-cursor", "none");
140
+ let e = {
141
+ capture: !0,
142
+ passive: !0,
143
+ signal: T
144
+ };
145
+ window.addEventListener("pointermove", Z, e), window.addEventListener("pointerdown", Z, e), window.addEventListener("pointercancel", X, e), document.addEventListener("mouseleave", X, { signal: T }), window.addEventListener("blur", X, { signal: T }), document.addEventListener("visibilitychange", () => {
146
+ document.hidden && X();
147
+ }, { signal: T }), window.addEventListener("resize", q, { signal: T }), Y.observe(document.body, {
148
+ subtree: !0,
149
+ attributes: !0,
150
+ attributeFilter: [
151
+ "open",
152
+ "aria-busy",
153
+ "data-vf-cursor"
154
+ ]
155
+ });
156
+ }, ee = n(q);
157
+ A(k.get("arrow")).then(() => (D.src = R.frames[0], D.decode())).then($, () => {
158
+ console.warn("applyCursor: could not load the arrow art; keeping the native pointer");
159
+ });
160
+ for (let [e, t] of k) e !== "arrow" && A(t).then(() => G(), () => {
161
+ console.warn(`applyCursor: could not load the "${e}" art; it will fall back to the arrow`);
162
+ });
163
+ return () => {
164
+ if (Q = !0, w.abort(), Y.disconnect(), ee(), cancelAnimationFrame(K), window.clearInterval(B), O && D.isConnected) try {
165
+ D.hidePopover();
166
+ } catch {}
167
+ D.remove(), E.remove(), C.style.removeProperty("--vf-cursor");
168
+ };
169
+ }
170
+ //#endregion
171
+ export { g as applyCursor };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Element registration, guarded against a second copy of the library.
3
+ *
4
+ * `customElements.define()` throws `NotSupportedError` if the name is taken,
5
+ * and an uncaught throw during module evaluation takes the whole import graph
6
+ * with it — one duplicated dependency and the consumer's page is blank. That is
7
+ * a plausible thing to happen by accident: a bundle that loads the kit
8
+ * alongside a micro-frontend that also bundles it, an app pinned to two
9
+ * versions through a nested dependency, a `<script type="module">` on a page
10
+ * whose framework already imported the package.
11
+ *
12
+ * So registration checks first and warns instead. The first copy wins — it owns
13
+ * the tag and the elements already upgraded against it — and the page keeps
14
+ * working. The warning matters as much as the guard, because a duplicated copy
15
+ * is not benign even once the page survives it: the scaling, grid-snapping and
16
+ * focus-modality registries are all module-scoped singletons, so the second
17
+ * copy's `applyGridSnap()` reaches none of the first copy's components and the
18
+ * two disagree silently. Better to say so than to let it look fine.
19
+ */
20
+ /**
21
+ * Register `ctor` under `tagName` unless the name is already taken.
22
+ *
23
+ * Re-registering the identical constructor (the same module evaluated twice) is
24
+ * a silent no-op; a *different* constructor warns and leaves the incumbent in
25
+ * place. Use it directly when you define a control without the decorator:
26
+ *
27
+ * ```ts
28
+ * defineElement('my-control', MyControl)
29
+ * ```
30
+ */
31
+ export declare const defineElement: (tagName: string, ctor: CustomElementConstructor) => void;
32
+ /**
33
+ * The kit's replacement for Lit's `@customElement`, registering through
34
+ * {@link defineElement} so a duplicated copy warns rather than throwing.
35
+ * Identical to use:
36
+ *
37
+ * ```ts
38
+ * @vfElement('my-control')
39
+ * export class MyControl extends LitElement {}
40
+ * ```
41
+ *
42
+ * Handles both decorator dialects the way Lit's own does, so it keeps working
43
+ * if the project moves off `experimentalDecorators`.
44
+ */
45
+ export declare const vfElement: (tagName: string) => <T extends CustomElementConstructor>(classOrTarget: T, context?: ClassDecoratorContext<T>) => T;
package/dist/define.js ADDED
@@ -0,0 +1,15 @@
1
+ //#region src/define.ts
2
+ var e = (e, t) => {
3
+ let n = customElements.get(e);
4
+ if (n !== t) {
5
+ if (n !== void 0) {
6
+ console.warn(`[vintage-frames] <${e}> is already registered on this page, so this second registration was skipped and the first one kept. Two copies of the library also mean two of every module-scoped singleton — applyGridSnap(), applyScale() and the focus-modality tracker each only reach the components built from their own copy. Dedupe vintage-frames to a single version.`);
7
+ return;
8
+ }
9
+ customElements.define(e, t);
10
+ }
11
+ }, t = (t) => (n, r) => (r === void 0 ? e(t, n) : r.addInitializer(() => {
12
+ e(t, n);
13
+ }), n);
14
+ //#endregion
15
+ export { e as defineElement, t as vfElement };
@@ -0,0 +1,62 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * One managed listener: target, event type, handler, and capture/options —
4
+ * exactly the arguments the add/remove pair is made with. The handler's event
5
+ * parameter is typed `never` (the bottom type) so a handler narrowed to its
6
+ * real event (`(event: PointerEvent) => void`) is accepted without a cast;
7
+ * the single widening cast lives inside the controller.
8
+ */
9
+ export type DocumentListenerSpec = readonly [
10
+ target: EventTarget,
11
+ type: string,
12
+ handler: (event: never) => void,
13
+ options?: boolean | AddEventListenerOptions
14
+ ];
15
+ /**
16
+ * Document-level listeners scoped to a transient state — an open panel, an
17
+ * in-flight gesture.
18
+ *
19
+ * The kit's transient surfaces (`vf-menu`, `vf-menu-bar`, `vf-select`) listen
20
+ * outside their own shadow root while open: capture-phase `pointerdown` for
21
+ * outside dismissal, `scroll`/`resize` to keep a fixed-position panel honest,
22
+ * document-wide keyboard navigation, a press gesture tracked to its release
23
+ * anywhere on the page. Each of them hand-rolled the same bookkeeping —
24
+ * add/remove calls that must mirror each other exactly, an attached guard
25
+ * making both idempotent, and a disconnect cleanup so a host torn down
26
+ * mid-open can't leak listeners onto the document. This controller owns that
27
+ * bookkeeping; the handlers, and when to {@link attach}, stay the host's.
28
+ *
29
+ * The spec list is a thunk, evaluated at attach time, so a controller field
30
+ * may sit above the handler fields it references without capturing their
31
+ * pre-initialization `undefined`. {@link detach} removes exactly the specs
32
+ * the last {@link attach} added.
33
+ *
34
+ * Listeners are detached on host disconnect and are NOT re-attached on
35
+ * reconnect: the state that warranted them (an open panel, a pressed pointer)
36
+ * is itself torn down by the host on disconnect.
37
+ */
38
+ /**
39
+ * Runs `release` once the in-flight press gesture is over — its trailing
40
+ * `click`, wherever that lands, or a `pointercancel` if no click ever comes.
41
+ *
42
+ * A swallow-click latch armed on `pointerdown` cannot be cleared by the
43
+ * element's own click handler alone: per UI Events, the click after a
44
+ * press-drag-release is dispatched at the nearest common inclusive ancestor of
45
+ * the pointerdown and pointerup targets, so a press that releases elsewhere
46
+ * lands it *above* the pressed element, the clearing handler never runs, and
47
+ * the stuck latch silently swallows the next synthetic (assistive-tech)
48
+ * click. One-shot and bubble-phase, so the element's own click handler — the
49
+ * swallow itself — always runs first.
50
+ */
51
+ export declare function releaseAfterGesture(release: () => void): void;
52
+ export declare class DocumentListenersController implements ReactiveController {
53
+ private readonly listeners;
54
+ /** The specs currently attached; null while detached. */
55
+ private active;
56
+ constructor(host: ReactiveControllerHost, listeners: () => readonly DocumentListenerSpec[]);
57
+ /** Add every listener. A no-op while already attached. */
58
+ attach(): void;
59
+ /** Remove every listener the last {@link attach} added. A no-op while detached. */
60
+ detach(): void;
61
+ hostDisconnected(): void;
62
+ }
@@ -0,0 +1,29 @@
1
+ //#region src/document-listeners.ts
2
+ function e(e) {
3
+ let t = () => {
4
+ document.removeEventListener("click", t), document.removeEventListener("pointercancel", t), e();
5
+ };
6
+ document.addEventListener("click", t), document.addEventListener("pointercancel", t);
7
+ }
8
+ var t = class {
9
+ constructor(e, t) {
10
+ this.listeners = t, this.active = null, e.addController(this);
11
+ }
12
+ attach() {
13
+ if (!this.active) {
14
+ this.active = this.listeners();
15
+ for (let [e, t, n, r] of this.active) e.addEventListener(t, n, r);
16
+ }
17
+ }
18
+ detach() {
19
+ if (this.active) {
20
+ for (let [e, t, n, r] of this.active) e.removeEventListener(t, n, r);
21
+ this.active = null;
22
+ }
23
+ }
24
+ hostDisconnected() {
25
+ this.detach();
26
+ }
27
+ };
28
+ //#endregion
29
+ export { t as DocumentListenersController, e as releaseAfterGesture };
package/dist/drag.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ /**
3
+ * The moving side of a pointer drag. A component supplies these callbacks; the
4
+ * {@link DragController} owns the pointer bookkeeping (which button, capture,
5
+ * delta tracking, system-pixel snapping) so window, dialog and icon dragging
6
+ * stay identical.
7
+ *
8
+ * Every coordinate here is in **system px** — the art's own unit, and the unit
9
+ * a placement is stored in, so that a dropped host is still where it was
10
+ * dropped after a zoom. The controller does the one conversion: pointer events
11
+ * arrive in CSS px, and only the *delta* is converted.
12
+ */
13
+ export interface DragTarget {
14
+ /**
15
+ * A press landed on the drag handle. Return the origin in system px, the
16
+ * space {@link onDrag} writes back in, to begin the drag — or `null` to
17
+ * ignore this press (wrong button, landed on a widget, the host isn't
18
+ * movable). Seed any positioning state here before returning the origin.
19
+ */
20
+ onDragStart(event: PointerEvent): {
21
+ x: number;
22
+ y: number;
23
+ } | null;
24
+ /** Apply a moved origin, in system px, already snapped onto the lattice. */
25
+ onDrag(x: number, y: number): void;
26
+ /** Optional: the drag ended (pointer released or cancelled). */
27
+ onDragEnd?(): void;
28
+ }
29
+ /**
30
+ * Drag-to-move for 1-bit chrome. Bind {@link onPointerDown} / {@link onPointerMove}
31
+ * / {@link onPointerUp} to a handle element (`vf-window`'s title bar,
32
+ * `vf-dialog`'s title bar); the controller captures the pointer on that handle,
33
+ * tracks the delta from the press point, and hands the {@link DragTarget} a new
34
+ * origin in system px, snapped onto the placement lattice — whole art pixels,
35
+ * the way QuickDraw moved windows, which is also what keeps the pixel art
36
+ * inside fringe-free (see {@link snapSys}). The target decides how to apply
37
+ * it; all three write it through `top`/`left` (`src/position.ts`).
38
+ */
39
+ export declare class DragController implements ReactiveController {
40
+ #private;
41
+ private readonly host;
42
+ private readonly target;
43
+ constructor(host: ReactiveControllerHost & Element, target: DragTarget);
44
+ /** Abandon an in-flight drag if the host is torn down mid-gesture. */
45
+ hostDisconnected(): void;
46
+ onPointerDown: (event: PointerEvent) => void;
47
+ onPointerMove: (event: PointerEvent) => void;
48
+ onPointerUp: (event: PointerEvent) => void;
49
+ }
package/dist/drag.js ADDED
@@ -0,0 +1,30 @@
1
+ import { effectiveScale as e, snapSys as t } from "./scale.js";
2
+ //#region src/drag.ts
3
+ var n = class {
4
+ #e;
5
+ #t;
6
+ #n;
7
+ #r;
8
+ #i;
9
+ constructor(n, r) {
10
+ this.host = n, this.target = r, this.#e = null, this.#t = 0, this.#n = 0, this.#r = 0, this.#i = 0, this.onPointerDown = (e) => {
11
+ if (this.#e !== null) return;
12
+ let t = this.target.onDragStart(e);
13
+ t && (this.#e = e.pointerId, this.#t = e.clientX, this.#n = e.clientY, this.#r = t.x, this.#i = t.y, e.currentTarget.setPointerCapture(e.pointerId), e.preventDefault());
14
+ }, this.onPointerMove = (n) => {
15
+ if (n.pointerId !== this.#e) return;
16
+ let r = e(this.host);
17
+ this.target.onDrag(t(this.#r + (n.clientX - this.#t) / r, this.host), t(this.#i + (n.clientY - this.#n) / r, this.host));
18
+ }, this.onPointerUp = (e) => {
19
+ if (e.pointerId !== this.#e) return;
20
+ this.#e = null;
21
+ let t = e.currentTarget;
22
+ t.hasPointerCapture(e.pointerId) && t.releasePointerCapture(e.pointerId), this.target.onDragEnd?.();
23
+ }, n.addController(this);
24
+ }
25
+ hostDisconnected() {
26
+ this.#e = null;
27
+ }
28
+ };
29
+ //#endregion
30
+ export { n as DragController };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Dispatch a `vf-*` CustomEvent following the kit's SPEC §4 convention: every
3
+ * public component event bubbles and crosses shadow roots (`bubbles: true`,
4
+ * `composed: true`) so an ancestor listener in the light DOM can hear it.
5
+ *
6
+ * Centralises the boilerplate that was hand-rolled at 30+ call sites (and in
7
+ * three near-identical private `#emit`/`_emit` wrappers). Returns
8
+ * `dispatchEvent`'s result — `false` when a `cancelable` event was canceled
9
+ * (used by the internal `vf-menu-toggle-request` handshake), otherwise `true`.
10
+ *
11
+ * The *internal* coordination events (the menu handshakes, the list-item
12
+ * disabled notice) pass `composed: false`: parent and child share one light
13
+ * tree, so crossing shadow roots buys them nothing — and leaking a private,
14
+ * cancelable protocol out of a consumer's own shadow boundary hands their
15
+ * delegated listeners an event they can break the kit by cancelling.
16
+ *
17
+ * @param host element to dispatch from (the event's `target`)
18
+ * @param name event type, e.g. `'vf-change'`
19
+ * @param detail optional `event.detail` payload
20
+ * @param options set `cancelable` to allow a listener to `preventDefault()`;
21
+ * set `composed: false` to keep an internal event inside the
22
+ * dispatching tree
23
+ */
24
+ export declare function emit<T = unknown>(host: EventTarget, name: string, detail?: T, options?: {
25
+ cancelable?: boolean;
26
+ composed?: boolean;
27
+ }): boolean;
28
+ /**
29
+ * Dispatch the *native* `input`/`change` event a form control's host owes the
30
+ * platform, alongside the kit's `vf-input`/`vf-change`.
31
+ *
32
+ * A form-associated custom element that contributes to `FormData` but never
33
+ * fires `change` is half a native control: `form.addEventListener('change')`
34
+ * delegation hears nothing, and framework bindings (React's `onChange`, Vue's
35
+ * `v-model`) have nothing to bind to. Each control calls this from its **user
36
+ * interaction** paths only — a programmatic `value` set fires nothing, exactly
37
+ * as a native control fires nothing.
38
+ *
39
+ * The flags mirror the platform's own: both bubble; `input` is composed and
40
+ * `change` is not (UI Events). That composed split is also why the kit's
41
+ * *fields* only bridge `change`: their inner native control's own `input`
42
+ * already crosses the shadow boundary and retargets to the host, so
43
+ * re-dispatching it would double-fire every keystroke.
44
+ */
45
+ export declare function emitNative(host: EventTarget, type: 'input' | 'change'): void;
46
+ /**
47
+ * Run a control's **activation behavior** the way HTML runs a native one's: at
48
+ * the *end* of the click's propagation, and not at all if anything on the path
49
+ * called `preventDefault()`.
50
+ *
51
+ * This is what makes `preventDefault()` on a native checkbox — or on any
52
+ * ancestor — actually stop it from checking. A handler that acts where it sits
53
+ * cannot reproduce it: a listener on the control (or, worse, one registered in
54
+ * the constructor, which is first in the host's own listener list) beats every
55
+ * listener a consumer can write, so only a capture-phase cancel ever landed —
56
+ * and even that needs someone to *read* `defaultPrevented`, which is the half
57
+ * that is easy to forget.
58
+ *
59
+ * The deferral works because each node's listener list is read as that node is
60
+ * reached, so a listener added to the window *during* dispatch still runs when
61
+ * the event gets there — by which point `defaultPrevented` is final. The one
62
+ * ordering HTML has that this doesn't: a window listener registered before this
63
+ * one still runs before the action.
64
+ *
65
+ * `stopPropagation()` cancels nothing in HTML — a native button still submits —
66
+ * but it does stop the event ever reaching the window, so a task picks the
67
+ * action up in that case. A task and **never** a microtask: microtasks
68
+ * interleave *between* the listeners of a trusted dispatch, which would put the
69
+ * action back before the path is done.
70
+ *
71
+ * Callers keep their own guards (disabled, re-entrancy, gesture latches) — this
72
+ * owns the timing alone. `vf-button` runs the same shape inline, with the
73
+ * submit proxy's re-entrancy guard woven through it.
74
+ *
75
+ * @param host the element whose ownerDocument supplies the window to defer on
76
+ * @param event the click being deferred; its `defaultPrevented` is the verdict
77
+ * @param action run once, at the end of the path, unless cancelled
78
+ */
79
+ export declare function deferActivation(host: Element, event: Event, action: () => void): void;
package/dist/events.js ADDED
@@ -0,0 +1,25 @@
1
+ //#region src/events.ts
2
+ function e(e, t, n, r) {
3
+ return e.dispatchEvent(new CustomEvent(t, {
4
+ detail: n,
5
+ bubbles: !0,
6
+ composed: r?.composed ?? !0,
7
+ cancelable: r?.cancelable ?? !1
8
+ }));
9
+ }
10
+ function t(e, t) {
11
+ e.dispatchEvent(new Event(t, {
12
+ bubbles: !0,
13
+ composed: t === "input"
14
+ }));
15
+ }
16
+ function n(e, t, n) {
17
+ let r = e.ownerDocument.defaultView;
18
+ if (!r) return;
19
+ let i = 0, a = () => {
20
+ r.removeEventListener("click", a), r.clearTimeout(i), !t.defaultPrevented && n();
21
+ };
22
+ r.addEventListener("click", a), i = r.setTimeout(a, 0);
23
+ }
24
+ //#endregion
25
+ export { n as deferActivation, e as emit, t as emitNative };