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.
- package/LICENSE +21 -0
- package/README.md +262 -0
- package/custom-elements.json +19807 -0
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js +9 -0
- package/dist/chrome.d.ts +73 -0
- package/dist/chrome.js +94 -0
- package/dist/components/vf-button-group.d.ts +50 -0
- package/dist/components/vf-button-group.js +68 -0
- package/dist/components/vf-button.d.ts +169 -0
- package/dist/components/vf-button.js +234 -0
- package/dist/components/vf-checkbox.d.ts +60 -0
- package/dist/components/vf-checkbox.js +135 -0
- package/dist/components/vf-container.d.ts +95 -0
- package/dist/components/vf-container.js +86 -0
- package/dist/components/vf-desktop.d.ts +243 -0
- package/dist/components/vf-desktop.js +310 -0
- package/dist/components/vf-dialog.d.ts +105 -0
- package/dist/components/vf-dialog.js +280 -0
- package/dist/components/vf-fieldset.d.ts +34 -0
- package/dist/components/vf-fieldset.js +76 -0
- package/dist/components/vf-grid.d.ts +124 -0
- package/dist/components/vf-grid.js +133 -0
- package/dist/components/vf-icon.d.ts +356 -0
- package/dist/components/vf-icon.js +507 -0
- package/dist/components/vf-img.d.ts +63 -0
- package/dist/components/vf-img.js +65 -0
- package/dist/components/vf-label.d.ts +117 -0
- package/dist/components/vf-label.js +151 -0
- package/dist/components/vf-list-item.d.ts +48 -0
- package/dist/components/vf-list-item.js +98 -0
- package/dist/components/vf-list.d.ts +77 -0
- package/dist/components/vf-list.js +256 -0
- package/dist/components/vf-menu-bar.d.ts +64 -0
- package/dist/components/vf-menu-bar.js +236 -0
- package/dist/components/vf-menu-item.d.ts +91 -0
- package/dist/components/vf-menu-item.js +252 -0
- package/dist/components/vf-menu.d.ts +95 -0
- package/dist/components/vf-menu.js +346 -0
- package/dist/components/vf-number-field.d.ts +60 -0
- package/dist/components/vf-number-field.js +248 -0
- package/dist/components/vf-option.d.ts +49 -0
- package/dist/components/vf-option.js +119 -0
- package/dist/components/vf-paragraph.d.ts +73 -0
- package/dist/components/vf-paragraph.js +66 -0
- package/dist/components/vf-progress-bar.d.ts +69 -0
- package/dist/components/vf-progress-bar.js +272 -0
- package/dist/components/vf-radio-group.d.ts +109 -0
- package/dist/components/vf-radio-group.js +105 -0
- package/dist/components/vf-radio.d.ts +59 -0
- package/dist/components/vf-radio.js +135 -0
- package/dist/components/vf-scroll-area.d.ts +75 -0
- package/dist/components/vf-scroll-area.js +124 -0
- package/dist/components/vf-select.d.ts +334 -0
- package/dist/components/vf-select.js +658 -0
- package/dist/components/vf-separator.d.ts +38 -0
- package/dist/components/vf-separator.js +55 -0
- package/dist/components/vf-slider.d.ts +80 -0
- package/dist/components/vf-slider.js +255 -0
- package/dist/components/vf-stack.d.ts +188 -0
- package/dist/components/vf-stack.js +168 -0
- package/dist/components/vf-swatch.d.ts +98 -0
- package/dist/components/vf-swatch.js +201 -0
- package/dist/components/vf-text-area.d.ts +77 -0
- package/dist/components/vf-text-area.js +121 -0
- package/dist/components/vf-text-field.d.ts +53 -0
- package/dist/components/vf-text-field.js +79 -0
- package/dist/components/vf-window.d.ts +157 -0
- package/dist/components/vf-window.js +377 -0
- package/dist/cursor-art.d.ts +37 -0
- package/dist/cursor-art.js +41 -0
- package/dist/cursor.d.ts +49 -0
- package/dist/cursor.js +171 -0
- package/dist/define.d.ts +45 -0
- package/dist/define.js +15 -0
- package/dist/document-listeners.d.ts +62 -0
- package/dist/document-listeners.js +29 -0
- package/dist/drag.d.ts +49 -0
- package/dist/drag.js +30 -0
- package/dist/events.d.ts +79 -0
- package/dist/events.js +25 -0
- package/dist/focus-modality.d.ts +94 -0
- package/dist/focus-modality.js +56 -0
- package/dist/form-control.d.ts +247 -0
- package/dist/form-control.js +127 -0
- package/dist/glyphs.d.ts +117 -0
- package/dist/glyphs.js +100 -0
- package/dist/grid-snap.d.ts +73 -0
- package/dist/grid-snap.js +135 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.js +71 -0
- package/dist/menu-press.d.ts +60 -0
- package/dist/menu-press.js +97 -0
- package/dist/modal-dialog.d.ts +148 -0
- package/dist/modal-dialog.js +155 -0
- package/dist/motion.d.ts +72 -0
- package/dist/motion.js +22 -0
- package/dist/number.d.ts +24 -0
- package/dist/number.js +12 -0
- package/dist/open-art.d.ts +40 -0
- package/dist/open-art.js +35 -0
- package/dist/pixel-frame.d.ts +102 -0
- package/dist/pixel-frame.js +77 -0
- package/dist/popup-overflow.d.ts +157 -0
- package/dist/popup-overflow.js +45 -0
- package/dist/position.d.ts +187 -0
- package/dist/position.js +74 -0
- package/dist/scale.d.ts +259 -0
- package/dist/scale.js +133 -0
- package/dist/scroll-rail.d.ts +112 -0
- package/dist/scroll-rail.js +194 -0
- package/dist/scroll-state.d.ts +113 -0
- package/dist/scroll-state.js +55 -0
- package/dist/size.d.ts +47 -0
- package/dist/size.js +30 -0
- package/dist/styles/base.d.ts +38 -0
- package/dist/styles/body-font.d.ts +7 -0
- package/dist/styles/body-font.js +9 -0
- package/dist/styles/display-font.d.ts +7 -0
- package/dist/styles/display-font.js +9 -0
- package/dist/styles/recipes/body-face.d.ts +14 -0
- package/dist/styles/recipes/body-face.js +6 -0
- package/dist/styles/recipes/display-face.d.ts +23 -0
- package/dist/styles/recipes/display-face.js +10 -0
- package/dist/styles/recipes/field.d.ts +20 -0
- package/dist/styles/recipes/field.js +68 -0
- package/dist/styles/recipes/focus.d.ts +76 -0
- package/dist/styles/recipes/focus.js +39 -0
- package/dist/styles/recipes/host.d.ts +5 -0
- package/dist/styles/recipes/host.js +62 -0
- package/dist/styles/recipes/pattern.d.ts +80 -0
- package/dist/styles/recipes/pattern.js +82 -0
- package/dist/styles/recipes/scroll-rail.d.ts +69 -0
- package/dist/styles/recipes/scroll-rail.js +222 -0
- package/dist/styles/recipes/shadow.d.ts +13 -0
- package/dist/styles/recipes/shadow.js +5 -0
- package/dist/styles/recipes/static-text.d.ts +24 -0
- package/dist/styles/recipes/static-text.js +17 -0
- package/dist/styles/recipes/surface.d.ts +28 -0
- package/dist/styles/recipes/surface.js +27 -0
- package/dist/styles/recipes/tile.d.ts +122 -0
- package/dist/styles/recipes/tile.js +40 -0
- package/dist/styles/recipes/title-bar.d.ts +49 -0
- package/dist/styles/recipes/title-bar.js +134 -0
- package/dist/styles/recipes/toggle.d.ts +10 -0
- package/dist/styles/recipes/toggle.js +20 -0
- package/dist/styles/register-embedded-font.d.ts +64 -0
- package/dist/styles/register-embedded-font.js +30 -0
- package/dist/text-control.d.ts +131 -0
- package/dist/text-control.js +83 -0
- package/dist/tile-grid.d.ts +111 -0
- package/dist/tile-grid.js +53 -0
- package/dist/toggle-control.d.ts +60 -0
- package/dist/toggle-control.js +41 -0
- package/dist/track-width.d.ts +42 -0
- package/dist/track-width.js +30 -0
- package/dist/type-ahead.d.ts +35 -0
- package/dist/type-ahead.js +20 -0
- package/dist/zoom.d.ts +204 -0
- package/dist/zoom.js +105 -0
- package/docs/SPEC.md +2471 -0
- package/editor/vscode.html-custom-data.json +1320 -0
- package/editor/web-types.json +3237 -0
- package/package.json +118 -0
package/dist/scale.d.ts
ADDED
|
@@ -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;
|