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,259 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ import { CLASSIC_DPI, CSS_REFERENCE_DPI, SYSTEM_PX_IN_CSS_PX } from './zoom.js';
3
+ /**
4
+ * Display scaling — replicate the classic 72 dpi "system pixel" on modern
5
+ * screens.
6
+ *
7
+ * Vintage Frames' components are authored in *system pixels* (the 1-bit art
8
+ * grid: a 1px border, a 13px checkbox, a 22px control). A system pixel is
9
+ * 1/{@link CLASSIC_DPI} inch — the Macintosh screen the art was drawn for — and
10
+ * the job here is to put that size on a modern display without ever landing on
11
+ * a fractional device pixel.
12
+ *
13
+ * So the device-pixel count is *computed from the display*
14
+ * ({@link devicePxPerSystemPx}): `round(96/72 × trueDpr)`, the whole count
15
+ * nearest 1/72 inch, where 96 is {@link CSS_REFERENCE_DPI} and `trueDpr` is
16
+ * {@link truePixelRatio} — device px per CSS px *including* zoom, which
17
+ * `window.devicePixelRatio` reports in Chrome/Firefox but not Safari. The scale
18
+ * is that count divided back into CSS px:
19
+ *
20
+ * 1× display → 1 device px → scale 1.0 (1 CSS px × 1 dpr)
21
+ * 1.5× (Win) → 2 device px → scale 1.333 (1.333 CSS px × 1.5 dpr)
22
+ * 2× retina → 3 device px → scale 1.5 (1.5 CSS px × 2 dpr)
23
+ * 3× display → 4 device px → scale 1.333 (1.333 CSS px × 3 dpr)
24
+ *
25
+ * Zoom needs no term of its own: it multiplies device px per CSS px, so it is
26
+ * already inside `trueDpr`, and a zoomed page walks the same ladder (a 2×
27
+ * display at 200% is trueDpr 4 → 5 device px). The kit therefore grows with the
28
+ * page instead of dividing the zoom back out and holding its physical size
29
+ * while the copy around it doubles. The invariant is `--vf-scale × trueDpr = a
30
+ * whole device-px count`; because the count is always whole, the art is crisp
31
+ * at every density and every zoom level.
32
+ *
33
+ * Components multiply their metrics by the inherited `--vf-scale` custom
34
+ * property in `calc()`; JS geometry uses {@link sys} / {@link toSys} to
35
+ * convert between system and CSS px. The scale is a plain multiplier, so
36
+ * nesting never compounds: a window and a button inside it each scale their
37
+ * own metrics once.
38
+ *
39
+ * Nothing here runs automatically — a component with no `--vf-scale` in scope
40
+ * renders at 1× (today's behavior). Opt in with {@link applyScale} or by setting
41
+ * `--vf-scale` yourself.
42
+ */
43
+ export { CLASSIC_DPI, CSS_REFERENCE_DPI, SYSTEM_PX_IN_CSS_PX };
44
+ /**
45
+ * The CSS scale factor for the current display and zoom:
46
+ * `devicePxPerSystemPx(trueDpr) / trueDpr`. The numerator is whole, so the
47
+ * product with `trueDpr` is whole — the scale contract — and the value is
48
+ * `SYSTEM_PX_IN_CSS_PX` (1.333) to within the rounding.
49
+ */
50
+ export declare function getScale(): number;
51
+ /**
52
+ * The effective `--vf-scale` in force at `el` — the resolved custom property
53
+ * CSS multiplies every metric by. Reading the computed value keeps JS geometry
54
+ * in the SAME coordinate system as CSS: a consumer/ancestor override
55
+ * (`:root{--vf-scale:1}`, `.dense{--vf-scale:1.25}`) wins for both, so
56
+ * JS-written positions (drag origins, slider fill, panel placement, resize
57
+ * floors) never drift off the device grid the way a hardcoded `3/dpr` would.
58
+ * Falls back to the display scale when no property is in scope (before connect /
59
+ * SSR), matching {@link ScaleController}'s own default.
60
+ */
61
+ export declare function effectiveScale(el: Element): number;
62
+ /** Convert system (art) units to display (CSS) px, honoring `--vf-scale` at `el`. */
63
+ export declare function sys(value: number, el: Element): number;
64
+ /**
65
+ * A length of `size` system px as a CSS length that stays live: the scale is
66
+ * read at paint time, so the box follows the display the way every metric
67
+ * declared in a component's stylesheet does. `sys()` resolves the number *now*
68
+ * and freezes it; this is what a size written onto an element belongs in.
69
+ *
70
+ * `undefined` gives back the empty string — the inline declaration is removed
71
+ * and the box goes back to whatever layout gives it. So does `null`, which is
72
+ * what Lit's Number converter hands back when the attribute is *removed* rather
73
+ * than never set; `0` is a real length and still emits one.
74
+ */
75
+ export declare function sysLength(size: number | null | undefined): string;
76
+ /**
77
+ * A CSS shorthand of one to four system-px lengths — `padding`, `margin`,
78
+ * `inset` — each one live against `--vf-scale` the way {@link sysLength}'s
79
+ * single value is. `12` gives one length, `'10 12'` two, and so on in the usual
80
+ * top/right/bottom/left order.
81
+ *
82
+ * Whole system px only: a fractional entry is truncated rather than passed
83
+ * through, because an 8.5-system-px padding is precisely the off-grid metric
84
+ * the layout contract exists to prevent (docs/SIZING.md rule 2). A value that can't be
85
+ * read as one to four whole numbers gives back the empty string — the
86
+ * declaration is removed rather than half-applied.
87
+ */
88
+ export declare function sysLengths(value: number | string | null | undefined): string;
89
+ /** Convert display (CSS) px to whole system (art) units, honoring `--vf-scale` at `el`. */
90
+ export declare function toSys(value: number, el: Element): number;
91
+ /**
92
+ * Convert display (CSS) px to system units *without* rounding — the seed
93
+ * conversion for geometry that is about to be snapped onto the placement
94
+ * lattice anyway ({@link snapSys}), where rounding twice only loses precision.
95
+ */
96
+ export declare function toSysExact(value: number, el: Element): number;
97
+ /**
98
+ * Snap a CSS-px coordinate onto the device-pixel grid.
99
+ *
100
+ * The 1-bit art is only crisp when its container's origin sits on a whole
101
+ * device pixel: at a fractional origin every edge inside — clip-path
102
+ * staircases, 1px borders, stripes, dithers, bitmap glyphs — rasterizes with
103
+ * fractional coverage and grows a gray antialiasing fringe. Positions
104
+ * declared in CSS as `calc(var(--vf-scale) * Npx)` land on the grid by
105
+ * construction (N system px = 3N device px), but positions written from JS
106
+ * (pointer drags report fractional clientX/Y on trackpads, computed styles
107
+ * resolve percentages fractionally) must round through this before being
108
+ * applied.
109
+ *
110
+ * At an integral density the snap is to whole CSS px — still whole device px
111
+ * (1 CSS px = dpr device px). Historically that was also what kept WebKit's
112
+ * native scrollbar rects (which it pinned to whole CSS px) on the frame; the
113
+ * kit now draws its own scroll rails, so the whole-CSS-px lift survives on
114
+ * its remaining merits: drag granularity of one CSS px instead of one device
115
+ * px is imperceptible, engines still quantize sub-CSS-px paint per box (the
116
+ * border-floor wobble), and the dpr-2 baseline nudge always
117
+ * takes its known whole-CSS-px rendering rather than sometimes its exact
118
+ * half-px one. Fractional densities keep the finest crisp grid: whole device
119
+ * px.
120
+ */
121
+ export declare function snapToDevicePx(value: number): number;
122
+ /**
123
+ * Snap a CSS-px coordinate or length onto the system-pixel grid — the art's
124
+ * own unit — as resolved by `--vf-scale` at `el`.
125
+ *
126
+ * Window chrome geometry (title-bar drags, grow-box sizes, dialog pins) is
127
+ * held to whole system pixels rather than merely whole device pixels: a
128
+ * window N system px wide keeps every interior metric — the flexing body, an
129
+ * edge-mounted scroll rail — a whole count of system px too, and drags step
130
+ * whole art pixels, the way QuickDraw moved windows. Every step is a whole
131
+ * count of device px by the scale contract (`scale × dpr` integral), so the
132
+ * 1-bit art stays fringe-free.
133
+ *
134
+ * The step is the smallest run of system px that is also whole in CSS px —
135
+ * one system px at dpr 1 and 3, two at dpr 2's 1.5 scale, where an odd count
136
+ * lands an edge on a half CSS px. The lift earns its keep at dpr 2: engines
137
+ * still quantize sub-CSS-px paint per box (floored borders distribute their
138
+ * half-CSS slack by snap direction — the border-floor wobble), so whole-CSS
139
+ * edges are the positions every line renders the same at. It was once also
140
+ * load-bearing for the native scrollbars WebKit pinned to whole CSS px; the
141
+ * kit draws its own rails now, and the lattice keeps its gesture and
142
+ * art-crispness roles. A scale whole in no small run (a fractional density)
143
+ * falls back to single system px: whole device px, the finest crisp grid
144
+ * there.
145
+ */
146
+ export declare function snapToSystemPx(value: number, el: Element): number;
147
+ /**
148
+ * The placement lattice **in system px**: the smallest run of k ≤ 4 system px
149
+ * that is also whole in CSS px at `el`'s scale (see {@link snapToSystemPx} for
150
+ * why dpr 2 takes two). Identical to the historical behavior at 100% zoom
151
+ * (k = 1 for scales 3 and 1, k = 2 for 1.5); under zoom the scale can be a
152
+ * ratio like 5/3, where k = 3 is what keeps every drag step whole in CSS px —
153
+ * the positions every engine-quantized line (see {@link snapToSystemPx})
154
+ * renders the same at. A scale whole in no k ≤ 4 falls back to single system
155
+ * px: whole device px, the finest crisp grid there. The float tolerance absorbs a scale
156
+ * that round-trips through a custom-property string (5/3 stringifies and parses
157
+ * exactly, but k × scale can land a few ulps off a whole number).
158
+ *
159
+ * The quantum moves with the scale, which is why a *stored* placement has to be
160
+ * re-snapped when the display or zoom changes: 94 system px is legal at k = 2
161
+ * and lands on a half CSS px at k = 3. Whole system px is always whole device
162
+ * px (the scale contract), so that re-snap is about the CSS-px edge alone.
163
+ */
164
+ export declare function systemPxQuantum(el: Element): number;
165
+ /**
166
+ * Snap a **system-px** coordinate onto the placement lattice
167
+ * ({@link systemPxQuantum}) — the system-px twin of {@link snapToSystemPx},
168
+ * and the one gestures use, because a placement is *stored* in system px and
169
+ * written as a live `calc()` ({@link sysLength}) so it survives a zoom.
170
+ */
171
+ export declare function snapSys(value: number, el: Element): number;
172
+ type ScaleListener = (scale: number) => void;
173
+ /**
174
+ * Watch for anything that moves the scale *or* the device grid under it — the
175
+ * window moving to a different-density monitor (via a resolution media query),
176
+ * or the user changing browser zoom (via `onZoomChange`, which also carries
177
+ * Safari's dpr-invisible zoom) — and invoke `callback` with the new scale.
178
+ * Returns a cleanup function.
179
+ *
180
+ * Ordering guarantee: by the time a callback runs, every `--vf-scale` the kit
181
+ * itself manages (component defaults via {@link ScaleController}, a root set
182
+ * by {@link applyScale}) has already been updated — so reading
183
+ * {@link effectiveScale} or a computed style inside the callback is sound.
184
+ * The full-screen pattern (`onScaleChange(fit)` re-deriving a
185
+ * `vf-desktop` raster) depends on this.
186
+ */
187
+ export declare function onScaleChange(callback: ScaleListener): () => void;
188
+ /**
189
+ * Opt a subtree into true-size rendering: set `--vf-scale` on `target` (default
190
+ * the document root) to the dynamic scale and keep it in sync as the display
191
+ * changes. Returns a cleanup function that stops watching (it leaves the last
192
+ * value in place). Strictly opt-in — call it once from your app.
193
+ */
194
+ export declare function applyScale(target?: HTMLElement): () => void;
195
+ /**
196
+ * Reactive controller that makes true-size rendering the DEFAULT for a
197
+ * component: on connect it sets `--vf-scale` to the display scale
198
+ * ({@link getScale}) on the host — UNLESS a `--vf-scale` is already in scope
199
+ * (a consumer or ancestor
200
+ * override always wins) — and keeps it synced as the display's dpr changes.
201
+ * Because `--vf-scale` is a plain inherited multiplier, a component whose
202
+ * ancestor already set it just inherits that value (no compounding).
203
+ *
204
+ * Add one line to each component: `new ScaleController(this)`.
205
+ */
206
+ export declare class ScaleController implements ReactiveController {
207
+ private readonly host;
208
+ private stop?;
209
+ /**
210
+ * True once THIS controller has written the inline `--vf-scale`, so a
211
+ * reconnect resumes syncing the value it already owns.
212
+ *
213
+ * Ownership cannot be sniffed from the inline property merely being present:
214
+ * on a *first* connect that value is the consumer's own
215
+ * (`<vf-window style="--vf-scale:1">`), and treating it as ours overwrote
216
+ * exactly the override the contract promises always wins.
217
+ */
218
+ private owns;
219
+ constructor(host: ReactiveControllerHost & HTMLElement);
220
+ /**
221
+ * True while the connect-time read was not yet trustworthy, so the decision
222
+ * is waiting for the host to be rendered (see {@link decide}).
223
+ */
224
+ private pending;
225
+ hostConnected(): void;
226
+ /**
227
+ * Re-decide once the host has rendered. Controllers' `hostUpdated` runs
228
+ * before the component's own `firstUpdated`/`updated` and before paint, so a
229
+ * deferred takeover still lands in the first frame and ahead of any JS
230
+ * geometry the component derives from {@link sys}.
231
+ */
232
+ hostUpdated(): void;
233
+ hostDisconnected(): void;
234
+ /**
235
+ * A consumer/ancestor value always wins: an inline value we did not write, or
236
+ * any value inherited from a rule, leaves the controller dormant. Only take
237
+ * over when the property is genuinely unset — or when it is already ours from
238
+ * a previous connect.
239
+ *
240
+ * `deferrable` marks the connect-time call, whose inherited read can be a
241
+ * false negative: Lit attaches a shadow root synchronously on connect but
242
+ * renders its `<slot>`s on the first update, so during the
243
+ * `customElements.define` sweep a light-DOM child whose parent upgraded first
244
+ * is assigned to no slot. Such an element sits outside the flat tree — it
245
+ * still reports a computed style, because its own rules apply, but it
246
+ * inherits nothing, so `--vf-scale` reads back empty whether or not the page
247
+ * set one. Taking over on that read is how three components ended up pinned
248
+ * at 3× inside a `:root { --vf-scale: 1 }` page; which components were hit
249
+ * was pure module-evaluation order.
250
+ */
251
+ private decide;
252
+ /**
253
+ * Whether inherited properties can resolve on the host at all: every shadow
254
+ * host between it and the document must have assigned it (or its ancestor) to
255
+ * a slot. Keeps a host whose parent renders its slot late — or never — from
256
+ * being decided on an unresolvable read.
257
+ */
258
+ private inFlatTree;
259
+ }
package/dist/scale.js ADDED
@@ -0,0 +1,133 @@
1
+ import { CLASSIC_DPI as e, CSS_REFERENCE_DPI as t, SYSTEM_PX_IN_CSS_PX as n, devicePxPerSystemPx as r, onZoomChange as i, truePixelRatio as a } from "./zoom.js";
2
+ //#region src/scale.ts
3
+ function o() {
4
+ let e = a() || 1;
5
+ return r(e) / e;
6
+ }
7
+ function s(e) {
8
+ return parseFloat(getComputedStyle(e).getPropertyValue("--vf-scale")) || o();
9
+ }
10
+ function c(e, t) {
11
+ return e * s(t);
12
+ }
13
+ function l(e) {
14
+ return e == null ? "" : `calc(var(--vf-scale, 1) * ${e}px)`;
15
+ }
16
+ function u(e) {
17
+ if (e == null) return "";
18
+ let t = String(e).trim().split(/\s+/).filter(Boolean).slice(0, 4);
19
+ if (t.length === 0) return "";
20
+ let n = t.map((e) => {
21
+ let t = Math.trunc(Number(e));
22
+ return Number.isFinite(t) ? l(t) : "";
23
+ });
24
+ return n.every(Boolean) ? n.join(" ") : "";
25
+ }
26
+ function d(e, t) {
27
+ return Math.round(e / s(t));
28
+ }
29
+ function f(e, t) {
30
+ return e / s(t);
31
+ }
32
+ function p(e) {
33
+ let t = a() || 1;
34
+ return Number.isInteger(t) ? Math.round(e) : Math.round(e * t) / t;
35
+ }
36
+ function m(e, t) {
37
+ let n = _(t);
38
+ return Math.round(e / n) * n;
39
+ }
40
+ function h(e) {
41
+ let t = s(e);
42
+ for (let e = 1; e <= 4; e++) {
43
+ let n = e * t;
44
+ if (Math.abs(n - Math.round(n)) < 1e-9) return e;
45
+ }
46
+ return 1;
47
+ }
48
+ function g(e, t) {
49
+ let n = h(t);
50
+ return Math.round(e / n) * n;
51
+ }
52
+ function _(e) {
53
+ return h(e) * s(e);
54
+ }
55
+ var v = /* @__PURE__ */ new Set(), y = /* @__PURE__ */ new Set(), b = 0, x = {
56
+ scale: 0,
57
+ dpr: 0
58
+ }, S, C, w = () => {
59
+ let e = {
60
+ scale: o(),
61
+ dpr: a()
62
+ };
63
+ if (e.scale !== x.scale || e.dpr !== x.dpr) {
64
+ x = e;
65
+ for (let t of [...v]) t(e.scale);
66
+ for (let t of [...y]) t(e.scale);
67
+ }
68
+ }, T = () => {
69
+ D(), C = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`), C.addEventListener("change", E);
70
+ }, E = () => {
71
+ w(), T();
72
+ }, D = () => {
73
+ C?.removeEventListener("change", E), C = void 0;
74
+ };
75
+ function O(e, t) {
76
+ if (typeof window > "u" || !window.matchMedia) return () => {};
77
+ t.add(e), b++ === 0 && (x = {
78
+ scale: o(),
79
+ dpr: a()
80
+ }, S = i(w), T());
81
+ let n = !1;
82
+ return () => {
83
+ n || (n = !0, t.delete(e), --b === 0 && (S?.(), S = void 0, D()));
84
+ };
85
+ }
86
+ function k(e) {
87
+ return O(e, y);
88
+ }
89
+ function A(e = document.documentElement) {
90
+ let t = () => e.style.setProperty("--vf-scale", String(o()));
91
+ return t(), O(t, v);
92
+ }
93
+ var j = class {
94
+ constructor(e) {
95
+ this.host = e, this.owns = !1, this.pending = !1, e.addController(this);
96
+ }
97
+ hostConnected() {
98
+ typeof window > "u" || this.decide(!0);
99
+ }
100
+ hostUpdated() {
101
+ this.pending && this.decide(!1);
102
+ }
103
+ hostDisconnected() {
104
+ this.stop?.(), this.stop = void 0;
105
+ }
106
+ decide(e) {
107
+ let t = () => {
108
+ this.owns = !0, this.host.style.setProperty("--vf-scale", String(o()));
109
+ }, n = () => {
110
+ this.pending = !1, t(), this.stop = O(t, v);
111
+ };
112
+ if (this.owns) return n();
113
+ if (this.host.style.getPropertyValue("--vf-scale") !== "") {
114
+ this.pending = !1;
115
+ return;
116
+ }
117
+ if (getComputedStyle(this.host).getPropertyValue("--vf-scale").trim() !== "") {
118
+ this.pending = !1;
119
+ return;
120
+ }
121
+ if (e || !this.inFlatTree()) {
122
+ this.pending = !0;
123
+ return;
124
+ }
125
+ n();
126
+ }
127
+ inFlatTree() {
128
+ for (let e = this.host; e; e = e.parentElement) if (e.parentElement?.shadowRoot && !e.assignedSlot) return !1;
129
+ return !0;
130
+ }
131
+ };
132
+ //#endregion
133
+ export { e as CLASSIC_DPI, t as CSS_REFERENCE_DPI, n as SYSTEM_PX_IN_CSS_PX, j as ScaleController, A as applyScale, s as effectiveScale, o as getScale, k as onScaleChange, g as snapSys, p as snapToDevicePx, m as snapToSystemPx, c as sys, l as sysLength, u as sysLengths, h as systemPxQuantum, d as toSys, f as toSysExact };
@@ -0,0 +1,112 @@
1
+ import { type TemplateResult } from 'lit';
2
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
3
+ /** Which way a rail runs. A component renders one rail per reserved axis. */
4
+ export type RailAxis = 'vertical' | 'horizontal';
5
+ /** What the components hand the controller. */
6
+ export interface ScrollRailOptions {
7
+ /** The scrolling element the rail reflects and drives (a div or textarea). */
8
+ getScroll: () => HTMLElement | null | undefined;
9
+ /**
10
+ * Optionally, the content whose growth changes `scrollHeight` without the
11
+ * scroller's own box resizing (slotted rows, wrapping copy) — observed so
12
+ * the thumb re-syncs when it grows.
13
+ */
14
+ getContent?: () => Element | null | undefined;
15
+ }
16
+ /**
17
+ * The behavior half of the kit-drawn scroll rails (SCROLL-RAILS-PLAN.md):
18
+ * syncs the rail subtree {@link renderScrollRail} renders to the scroller's
19
+ * native scroll state, and drives the three classic pointer interactions.
20
+ *
21
+ * The native scrollbar is hidden (`vfScrollRail` recipe), never the native
22
+ * scrolling — wheel, trackpad momentum, keyboard and AT scrolling stay the
23
+ * platform's, and this controller writes scroll position exclusively through
24
+ * `scrollTop`/`scrollLeft`, so native scrolling, `ScrollStateController` and
25
+ * consumers observing scroll events see one source of truth.
26
+ *
27
+ * What it owns:
28
+ * - **Sync** — a `scroll` listener on the scroller (plus a ResizeObserver on
29
+ * scroller and content) recomputes the thumb's travel and writes it as a
30
+ * whole-system-px translate, so the art is crisp mid-scroll; accepts the
31
+ * ≤1-frame lag behind compositor wheel scrolling every scripted scrollbar
32
+ * has (at 1-bit there is no smooth motion to betray it — no rAF loop, no
33
+ * polling). The same pass sizes the trough's whole-surface dither raster
34
+ * (the exact-tile idiom, `tileRaster`) and writes the degenerate-track
35
+ * state the recipe's decision table styles.
36
+ * - **Thumb drag** — pointer-captured and axis-locked, live scrolling (the
37
+ * modern expectation; System 7's dotted-outline drag is a possible later
38
+ * opt-in). The thumb's paint position always snaps to whole system px.
39
+ * - **Trough press** — pages by one viewport minus one 16px line of overlap
40
+ * toward the press, auto-repeating after {@link PRESS_HOLD_MS} while held
41
+ * and pausing when the thumb reaches the pointer (classic behavior).
42
+ * - **Arrow press** — steps one 16px line, auto-repeats on hold, and holds
43
+ * `data-pressed` on the button so the glyph fills solid for the press's
44
+ * whole extent.
45
+ *
46
+ * The rail subtree is `aria-hidden` and pointer-only — System 7 scrollbars
47
+ * were never keyboard targets; keyboard users scroll the focused viewport
48
+ * natively and screen readers scroll content their own way — so the
49
+ * viewport's focus ring, tabindex gating and role/name logic are untouched.
50
+ * Deliberately NOT reused: `snapSys`'s placement lattice — the rail needs
51
+ * plain whole-system-px snapping, not the k-lattice a drag gesture stores
52
+ * coordinates on.
53
+ */
54
+ export declare class ScrollRailController implements ReactiveController {
55
+ private readonly host;
56
+ private readonly opts;
57
+ private resizeObserver?;
58
+ private scroller;
59
+ private press;
60
+ private holdTimer;
61
+ private repeatTimer;
62
+ /** One raster per axis, re-encoded only when the track's size changes. */
63
+ private readonly troughCache;
64
+ constructor(host: ReactiveControllerHost & HTMLElement & {
65
+ readonly renderRoot: HTMLElement | DocumentFragment;
66
+ }, opts: ScrollRailOptions);
67
+ hostUpdated(): void;
68
+ hostDisconnected(): void;
69
+ /** (Re-)attach the scroll listener and observers to the current scroller. */
70
+ private wire;
71
+ private unwire;
72
+ private readonly onScroll;
73
+ /**
74
+ * Re-derive every measured output: thumb position, trough raster,
75
+ * degenerate state. Safe to call any time; components hit it imperatively
76
+ * when content changes without a box resize (a `<textarea>` on input —
77
+ * the same moments they call `ScrollStateController.measure()`).
78
+ */
79
+ sync(): void;
80
+ private syncRail;
81
+ readonly onPointerDown: (event: PointerEvent) => void;
82
+ readonly onPointerMove: (event: PointerEvent) => void;
83
+ /** Ends a press on pointerup / pointercancel / lostpointercapture. */
84
+ readonly onPointerUp: (event: PointerEvent) => void;
85
+ private scrollRange;
86
+ /** One line ({@link LINE_SYS} system px) in `dir`. */
87
+ private stepBy;
88
+ /** One viewport minus one line of overlap in `dir` (the classic page). */
89
+ private pageBy;
90
+ /**
91
+ * One auto-repeat beat of a held trough press: page again unless the thumb
92
+ * has reached the pointer — classic behavior; the press keeps repeating if
93
+ * the pointer then moves past the thumb again.
94
+ */
95
+ private pageTowardPointer;
96
+ private armRepeat;
97
+ private clearRepeat;
98
+ }
99
+ /**
100
+ * The rail subtree the `vfScrollRail` recipe styles — arrows at each end, the
101
+ * dithered trough and the fixed thumb between them — wired to a
102
+ * {@link ScrollRailController}.
103
+ *
104
+ * Render it as a **later sibling of the scrolling element** (the recipe's
105
+ * state selectors reach the rail from the scroller's attributes), inside a
106
+ * container that lays the rail along the component's edge; the component's
107
+ * own 1px frame border supplies the rail's outer line. The subtree is
108
+ * `aria-hidden` and contributes nothing to the accessibility tree or tab
109
+ * order — scrolling's keyboard and AT contract lives on the viewport, where
110
+ * it always did.
111
+ */
112
+ export declare function renderScrollRail(rail: ScrollRailController, axis: RailAxis): TemplateResult;