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,507 @@
1
+ import { vfElement as e } from "../define.js";
2
+ import { ScaleController as t, effectiveScale as n, toSysExact as r } from "../scale.js";
3
+ import i from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
+ import { PlacementController as a, VfPositioned as o, warnMovableContract as s } from "../position.js";
5
+ import { vfBodyDecls as c } from "../styles/recipes/body-face.js";
6
+ import { vfBase as l } from "../styles/recipes/host.js";
7
+ import { vfFocusUnderline as u } from "../styles/recipes/focus.js";
8
+ import { GridSnapController as d } from "../grid-snap.js";
9
+ import { DocumentListenersController as f } from "../document-listeners.js";
10
+ import { DragController as p } from "../drag.js";
11
+ import { emit as m } from "../events.js";
12
+ import "../motion.js";
13
+ import { FocusRuleController as h } from "../focus-modality.js";
14
+ import { deriveOpenArt as g } from "../open-art.js";
15
+ import { LitElement as _, css as v, html as y, nothing as b } from "lit";
16
+ import { property as x, state as S } from "lit/decorators.js";
17
+ //#region src/components/vf-icon.ts
18
+ var C = {
19
+ large: 32,
20
+ small: 16
21
+ }, w = 1, T = 8, E = (e, t) => t <= 0 ? Math.max(e, 0) : Math.min(Math.max(e, 0), t), D = class extends o(_) {
22
+ constructor(...e) {
23
+ super(...e), this.label = "", this.size = "large", this.selectable = !1, this.selected = !1, this.open = !1, this.movable = !1, this.editable = !1, this.maxlength = 31, this.scale = new t(this), this.gridSnap = new d(this), this.focusRule = new h(this), this._editing = !1, this._draft = "", this.#e = 0, this._ghost = null, this.#t = null, this.#n = this.attachInternals(), this.#i = "", this.#a = "", this.#o = 0, this.#s = -Infinity, this.#c = new f(this, () => [[
24
+ document,
25
+ "pointerdown",
26
+ this.#A,
27
+ !0
28
+ ]]), this.#l = (e, t, n) => ({
29
+ x: E(e, n.width - r(this.offsetWidth, this)),
30
+ y: E(t, n.height - r(this.offsetHeight, this))
31
+ }), this.#u = new a(this, (e, t, n) => this.#l(e, t, n)), this.#d = {
32
+ x: 0,
33
+ y: 0
34
+ }, this.#f = new p(this, {
35
+ onDragStart: (e) => !this.movable || e.button !== 0 || this._editing ? null : (this.#m(), this.#d = this.#u.seed()),
36
+ onDrag: (e, t) => {
37
+ (e !== this.#d.x || t !== this.#d.y) && this.#S(), this.#u.moveTo(e, t);
38
+ }
39
+ }), this.#p = !1, this.#_ = () => {
40
+ this.#y(), this.#C();
41
+ }, this.#v = () => {
42
+ this.#b();
43
+ }, this.#O = (e) => {
44
+ if (e.button !== 0 || this._editing) return;
45
+ let t = e.composedPath().some((e) => e instanceof HTMLElement && e.classList.contains("label")), n = this.selected, r = e.timeStamp - this.#s < 800;
46
+ this.#s = e.timeStamp, this.#S(), this.selectable && (e.shiftKey || e.metaKey ? this.#D(!this.selected) : this.#D(!0)), this.editable && t && n && !r && !e.shiftKey && !e.metaKey && this.#x(), this.#T && (this.focus({ preventScroll: !0 }), this.focusRule.suppress()), this.#f.onPointerDown(e);
47
+ }, this.#k = () => {
48
+ this._editing || m(this, "vf-open", {});
49
+ }, this.#A = (e) => {
50
+ e.composedPath().includes(this) || (this.#S(), !(e.shiftKey || e.metaKey) && this.#D(!1));
51
+ }, this.#j = (e) => {
52
+ let t = e.target, n = e.data ?? e.dataTransfer?.getData("text") ?? "";
53
+ if (n === "") return;
54
+ let r = t.selectionStart ?? t.value.length, i = t.selectionEnd ?? r, a = t.value.slice(0, r) + n + t.value.slice(i);
55
+ a.length > this.maxlength && this.#N(a);
56
+ }, this.#M = (e) => {
57
+ let t = e.target;
58
+ if (t.value.length > this.maxlength) {
59
+ let e = t.value, n = Math.min(t.selectionStart ?? this.maxlength, this.maxlength);
60
+ t.value = e.slice(0, this.maxlength), t.setSelectionRange(n, n), this.#N(e);
61
+ }
62
+ this._draft = t.value;
63
+ }, this.#P = (e) => {
64
+ e.key === "Enter" ? (e.preventDefault(), this.commitEditing(), this.focus()) : e.key === "Escape" && (e.preventDefault(), e.stopPropagation(), this.cancelEditing(), this.focus());
65
+ }, this.#F = (e) => {
66
+ if (this.#S(), !(this._editing || e.defaultPrevented)) {
67
+ if ((e.metaKey || e.ctrlKey) && !e.altKey && (e.key.toLowerCase() === "o" || e.key === "ArrowDown")) {
68
+ e.preventDefault(), m(this, "vf-open", {});
69
+ return;
70
+ }
71
+ switch (e.key) {
72
+ case "Enter":
73
+ if (!this.editable) return;
74
+ e.preventDefault(), this.startEditing();
75
+ return;
76
+ case " ":
77
+ if (!this.selectable) return;
78
+ e.preventDefault(), this.focusRule.reveal(), this.#D(!this.selected);
79
+ return;
80
+ case "ArrowLeft":
81
+ case "ArrowRight":
82
+ case "ArrowUp":
83
+ case "ArrowDown": {
84
+ if (!this.movable) return;
85
+ e.preventDefault(), this.focusRule.reveal();
86
+ let t = e.shiftKey ? T : w, n = e.key === "ArrowLeft" ? -t : e.key === "ArrowRight" ? t : 0, r = e.key === "ArrowUp" ? -t : e.key === "ArrowDown" ? t : 0;
87
+ this.#m();
88
+ let i = this.#u.seed();
89
+ this.#u.moveTo(i.x + n, i.y + r);
90
+ }
91
+ }
92
+ }
93
+ };
94
+ }
95
+ static {
96
+ this.styles = [l, v`
97
+ :host {
98
+ display: inline-block;
99
+ cursor: var(--vf-cursor, default);
100
+ /* The reserved cell, resolved once so the art box and the no-label
101
+ focus rule share one definition. Not a public token: the two sizes
102
+ are the two icon resources, not a taste. */
103
+ --_cell: 32px;
104
+ }
105
+ :host([size='small']) {
106
+ --_cell: 16px;
107
+ }
108
+ .frame {
109
+ display: flex;
110
+ flex-direction: column;
111
+ align-items: center;
112
+ }
113
+ .art {
114
+ position: relative;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ width: calc(var(--vf-scale, 1) * var(--_cell));
119
+ height: calc(var(--vf-scale, 1) * var(--_cell));
120
+ /* Art larger than its cell is clipped rather than allowed to collide
121
+ with the plate — a wrong-size asset stays a visibly wrong asset. */
122
+ overflow: hidden;
123
+ }
124
+ /* The classic selected icon: the art is ink and opaque white on a
125
+ transparent surround, so inverting flips the two and the surround
126
+ stays out of it (see the class doc). The open ghost keeps exactly
127
+ that shape, so the same rule is the whole of selected-open too. */
128
+ :host([selected]) .art {
129
+ filter: invert(1);
130
+ }
131
+ /* The open ghost replaces the slotted art while there is one — the
132
+ slot stays in the tree (it is where the art loads and re-loads
133
+ from) but paints nothing. Gated on the class, not :host([open]):
134
+ art the pipeline cannot draw keeps rendering as itself rather than
135
+ vanishing behind a state it cannot show. */
136
+ .art.open slot {
137
+ display: none;
138
+ }
139
+ /* The derived raster displays on the terms vf-img gives the art it
140
+ came from: natural size in system px (the inline style it carries),
141
+ nearest-neighbor on whole device pixels. */
142
+ .ghost {
143
+ display: block;
144
+ image-rendering: pixelated;
145
+ }
146
+ /* Sized by the plate, and deliberately NOT clamped to the frame: a name
147
+ wider than the cell has to straddle the centre line, not start at the
148
+ cell's left edge and run off to the right. Clamping it left the art
149
+ and the name on two different axes — and since the plate is even and
150
+ the cell is even, the negative offset a wider caption gets from the
151
+ centring below is still a whole number of system px. */
152
+ .caption {
153
+ position: relative;
154
+ margin-top: calc(var(--vf-scale, 1) * var(--vf-icon-gap, 2px));
155
+ }
156
+ .label {
157
+ ${c}
158
+ font-weight: var(--vf-font-weight, 700);
159
+ /* Whole system px, never a ratio — layout contract rule 2. 12, not
160
+ the face's 16px em: the Finder's plate is 12 system px tall, and
161
+ the em centered in it lands the ink exactly where the Finder put
162
+ it — 3px above the ascenders, descenders on the bottom edge. The
163
+ half-leading is (12 − 16) / 2, so an override must keep the box's
164
+ parity even for the baseline to stay on a whole pixel. */
165
+ line-height: calc(
166
+ var(--vf-scale, 1) * var(--vf-icon-label-height, 12px)
167
+ );
168
+ /* One line, always. A name is never abbreviated and never wrapped: it
169
+ is at most 31 characters, and it overflows its cell rather than
170
+ folding — which is also what keeps its x whole (see the class doc). */
171
+ white-space: nowrap;
172
+ text-align: center;
173
+ }
174
+ /* Inline, not block, so the plate hugs each line separately — a wrapped
175
+ name reads as stacked bars of different widths, which is how System 7
176
+ drew it. The clone value is what repeats the 1px side padding onto
177
+ every fragment instead of only the first and last. */
178
+ /* The plate: an inline-block sized to a whole EVEN number of system px
179
+ (see #measurePlate). That parity is the whole alignment argument —
180
+ with it, both things the frame centers land on a whole pixel by
181
+ construction rather than by correction. */
182
+ .name {
183
+ display: inline-block;
184
+ /* The plate is opaque white in its own right, not the surrounding
185
+ surface: on the desktop dither the name reads because it sits on a
186
+ plate, and --vf-surface is unset out there. */
187
+ background: var(--vf-white, #fff);
188
+ color: var(--vf-black, #000);
189
+ /* Whole system px, and NOT centered inside the plate: the text starts
190
+ at the padding edge, so its own x is the plate's x plus one whole
191
+ pixel. Centering here would re-introduce the half pixel the even
192
+ width just removed, one level down. Any slack from rounding up to
193
+ even lands after the text, where it is a hairline of extra plate. */
194
+ padding: 0 calc(var(--vf-scale, 1) * 1px);
195
+ text-align: left;
196
+ }
197
+ :host([selected]) .name {
198
+ /* Forced colors: exempt the inverted plate from the mode's text
199
+ backplate, which would land a Canvas slab on the highlight bar —
200
+ see vf-list-item's forced-colors note. The pair is already the
201
+ user's own via the vfBase token remap. */
202
+ @media (forced-colors: active) {
203
+ forced-color-adjust: none;
204
+ }
205
+ background: var(--vf-highlight, #000);
206
+ color: var(--vf-highlight-text, #fff);
207
+ }
208
+ /* Editing: the plate keeps rendering the draft — still measured, still
209
+ even — so the box tracks the typed width the way the Finder's rename
210
+ box grew, while handing its pixels to the field overlaying it.
211
+ white-space: pre holds the spaces a name may legitimately end in,
212
+ which the collapsing default would drop from the measurement. */
213
+ .caption.editing .label {
214
+ visibility: hidden;
215
+ white-space: pre;
216
+ }
217
+ /* The rename box: a 1px border with a 1px white well inside it, boxing
218
+ the highlighted name rather than letting it touch the rule, the way
219
+ the classic rename box did. The border and well are a wrapper rather
220
+ than the input's own border and padding, because the selection behind
221
+ a highlighted name paints at the face's full 16px em and is clipped
222
+ only at the input's own edge — on one element the em-tall selection
223
+ floods the padding and lands on the border. Splitting them sizes the
224
+ input to the plate's line box, which clips the selection to the same
225
+ 12px the plate stands, and keeps the well white outside it.
226
+ Out by the border sideways, and by border-plus-well top and bottom:
227
+ border + well comes to 1px in from the caption horizontally against
228
+ the plate's 1px padding, and 2px in vertically against the 2px it
229
+ was pushed out by, so the run sits exactly where the plate put it
230
+ and nothing shifts when the field opens. */
231
+ .rename-box {
232
+ position: absolute;
233
+ inset: calc(var(--vf-scale, 1) * -2px) calc(var(--vf-scale, 1) * -1px);
234
+ background: var(--vf-white, #fff);
235
+ border: calc(var(--vf-scale, 1) * 1px) solid var(--vf-black, #000);
236
+ padding: calc(var(--vf-scale, 1) * 1px);
237
+ }
238
+ .rename {
239
+ display: block;
240
+ width: 100%;
241
+ /* The plate's own line box, stated as height too: the input clips its
242
+ selection paint at this edge, which is the whole reason it has no
243
+ border of its own (see .rename-box). */
244
+ height: calc(var(--vf-scale, 1) * var(--vf-icon-label-height, 12px));
245
+ ${c}
246
+ font-weight: var(--vf-font-weight, 700);
247
+ line-height: calc(
248
+ var(--vf-scale, 1) * var(--vf-icon-label-height, 12px)
249
+ );
250
+ /* Hand-rolled rather than composed from vfField: that recipe sets the
251
+ Chicago display face, and pulling it in would ship the whole chrome
252
+ WOFF2 with an element that otherwise sets only body type (see the
253
+ note at the top of styles/base.ts). The Finder renamed in the same
254
+ face it labelled in, so this is the faithful reading too. */
255
+ background: none;
256
+ color: var(--vf-black, #000);
257
+ border: none;
258
+ border-radius: 0;
259
+ padding: 0;
260
+ margin: 0;
261
+ /* Left, matching the plate it overlays: same text x, so the name does
262
+ not shift by half a pixel the moment you start typing — and the
263
+ field's own run stays on the grid for the same reason the plate's
264
+ does. */
265
+ text-align: left;
266
+ user-select: text;
267
+ -webkit-user-select: text;
268
+ outline: none;
269
+ -webkit-appearance: none;
270
+ appearance: none;
271
+ }
272
+ .rename::selection {
273
+ background-color: var(--vf-highlight, #000);
274
+ color: var(--vf-highlight-text, #fff);
275
+ }
276
+ /* Keyboard focus is the kit's dashed rule below the plate, not a ring
277
+ around the icon (see vfFocusUnderline). Below rather than inside it:
278
+ every pixel of the plate is either the name or the fill inverting
279
+ behind it, so there is no interior to lend — the vf-select placement,
280
+ for the same reason. -2px puts it in the second row under a box whose
281
+ ink runs to its own edge. Drawn in currentColor, so it turns white
282
+ along with the name on a selected plate.
283
+ Gated on a class, not :focus-visible: the host takes focus from its own
284
+ pointerdown so a press-drag can own the pointer, and Blink reads that
285
+ as a visible focus (see focus-modality.ts). */
286
+ .caption.focus-rule .label::after {
287
+ --vf-focus-underline-offset: -2px;
288
+ ${u}
289
+ }
290
+ /* No name, no plate — the rule falls back to the art cell. */
291
+ .art.focus-rule::after {
292
+ --vf-focus-underline-offset: -2px;
293
+ ${u}
294
+ }
295
+ :host(:focus) {
296
+ outline: none;
297
+ }
298
+ `];
299
+ }
300
+ #e;
301
+ #t;
302
+ #n;
303
+ get #r() {
304
+ return this.closest("[role=\"listbox\"]") !== null;
305
+ }
306
+ #i;
307
+ #a;
308
+ #o;
309
+ #s;
310
+ #c;
311
+ #l;
312
+ #u;
313
+ #d;
314
+ #f;
315
+ connectedCallback() {
316
+ super.connectedCallback(), this.#E(), this.#C(), this.addEventListener("keydown", this.#F);
317
+ }
318
+ disconnectedCallback() {
319
+ super.disconnectedCallback(), this.removeEventListener("keydown", this.#F), this.#S();
320
+ }
321
+ updated(e) {
322
+ if (e.has("selectable") && this.#E(), (e.has("selectable") || e.has("selected") || e.has("label")) && this.#C(), (e.has("selected") || e.has("selectable")) && (this.selectable && this.selected ? this.#c.attach() : this.#c.detach()), e.has("_editing") && this._editing) {
323
+ let e = this.renderRoot.querySelector("input");
324
+ e?.focus(), e?.select();
325
+ }
326
+ (e.has("label") || e.has("_draft") || e.has("_editing")) && this.#h(), (e.has("open") || e.has("size")) && this.#y();
327
+ }
328
+ #p;
329
+ #m() {
330
+ this.#p ||= s(this, `vf-icon${this.label ? ` ("${this.label}")` : ""}`, "<vf-icon movable label=\"Read Me\" width=\"64\" top=\"16\" left=\"16\">");
331
+ }
332
+ firstUpdated() {
333
+ document.fonts.ready.then(() => this.#h());
334
+ }
335
+ #h() {
336
+ let e = this.renderRoot?.querySelector(".name");
337
+ if (!e) {
338
+ this.#e = 0;
339
+ return;
340
+ }
341
+ let t = 0;
342
+ if (e.firstChild) {
343
+ let n = document.createRange();
344
+ n.selectNodeContents(e), t = n.getBoundingClientRect().width, n.detach();
345
+ }
346
+ let r = t / n(this) + 2, i = Math.max(2, Math.ceil(r / 2) * 2);
347
+ this.#e = this._editing && t === 0 ? Math.max(i, C[this.size]) : i, e.style.width = this.#g;
348
+ }
349
+ get #g() {
350
+ return this.#e ? `calc(var(--vf-scale, 1) * ${this.#e}px)` : "";
351
+ }
352
+ #_;
353
+ #v;
354
+ #y() {
355
+ let e = this.renderRoot?.querySelector(`slot[name='${this.size}']`), t = null;
356
+ for (let n of e?.assignedElements({ flatten: !0 }) ?? []) {
357
+ let e = n instanceof HTMLImageElement || n instanceof HTMLCanvasElement ? n : n.querySelector("img, canvas");
358
+ if (e instanceof HTMLImageElement || e instanceof HTMLCanvasElement) {
359
+ t = e;
360
+ break;
361
+ }
362
+ }
363
+ t !== this.#t && (this.#t?.removeEventListener("load", this.#v), this.#t?.removeEventListener("error", this.#v), this.#t = t, t?.addEventListener("load", this.#v), t?.addEventListener("error", this.#v)), this.#b();
364
+ }
365
+ #b() {
366
+ let e = this.#t, t = e instanceof HTMLImageElement ? e.complete && e.naturalWidth > 0 : e != null && e.width > 0;
367
+ if (!this.open || e == null || !t) {
368
+ this._ghost = null, this.#i = "";
369
+ return;
370
+ }
371
+ let n = e instanceof HTMLImageElement ? `${this.size} ${e.currentSrc}` : "";
372
+ if (n !== "" && n === this.#i && this._ghost) return;
373
+ let r = g(e);
374
+ if (r) {
375
+ r.className = "ghost", r.style.width = `calc(var(--vf-scale, 1) * ${r.width}px)`, r.style.height = `calc(var(--vf-scale, 1) * ${r.height}px)`;
376
+ let t = e instanceof HTMLImageElement ? e.alt : "";
377
+ t && (r.setAttribute("role", "img"), r.setAttribute("aria-label", t));
378
+ }
379
+ this._ghost = r, this.#i = r ? n : "";
380
+ }
381
+ #x() {
382
+ this.#S(), this.#o = window.setTimeout(() => {
383
+ this.#o = 0, this.startEditing();
384
+ }, 800);
385
+ }
386
+ #S() {
387
+ this.#o &&= (clearTimeout(this.#o), 0);
388
+ }
389
+ startEditing() {
390
+ this.#S(), !(!this.editable || this._editing) && (this.#a = this.label, this._draft = this.label, this._editing = !0);
391
+ }
392
+ commitEditing() {
393
+ if (!this._editing) return;
394
+ this._editing = !1;
395
+ let e = this.#a, t = this._draft;
396
+ if (t.trim() === "") {
397
+ this._draft = e, t !== e && m(this, "vf-name-rejected", {
398
+ attempted: t,
399
+ kept: e,
400
+ reason: "empty"
401
+ });
402
+ return;
403
+ }
404
+ t !== e && (this.label = t, m(this, "vf-change", {
405
+ label: t,
406
+ previous: e
407
+ }));
408
+ }
409
+ cancelEditing() {
410
+ this._editing && (this._editing = !1, this._draft = this.#a);
411
+ }
412
+ render() {
413
+ let e = this._editing, t = e ? this._draft : this.label, n = this.focusRule.marked && !e, r = t !== "" || e, i = `caption${e ? " editing" : ""}${n && t ? " focus-rule" : ""}`, a = `art${this.open && this._ghost ? " open" : ""}${n && !t ? " focus-rule" : ""}`, o = this.width ? `width: calc(var(--vf-scale, 1) * ${this.width}px)` : "";
414
+ return y`<div
415
+ class="frame vf-snap"
416
+ part="frame"
417
+ style=${o}
418
+ @pointerdown=${this.#O}
419
+ @pointermove=${this.#f.onPointerMove}
420
+ @pointerup=${this.#f.onPointerUp}
421
+ @pointercancel=${this.#f.onPointerUp}
422
+ @dblclick=${this.#k}
423
+ >
424
+ <div class=${a} part="icon">
425
+ ${this.size === "small" ? y`<slot name="small" @slotchange=${this.#_}></slot>` : y`<slot name="large" @slotchange=${this.#_}></slot>`}${this._ghost ?? b}
426
+ </div>
427
+ ${r ? y`<div class=${i}>
428
+ <!-- No whitespace around the span: it is the measured run, and a
429
+ stray space would widen it. -->
430
+ <div class="label" part="label"><span
431
+ class="name"
432
+ part="plate"
433
+ style=${this.#e ? `width: ${this.#g}` : ""}
434
+ >${t}</span
435
+ ></div>
436
+ ${e ? y`<div class="rename-box">
437
+ <input
438
+ class="rename"
439
+ part="input"
440
+ type="text"
441
+ maxlength=${this.maxlength}
442
+ .value=${this._draft}
443
+ @beforeinput=${this.#j}
444
+ @input=${this.#M}
445
+ @keydown=${this.#P}
446
+ @blur=${this.commitEditing}
447
+ />
448
+ </div>` : b}
449
+ </div>` : b}
450
+ </div>`;
451
+ }
452
+ #C() {
453
+ if (!this.selectable) {
454
+ this.#n.role = null, this.#n.ariaSelected = null, this.#n.ariaLabel = null;
455
+ return;
456
+ }
457
+ if (this.#r) {
458
+ this.#n.role = "option", this.#n.ariaSelected = this.selected ? "true" : "false", this.#n.ariaLabel = null;
459
+ return;
460
+ }
461
+ this.#n.role = "img", this.#n.ariaSelected = null, this.#n.ariaLabel = this.label || this.#w || null;
462
+ }
463
+ get #w() {
464
+ return this.#t instanceof HTMLImageElement ? this.#t.alt : "";
465
+ }
466
+ get #T() {
467
+ return this.selectable;
468
+ }
469
+ #E() {
470
+ this.hasAttribute("tabindex") || (this.#T ? this.tabIndex = 0 : this.removeAttribute("tabindex"));
471
+ }
472
+ #D(e) {
473
+ e !== this.selected && (this.selected = e, m(this, "vf-select", { selected: e }));
474
+ }
475
+ #O;
476
+ #k;
477
+ #A;
478
+ #j;
479
+ #M;
480
+ #N(e) {
481
+ m(this, "vf-name-too-long", {
482
+ attempted: e,
483
+ accepted: e.slice(0, this.maxlength),
484
+ limit: this.maxlength
485
+ });
486
+ }
487
+ #P;
488
+ #F;
489
+ };
490
+ i([x()], D.prototype, "label", void 0), i([x({ reflect: !0 })], D.prototype, "size", void 0), i([x({
491
+ type: Boolean,
492
+ reflect: !0
493
+ })], D.prototype, "selectable", void 0), i([x({
494
+ type: Boolean,
495
+ reflect: !0
496
+ })], D.prototype, "selected", void 0), i([x({
497
+ type: Boolean,
498
+ reflect: !0
499
+ })], D.prototype, "open", void 0), i([x({
500
+ type: Boolean,
501
+ reflect: !0
502
+ })], D.prototype, "movable", void 0), i([x({
503
+ type: Boolean,
504
+ reflect: !0
505
+ })], D.prototype, "editable", void 0), i([x({ type: Number })], D.prototype, "width", void 0), i([x({ type: Number })], D.prototype, "maxlength", void 0), i([S()], D.prototype, "_editing", void 0), i([S()], D.prototype, "_draft", void 0), i([S()], D.prototype, "_ghost", void 0), D = i([e("vf-icon")], D);
506
+ //#endregion
507
+ export { D as VfIcon };
@@ -0,0 +1,63 @@
1
+ import { LitElement } from 'lit';
2
+ declare const VfImg_base: (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof LitElement;
3
+ /**
4
+ * `<vf-img>` — a raster image on the kit's pixel grid.
5
+ *
6
+ * The reference art is full of small pixel graphics that are *pictures*, not
7
+ * glyphs: the Apple menu's logo, desk-accessory and document icons, the
8
+ * about-box machine portrait. This component displays one — a slotted native
9
+ * `<img>` — at the kit's own magnification, treating **one image pixel as one
10
+ * system pixel**:
11
+ *
12
+ * - the image is sized to `naturalWidth × naturalHeight` system px, multiplied
13
+ * by `--vf-scale` in `calc()` like every other metric, so a source pixel
14
+ * covers exactly `scale × dpr` device pixels (a whole number, per layout
15
+ * contract rule 1);
16
+ * - `image-rendering: pixelated` keeps the resampling nearest-neighbor, and on
17
+ * a whole-device-pixel box that is bit-exact magnification — no smoothing,
18
+ * no half-covered edge rows;
19
+ * - its own {@link GridSnapController}, so the image's origin holds the device
20
+ * grid wherever the page puts it (a fractional origin would slide the whole
21
+ * box across device pixels and shear every source pixel's coverage).
22
+ *
23
+ * The graphic stays a real `<img>` in the consumer's light DOM — it keeps
24
+ * native loading, `alt` text (use `alt=""` for a decorative icon) and the
25
+ * consumer's own asset URLs; the kit ships no raster files. `width`/`height`
26
+ * (in system px, whole numbers) state the box up front so the page doesn't
27
+ * shift when the file lands — until a size is known the box is 0×0, never a
28
+ * flash of the image at some other scale. They also override the natural size
29
+ * deliberately: a whole multiple (`width="32"` on 16-px art) magnifies on the
30
+ * same grid.
31
+ *
32
+ * @slot - The image: a single `<img>` element.
33
+ * @csspart frame - The sized box the image fills.
34
+ */
35
+ export declare class VfImg extends VfImg_base {
36
+ #private;
37
+ static styles: import("lit").CSSResult[];
38
+ /** Default-on display scaling (true 72dpi size); see src/scale.ts. */
39
+ private readonly scale;
40
+ /** Device-pixel grid snapping (opt in with applyGridSnap()); see src/grid-snap.ts. */
41
+ private readonly gridSnap;
42
+ /**
43
+ * The displayed width in system px — a whole number. Defaults to the slotted
44
+ * image's `naturalWidth` (one image px = one system px); state it explicitly
45
+ * to reserve the box before the file loads, or a whole multiple to magnify.
46
+ */
47
+ width?: number;
48
+ /** The displayed height in system px; see {@link width}. */
49
+ height?: number;
50
+ /** The slotted image's intrinsic size, 0 until it has one. */
51
+ private _naturalWidth;
52
+ private _naturalHeight;
53
+ /** True when the slotted image has settled with no natural size (a failed
54
+ * load); see {@link #measure}. */
55
+ private _failed;
56
+ protected render(): import("lit-html").TemplateResult<1>;
57
+ }
58
+ declare global {
59
+ interface HTMLElementTagNameMap {
60
+ 'vf-img': VfImg;
61
+ }
62
+ }
63
+ export {};
@@ -0,0 +1,65 @@
1
+ import { vfElement as e } from "../define.js";
2
+ import { ScaleController as t } from "../scale.js";
3
+ import n from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
+ import { VfPositioned as r } from "../position.js";
5
+ import { vfBase as i } from "../styles/recipes/host.js";
6
+ import { GridSnapController as a } from "../grid-snap.js";
7
+ import { LitElement as o, css as s, html as c, nothing as l } from "lit";
8
+ import { property as u, state as d } from "lit/decorators.js";
9
+ //#region src/components/vf-img.ts
10
+ var f = class extends r(o) {
11
+ constructor(...e) {
12
+ super(...e), this.scale = new t(this), this.gridSnap = new a(this), this._naturalWidth = 0, this._naturalHeight = 0, this._failed = !1, this.#e = null, this.#t = (e) => {
13
+ let t = e.target.assignedElements({ flatten: !0 }).find((e) => e instanceof HTMLImageElement) ?? null;
14
+ t !== this.#e && (this.#e?.removeEventListener("load", this.#n), this.#e?.removeEventListener("error", this.#n), this.#e = t, t?.addEventListener("load", this.#n), t?.addEventListener("error", this.#n)), this.#r();
15
+ }, this.#n = () => {
16
+ this.#r();
17
+ };
18
+ }
19
+ static {
20
+ this.styles = [i, s`
21
+ :host {
22
+ display: inline-block;
23
+ }
24
+ .frame {
25
+ display: block;
26
+ }
27
+ ::slotted(img) {
28
+ display: block;
29
+ width: 100%;
30
+ height: 100%;
31
+ /* Nearest-neighbor: the whole point. The box is whole device px, so
32
+ magnification is bit-exact and no edge row is half-covered. */
33
+ image-rendering: pixelated;
34
+ /* Chrome, not content: System 7 icons don't drag out of the UI. */
35
+ -webkit-user-drag: none;
36
+ }
37
+ /* A failed, undeclared image (see render()): released from the stretch
38
+ so the browser renders it as a native broken <img> — alt text at its
39
+ own size — instead of scaling it into a 0×0 frame. */
40
+ .frame.failed ::slotted(img) {
41
+ width: auto;
42
+ height: auto;
43
+ }
44
+ `];
45
+ }
46
+ #e;
47
+ render() {
48
+ let e = this.width ?? this._naturalWidth, t = this.height ?? this._naturalHeight, n = this._failed && this.width == null && this.height == null;
49
+ return c`<span
50
+ class="frame vf-snap ${n ? "failed" : ""}"
51
+ part="frame"
52
+ style=${n ? l : `width: calc(var(--vf-scale, 1) * ${e}px); height: calc(var(--vf-scale, 1) * ${t}px)`}
53
+ ><slot @slotchange=${this.#t}></slot
54
+ ></span>`;
55
+ }
56
+ #t;
57
+ #n;
58
+ #r() {
59
+ let e = this.#e;
60
+ this._naturalWidth = e?.naturalWidth ?? 0, this._naturalHeight = e?.naturalHeight ?? 0, this._failed = e != null && e.complete && e.naturalWidth === 0 && e.src !== "";
61
+ }
62
+ };
63
+ n([u({ type: Number })], f.prototype, "width", void 0), n([u({ type: Number })], f.prototype, "height", void 0), n([d()], f.prototype, "_naturalWidth", void 0), n([d()], f.prototype, "_naturalHeight", void 0), n([d()], f.prototype, "_failed", void 0), f = n([e("vf-img")], f);
64
+ //#endregion
65
+ export { f as VfImg };