vintage-frames 0.1.0 → 0.2.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/README.md +12 -233
- package/custom-elements.json +290 -38
- package/dist/components/vf-button.d.ts +1 -1
- package/dist/components/vf-container.d.ts +2 -2
- package/dist/components/vf-desktop.d.ts +1 -1
- package/dist/components/vf-fieldset.d.ts +1 -1
- package/dist/components/vf-fieldset.js +6 -1
- package/dist/components/vf-grid.d.ts +1 -1
- package/dist/components/vf-icon.d.ts +1 -1
- package/dist/components/vf-img.d.ts +1 -1
- package/dist/components/vf-label.d.ts +1 -1
- package/dist/components/vf-list-item.d.ts +9 -1
- package/dist/components/vf-list-item.js +15 -14
- package/dist/components/vf-list.d.ts +1 -1
- package/dist/components/vf-menu-bar.d.ts +1 -1
- package/dist/components/vf-menu-item.d.ts +10 -1
- package/dist/components/vf-menu-item.js +35 -34
- package/dist/components/vf-menu.d.ts +11 -2
- package/dist/components/vf-menu.js +33 -32
- package/dist/components/vf-option.d.ts +11 -1
- package/dist/components/vf-option.js +16 -15
- package/dist/components/vf-paragraph.d.ts +3 -3
- package/dist/components/vf-progress-bar.d.ts +1 -1
- package/dist/components/vf-scroll-area.d.ts +1 -1
- package/dist/components/vf-select.d.ts +1 -1
- package/dist/components/vf-separator.d.ts +1 -1
- package/dist/components/vf-slider.d.ts +1 -1
- package/dist/components/vf-stack.d.ts +27 -9
- package/dist/components/vf-stack.js +30 -8
- package/dist/components/vf-swatch.d.ts +1 -1
- package/dist/components/vf-window.d.ts +1 -1
- package/dist/cross-center.d.ts +76 -0
- package/dist/cross-center.js +69 -0
- package/dist/define.d.ts +2 -2
- package/dist/define.js +1 -1
- package/dist/grid-snap.d.ts +2 -24
- package/dist/grid-snap.js +7 -33
- package/dist/index.d.ts +5 -5
- package/dist/index.js +50 -50
- package/dist/position.d.ts +11 -2
- package/dist/styles/recipes/host.js +14 -1
- package/dist/styles/recipes/toggle.js +16 -0
- package/dist/text-control.d.ts +1 -1
- package/docs/SPEC.md +287 -2249
- package/editor/vscode.html-custom-data.json +48 -8
- package/editor/web-types.json +92 -12
- package/package.json +2 -4
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { vfElement as e } from "../define.js";
|
|
2
2
|
import { ScaleController as t } from "../scale.js";
|
|
3
3
|
import n from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { VfPositioned as r } from "../position.js";
|
|
5
|
+
import { vfDisplay as i } from "../styles/recipes/display-face.js";
|
|
6
|
+
import { vfBase as a } from "../styles/recipes/host.js";
|
|
7
|
+
import { CHECKMARK as o, glyphSvg as s } from "../glyphs.js";
|
|
8
|
+
import { LitElement as c, css as l, html as u } from "lit";
|
|
9
|
+
import { property as d } from "lit/decorators.js";
|
|
9
10
|
//#region src/components/vf-option.ts
|
|
10
|
-
var
|
|
11
|
+
var f = class extends r(c) {
|
|
11
12
|
static {
|
|
12
13
|
this.styles = [
|
|
14
|
+
a,
|
|
13
15
|
i,
|
|
14
|
-
|
|
15
|
-
c`
|
|
16
|
+
l`
|
|
16
17
|
:host {
|
|
17
18
|
position: relative;
|
|
18
19
|
display: flex;
|
|
@@ -97,23 +98,23 @@ var d = class extends s {
|
|
|
97
98
|
e.has("selected") && (this.#e.ariaSelected = this.selected ? "true" : "false"), e.has("disabled") && (this.#e.ariaDisabled = this.disabled ? "true" : null);
|
|
98
99
|
}
|
|
99
100
|
render() {
|
|
100
|
-
return
|
|
101
|
+
return u`
|
|
101
102
|
<span class="check" part="check" aria-hidden="true"
|
|
102
|
-
>${o
|
|
103
|
+
>${s(o, "checkmark")}</span
|
|
103
104
|
>
|
|
104
105
|
<slot></slot>
|
|
105
106
|
`;
|
|
106
107
|
}
|
|
107
108
|
};
|
|
108
|
-
n([
|
|
109
|
+
n([d()], f.prototype, "value", void 0), n([d({
|
|
109
110
|
type: Boolean,
|
|
110
111
|
reflect: !0
|
|
111
|
-
})],
|
|
112
|
+
})], f.prototype, "disabled", void 0), n([d({
|
|
112
113
|
type: Boolean,
|
|
113
114
|
reflect: !0
|
|
114
|
-
})],
|
|
115
|
+
})], f.prototype, "selected", void 0), n([d({
|
|
115
116
|
type: Boolean,
|
|
116
117
|
reflect: !0
|
|
117
|
-
})],
|
|
118
|
+
})], f.prototype, "active", void 0), f = n([e("vf-option")], f);
|
|
118
119
|
//#endregion
|
|
119
|
-
export {
|
|
120
|
+
export { f as VfOption };
|
|
@@ -19,8 +19,8 @@ declare const VfParagraph_base: (new (...args: any[]) => import("../size.js").Vf
|
|
|
19
19
|
* everything after it further off the device-pixel grid, smearing 1-bit
|
|
20
20
|
* borders and bitmap glyph stems (docs/SIZING.md rule 2). A
|
|
21
21
|
* whole-pixel line box accumulates whole offsets;
|
|
22
|
-
* - its own {@link GridSnapController}, so it holds its own origin
|
|
23
|
-
*
|
|
22
|
+
* - its own {@link GridSnapController}, so it holds its own origin
|
|
23
|
+
* wherever the page puts it;
|
|
24
24
|
* - a **declared box** when the layout wants one — `width`/`height` in whole
|
|
25
25
|
* system px ({@link VfSized}). In flow a paragraph takes its container's
|
|
26
26
|
* width, which is usually right; a *placed* one (`top`/`left`) shrink-wraps
|
|
@@ -52,7 +52,7 @@ export declare class VfParagraph extends VfParagraph_base {
|
|
|
52
52
|
static styles: import("lit").CSSResult[];
|
|
53
53
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
54
54
|
private readonly scale;
|
|
55
|
-
/** Device-pixel grid snapping
|
|
55
|
+
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
56
56
|
private readonly gridSnap;
|
|
57
57
|
/**
|
|
58
58
|
* Which embedded face to set the copy in — `'body'` (Geneva, the
|
|
@@ -28,7 +28,7 @@ export declare class VfProgressBar extends VfProgressBar_base {
|
|
|
28
28
|
#private;
|
|
29
29
|
static styles: import("lit").CSSResult[];
|
|
30
30
|
private readonly scale;
|
|
31
|
-
/** Device-pixel grid snapping
|
|
31
|
+
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
32
32
|
private readonly gridSnap;
|
|
33
33
|
/** Current progress, from 0 to `max`. Clamped for display and ARIA. */
|
|
34
34
|
value: number;
|
|
@@ -30,7 +30,7 @@ declare const VfScrollArea_base: (new (...args: any[]) => import("../position.js
|
|
|
30
30
|
*/
|
|
31
31
|
export declare class VfScrollArea extends VfScrollArea_base {
|
|
32
32
|
private readonly scale;
|
|
33
|
-
/** Device-pixel grid snapping
|
|
33
|
+
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
34
34
|
private readonly gridSnap;
|
|
35
35
|
/**
|
|
36
36
|
* Which scroll rails to reserve as permanent placeholders: `vertical`
|
|
@@ -116,7 +116,7 @@ export declare class VfSelect extends VfSelect_base {
|
|
|
116
116
|
private cachedOptions;
|
|
117
117
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
118
118
|
private readonly scale;
|
|
119
|
-
/** Device-pixel grid snapping
|
|
119
|
+
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
120
120
|
private readonly gridSnap;
|
|
121
121
|
/**
|
|
122
122
|
* Whether the closed pill wears the kit's dashed focus rule — keyboard focus
|
|
@@ -22,7 +22,7 @@ export declare class VfSeparator extends VfSeparator_base {
|
|
|
22
22
|
#private;
|
|
23
23
|
static styles: import("lit").CSSResult[];
|
|
24
24
|
private readonly scale;
|
|
25
|
-
/** Device-pixel grid snapping
|
|
25
|
+
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
26
26
|
private readonly gridSnap;
|
|
27
27
|
/** Render as a vertical rule (1px wide) instead of horizontal. */
|
|
28
28
|
vertical: boolean;
|
|
@@ -48,7 +48,7 @@ export declare class VfSlider extends VfSlider_base {
|
|
|
48
48
|
private readonly trackSize;
|
|
49
49
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
50
50
|
private readonly scale;
|
|
51
|
-
/** Device-pixel grid snapping
|
|
51
|
+
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
52
52
|
private readonly gridSnap;
|
|
53
53
|
/** True when this component owns the host `tabindex`. */
|
|
54
54
|
private selfManagedTabIndex;
|
|
@@ -93,12 +93,16 @@ declare const VfStack_base: (new (...args: any[]) => import("../size.js").VfSize
|
|
|
93
93
|
* and a layout box for *anything*, and consumer content inside it — a div, an
|
|
94
94
|
* `<img>`, a run of text — has no controller of its own. So the flex
|
|
95
95
|
* container, the placed-child anchor and the `vf-snap` class live on one
|
|
96
|
-
* shadow box, and
|
|
96
|
+
* shadow box, and the correction moves the stack's
|
|
97
97
|
* whole coordinate system; children (kit or not) ride it, and the sweep's
|
|
98
98
|
* outermost-first order means the kit children then find nothing left to
|
|
99
99
|
* correct. What this deliberately does not fix: a text-governed child width
|
|
100
100
|
* mid-row still shifts later siblings fractionally (their own controllers
|
|
101
|
-
* cover that)
|
|
101
|
+
* cover that).
|
|
102
|
+
*
|
|
103
|
+
* **Centering lands on whole system px as well** — halving an odd count of free
|
|
104
|
+
* system px gives a half, which no container can round in CSS. See
|
|
105
|
+
* {@link CrossCenterController}.
|
|
102
106
|
*
|
|
103
107
|
* It is also the kit's one **typographically transparent** component: `vfBase`'s
|
|
104
108
|
* chrome face, ratio line box, color and non-selectability are all reset to
|
|
@@ -148,18 +152,18 @@ export declare class VfStack extends VfStack_base {
|
|
|
148
152
|
* `text-align` on any element, so the cross-axis switch used to re-align every
|
|
149
153
|
* run of copy inside the stack (see the `text-align` reset above).
|
|
150
154
|
*
|
|
151
|
-
*
|
|
152
|
-
* 16px caption centered in a row set by the
|
|
153
|
-
* sits at 4.5
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
155
|
+
* Centering divides the free space in two, so an odd count of system px would
|
|
156
|
+
* land a child on a half — a 16px caption centered in a row set by the
|
|
157
|
+
* 25-system-px `vf-number-field` sits at 4.5. {@link CrossCenterController}
|
|
158
|
+
* steps that back onto whole system px, the exact half going toward the start
|
|
159
|
+
* the way QuickDraw's `div 2` did; `place="start"` is still the way to ask
|
|
160
|
+
* for no centering at all.
|
|
157
161
|
*/
|
|
158
162
|
place?: VfStackPlace;
|
|
159
163
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
160
164
|
private readonly scale;
|
|
161
165
|
/**
|
|
162
|
-
* Hold the box on the device-pixel grid
|
|
166
|
+
* Hold the box on the device-pixel grid — see the
|
|
163
167
|
* class doc for why the original no-controller decision was reversed. The
|
|
164
168
|
* host is what gets measured; `.box` (vf-snap) is where the correction
|
|
165
169
|
* lands, taking the whole coordinate system with it.
|
|
@@ -167,6 +171,20 @@ export declare class VfStack extends VfStack_base {
|
|
|
167
171
|
private readonly gridSnap;
|
|
168
172
|
/** The shadow flex box `gap`/`pad` write to; exists from the first render. */
|
|
169
173
|
private readonly box;
|
|
174
|
+
/** The slot the children arrive through. (`slot` itself is HTMLElement's.) */
|
|
175
|
+
private readonly childSlot;
|
|
176
|
+
/**
|
|
177
|
+
* Keeps cross-axis centering on whole system px (src/cross-center.ts).
|
|
178
|
+
* A null axis is the inert state — no observers, nothing held — which is
|
|
179
|
+
* every column that starts its children, i.e. the default.
|
|
180
|
+
*/
|
|
181
|
+
private readonly crossCenter;
|
|
182
|
+
/**
|
|
183
|
+
* Mirrors the stylesheet's alignment selectors: a row centers unless `place`
|
|
184
|
+
* names something else, a column only when it says `center`, and an
|
|
185
|
+
* unrecognized value lands on the direction's default in both places.
|
|
186
|
+
*/
|
|
187
|
+
private get centersChildren();
|
|
170
188
|
/**
|
|
171
189
|
* `gap` and `pad` go on the shadow box's inline style as
|
|
172
190
|
* `calc(var(--vf-scale, 1) * Npx)`, the way VfSized writes the declared
|
|
@@ -5,15 +5,20 @@ import { VfPositioned as a } from "../position.js";
|
|
|
5
5
|
import { vfBase as o } from "../styles/recipes/host.js";
|
|
6
6
|
import { GridSnapController as s } from "../grid-snap.js";
|
|
7
7
|
import { VfSized as c } from "../size.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { CrossCenterController as l } from "../cross-center.js";
|
|
9
|
+
import { LitElement as u, css as d, html as f } from "lit";
|
|
10
|
+
import { property as p, query as m } from "lit/decorators.js";
|
|
10
11
|
//#region src/components/vf-stack.ts
|
|
11
|
-
var
|
|
12
|
+
var h = class extends c(a(u)) {
|
|
12
13
|
constructor(...e) {
|
|
13
|
-
super(...e), this.direction = "column", this.gap = 0, this.scale = new t(this), this.gridSnap = new s(this)
|
|
14
|
+
super(...e), this.direction = "column", this.gap = 0, this.scale = new t(this), this.gridSnap = new s(this), this.crossCenter = new l(this, () => ({
|
|
15
|
+
box: this.box,
|
|
16
|
+
slot: this.childSlot,
|
|
17
|
+
axis: this.centersChildren ? this.direction === "row" ? "y" : "x" : null
|
|
18
|
+
}));
|
|
14
19
|
}
|
|
15
20
|
static {
|
|
16
|
-
this.styles = [o,
|
|
21
|
+
this.styles = [o, d`
|
|
17
22
|
:host {
|
|
18
23
|
/* Shrink to the content, but stay BLOCK-level while doing it: the stack
|
|
19
24
|
is exactly as big as what it holds, so it can't hand a size it never
|
|
@@ -126,6 +131,20 @@ var m = class extends c(a(l)) {
|
|
|
126
131
|
align-items: flex-end;
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
/* The centering tie: CrossCenterController steps a centered child onto
|
|
135
|
+
whole system px through these variables (src/cross-center.ts has the
|
|
136
|
+
why). Keyed off the marker because a ::slotted rule outranks a slotted
|
|
137
|
+
element's own :host — a blanket position would restyle vf-window and
|
|
138
|
+
vf-menu. */
|
|
139
|
+
:host(:not([direction='row'])) ::slotted([data-vf-tie]) {
|
|
140
|
+
position: relative;
|
|
141
|
+
left: var(--vf-stack-dx, 0px);
|
|
142
|
+
}
|
|
143
|
+
:host([direction='row']) ::slotted([data-vf-tie]) {
|
|
144
|
+
position: relative;
|
|
145
|
+
top: var(--vf-stack-dy, 0px);
|
|
146
|
+
}
|
|
147
|
+
|
|
129
148
|
/* --- Children ----------------------------------------------------- */
|
|
130
149
|
/* Neither grow nor shrink: the content governs the box, not the other way
|
|
131
150
|
round (see the class doc). A light-DOM declaration beats a ::slotted
|
|
@@ -156,13 +175,16 @@ var m = class extends c(a(l)) {
|
|
|
156
175
|
}
|
|
157
176
|
`];
|
|
158
177
|
}
|
|
178
|
+
get centersChildren() {
|
|
179
|
+
return this.place === "center" ? !0 : this.place === "start" || this.place === "end" ? !1 : this.direction === "row";
|
|
180
|
+
}
|
|
159
181
|
updated(e) {
|
|
160
182
|
e.has("gap") && (this.box.style.gap = n(this.gap)), e.has("pad") && (this.box.style.padding = r(this.pad));
|
|
161
183
|
}
|
|
162
184
|
render() {
|
|
163
|
-
return
|
|
185
|
+
return f`<div class="vf-snap box"><slot></slot></div>`;
|
|
164
186
|
}
|
|
165
187
|
};
|
|
166
|
-
i([
|
|
188
|
+
i([p({ reflect: !0 })], h.prototype, "direction", void 0), i([p({ type: Number })], h.prototype, "gap", void 0), i([p()], h.prototype, "pad", void 0), i([p({ reflect: !0 })], h.prototype, "place", void 0), i([m(".box")], h.prototype, "box", void 0), i([m("slot")], h.prototype, "childSlot", void 0), h = i([e("vf-stack")], h);
|
|
167
189
|
//#endregion
|
|
168
|
-
export {
|
|
190
|
+
export { h as VfStack };
|
|
@@ -79,7 +79,7 @@ export declare class VfSwatch extends VfSwatch_base {
|
|
|
79
79
|
disabled: boolean;
|
|
80
80
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
81
81
|
private readonly scale;
|
|
82
|
-
/** Device-pixel grid snapping
|
|
82
|
+
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
83
83
|
private readonly gridSnap;
|
|
84
84
|
/**
|
|
85
85
|
* The consumer's `--vf-swatch-checker` override, or `''` for the kit
|
|
@@ -92,7 +92,7 @@ export declare class VfWindow extends VfWindow_base {
|
|
|
92
92
|
scrollbars?: 'vertical' | 'horizontal' | 'both';
|
|
93
93
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
94
94
|
private readonly scale;
|
|
95
|
-
/** Device-pixel grid snapping
|
|
95
|
+
/** Device-pixel grid snapping; see src/grid-snap.ts. */
|
|
96
96
|
private readonly gridSnap;
|
|
97
97
|
/** Holds the centered title patch on the placement lattice (src/chrome.ts). */
|
|
98
98
|
private readonly titleCenter;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
+
/** The axis a container centers its children across — `null` when it doesn't. */
|
|
3
|
+
export type CrossAxis = 'x' | 'y' | null;
|
|
4
|
+
/** What {@link CrossCenterController} reads off its host before each measure. */
|
|
5
|
+
export interface CrossCenterParts {
|
|
6
|
+
/** The flex box the children are centered in. */
|
|
7
|
+
box: HTMLElement | null | undefined;
|
|
8
|
+
/** The slot they arrive through. */
|
|
9
|
+
slot: HTMLSlotElement | null | undefined;
|
|
10
|
+
/** Which axis that centering happens on, if it happens at all. */
|
|
11
|
+
axis: CrossAxis;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Marks a held child; the container's `::slotted()` rule keys off it. The
|
|
15
|
+
* container owns the correction, the child carries only the fact of it.
|
|
16
|
+
*/
|
|
17
|
+
export declare const CENTER_TIE_ATTRIBUTE = "data-vf-tie";
|
|
18
|
+
/**
|
|
19
|
+
* Holds cross-axis centering on the system-pixel grid.
|
|
20
|
+
*
|
|
21
|
+
* Centering halves the free space, and half of an odd count of system px is a
|
|
22
|
+
* half: a 71-system-px child centered in 200 sits at 64.5 — half a *system*
|
|
23
|
+
* px, so the child's whole 1-bit interior misses the device grid at every
|
|
24
|
+
* density. CSS can't round it: no rule can say "half my child's width,
|
|
25
|
+
* floored", and a fractional transform leaves the composite-time fringe
|
|
26
|
+
* grid-snap.ts measured. So the offset is measured here and corrected through
|
|
27
|
+
* layout: each centered child steps to the nearest whole system px, the exact
|
|
28
|
+
* half going toward the start as QuickDraw's `div 2` did.
|
|
29
|
+
*
|
|
30
|
+
* It measures where flexbox actually put each child rather than deriving from
|
|
31
|
+
* the two widths — a stack holds anything, including a child whose width isn't
|
|
32
|
+
* whole system px (`vf-select` is 87⅓ at 1.5×), and deriving would move such a
|
|
33
|
+
* child *off* a grid position it already held. Everything is relative to the
|
|
34
|
+
* box, so a stack the page put on a fractional origin is untouched: that stays
|
|
35
|
+
* the page's fault, or the grid snapper's to absorb (src/grid-snap.ts).
|
|
36
|
+
*
|
|
37
|
+
* The correction lands as {@link CENTER_TIE_ATTRIBUTE} plus a length in
|
|
38
|
+
* `--vf-stack-dx` / `--vf-stack-dy`, applied by the container's own
|
|
39
|
+
* `::slotted([data-vf-tie])` rule (see vf-stack.ts). A marker plus a variable
|
|
40
|
+
* is the weakest channel available — any inline style or page rule beats it —
|
|
41
|
+
* and the marker matters because a `::slotted` rule outranks a slotted
|
|
42
|
+
* element's own `:host` (measured): a blanket `position` would restyle
|
|
43
|
+
* `vf-window` and `vf-menu`.
|
|
44
|
+
*
|
|
45
|
+
* Re-measures after every host render and on any resize of the box or a child
|
|
46
|
+
* — a density change, a late-registering face, an edited label. A relative
|
|
47
|
+
* offset changes no box's size, so the measurement stays stable: the applied
|
|
48
|
+
* correction is subtracted back out before the next one is derived, and a
|
|
49
|
+
* child already on the grid is left exactly as it is.
|
|
50
|
+
*/
|
|
51
|
+
export declare class CrossCenterController implements ReactiveController {
|
|
52
|
+
private readonly host;
|
|
53
|
+
private readonly read;
|
|
54
|
+
private observer?;
|
|
55
|
+
private observed;
|
|
56
|
+
private slot?;
|
|
57
|
+
private readonly held;
|
|
58
|
+
constructor(host: ReactiveControllerHost & HTMLElement, read: () => CrossCenterParts);
|
|
59
|
+
hostConnected(): void;
|
|
60
|
+
hostUpdated(): void;
|
|
61
|
+
hostDisconnected(): void;
|
|
62
|
+
/** Re-attach to the current box, slot and children, then re-measure. */
|
|
63
|
+
private readonly sync;
|
|
64
|
+
/**
|
|
65
|
+
* Detach from everything and give every held child its box back. Keeps the
|
|
66
|
+
* observer instance, so a re-parented stack doesn't allocate a fresh one.
|
|
67
|
+
*/
|
|
68
|
+
private rest;
|
|
69
|
+
/** Observe the box and every child, once per distinct set. */
|
|
70
|
+
private watch;
|
|
71
|
+
/** Put every centered child on the nearest whole system px, ties to the start. */
|
|
72
|
+
private hold;
|
|
73
|
+
private apply;
|
|
74
|
+
/** Give a child back exactly the box it arrived with. */
|
|
75
|
+
private clear;
|
|
76
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { effectiveScale as e } from "./scale.js";
|
|
2
|
+
//#region src/cross-center.ts
|
|
3
|
+
var t = "data-vf-tie", n = {
|
|
4
|
+
x: "--vf-stack-dx",
|
|
5
|
+
y: "--vf-stack-dy"
|
|
6
|
+
}, r = 1 / 32, i = .05, a = (e) => parseFloat(e) || 0;
|
|
7
|
+
function o(e, t) {
|
|
8
|
+
let n = getComputedStyle(e), r = e.getBoundingClientRect();
|
|
9
|
+
return t === "x" ? r.left + a(n.borderLeftWidth) + a(n.paddingLeft) : r.top + a(n.borderTopWidth) + a(n.paddingTop);
|
|
10
|
+
}
|
|
11
|
+
var s = class {
|
|
12
|
+
constructor(e, t) {
|
|
13
|
+
this.host = e, this.read = t, this.observed = [], this.held = /* @__PURE__ */ new Map(), this.sync = () => {
|
|
14
|
+
let e = this.read(), { box: t, slot: n, axis: r } = e;
|
|
15
|
+
if (!t || !n || !r) return this.rest();
|
|
16
|
+
n !== this.slot && (this.slot?.removeEventListener("slotchange", this.sync), n.addEventListener("slotchange", this.sync), this.slot = n), this.watch(t, n), this.hold(e);
|
|
17
|
+
}, e.addController(this);
|
|
18
|
+
}
|
|
19
|
+
hostConnected() {
|
|
20
|
+
this.sync();
|
|
21
|
+
}
|
|
22
|
+
hostUpdated() {
|
|
23
|
+
this.sync();
|
|
24
|
+
}
|
|
25
|
+
hostDisconnected() {
|
|
26
|
+
this.rest();
|
|
27
|
+
}
|
|
28
|
+
rest() {
|
|
29
|
+
this.observer?.disconnect(), this.observed = [], this.slot?.removeEventListener("slotchange", this.sync), this.slot = void 0;
|
|
30
|
+
for (let e of [...this.held.keys()]) this.clear(e);
|
|
31
|
+
}
|
|
32
|
+
watch(e, t) {
|
|
33
|
+
if (typeof ResizeObserver > "u") return;
|
|
34
|
+
let n = [e, ...t.assignedElements()];
|
|
35
|
+
if (!(n.length === this.observed.length && n.every((e, t) => e === this.observed[t]))) {
|
|
36
|
+
this.observer ??= new ResizeObserver(() => this.hold(this.read())), this.observer.disconnect();
|
|
37
|
+
for (let e of n) this.observer.observe(e);
|
|
38
|
+
this.observed = n;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
hold({ box: t, slot: n, axis: a }) {
|
|
42
|
+
if (!t || !n || !a) return;
|
|
43
|
+
let s = e(this.host), c = o(t, a), l = new Set(this.held.keys());
|
|
44
|
+
for (let e of n.assignedElements()) {
|
|
45
|
+
if (!(e instanceof HTMLElement)) continue;
|
|
46
|
+
l.delete(e);
|
|
47
|
+
let t = getComputedStyle(e).position;
|
|
48
|
+
if (t === "absolute" || t === "fixed") {
|
|
49
|
+
this.clear(e);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
let n = e.getBoundingClientRect(), o = a === "x" ? n.left : n.top, u = this.held.get(e), d = o - c - (u?.axis === a ? u.offset : 0), f = Math.ceil(d / s - .5 - i) * s - d;
|
|
53
|
+
Math.abs(f) < r ? this.clear(e) : this.apply(e, a, f);
|
|
54
|
+
}
|
|
55
|
+
for (let e of l) this.clear(e);
|
|
56
|
+
}
|
|
57
|
+
apply(e, r, i) {
|
|
58
|
+
let a = this.held.get(e);
|
|
59
|
+
(a?.axis !== r || a.offset !== i) && (e.style.setProperty(n[r], `${i}px`), e.style.removeProperty(n[r === "x" ? "y" : "x"]), e.setAttribute(t, ""), this.held.set(e, {
|
|
60
|
+
axis: r,
|
|
61
|
+
offset: i
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
clear(e) {
|
|
65
|
+
this.held.delete(e) && (e.style.removeProperty(n.x), e.style.removeProperty(n.y), e.removeAttribute(t), e.getAttribute("style") || e.removeAttribute("style"));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
export { t as CENTER_TIE_ATTRIBUTE, s as CrossCenterController };
|
package/dist/define.d.ts
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* working. The warning matters as much as the guard, because a duplicated copy
|
|
15
15
|
* is not benign even once the page survives it: the scaling, grid-snapping and
|
|
16
16
|
* focus-modality registries are all module-scoped singletons, so the second
|
|
17
|
-
* copy's
|
|
18
|
-
*
|
|
17
|
+
* copy's schedulers reach none of the first copy's components and the two
|
|
18
|
+
* disagree silently. Better to say so than to let it look fine.
|
|
19
19
|
*/
|
|
20
20
|
/**
|
|
21
21
|
* Register `ctor` under `tagName` unless the name is already taken.
|
package/dist/define.js
CHANGED
|
@@ -3,7 +3,7 @@ var e = (e, t) => {
|
|
|
3
3
|
let n = customElements.get(e);
|
|
4
4
|
if (n !== t) {
|
|
5
5
|
if (n !== void 0) {
|
|
6
|
-
console.warn(`[vintage-frames] <${e}> is already registered on this page, so this second registration was skipped and the first one kept. Two copies of the library also mean two of every module-scoped singleton —
|
|
6
|
+
console.warn(`[vintage-frames] <${e}> is already registered on this page, so this second registration was skipped and the first one kept. Two copies of the library also mean two of every module-scoped singleton — the grid-snap scheduler, applyScale() and the focus-modality tracker each only reach the components built from their own copy. Dedupe vintage-frames to a single version.`);
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
9
|
customElements.define(e, t);
|
package/dist/grid-snap.d.ts
CHANGED
|
@@ -9,9 +9,8 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
|
9
9
|
*
|
|
10
10
|
* The component's template must put the `vf-snap` class (see `vfBase`) on its
|
|
11
11
|
* top-level painted element(s); the host box is what gets measured, so an
|
|
12
|
-
* authored offset inside it (a toggle's centered box) stays put.
|
|
13
|
-
*
|
|
14
|
-
* `nosnap` attribute on the host.
|
|
12
|
+
* authored offset inside it (a toggle's centered box) stays put. Always
|
|
13
|
+
* active; skipped per-element by a `nosnap` attribute on the host.
|
|
15
14
|
*/
|
|
16
15
|
export declare class GridSnapController implements ReactiveController {
|
|
17
16
|
readonly host: ReactiveControllerHost & HTMLElement;
|
|
@@ -44,27 +43,6 @@ export declare class GridSnapController implements ReactiveController {
|
|
|
44
43
|
/** Drop the correction: delete the two variables and forget everything. */
|
|
45
44
|
reset(): void;
|
|
46
45
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Opt the page into automatic device-pixel-grid snapping: every mounted
|
|
49
|
-
* component (and every one mounted afterwards) holds its paint on whole device
|
|
50
|
-
* pixels, whatever the surrounding layout does. Returns a cleanup function
|
|
51
|
-
* that turns it back off. Calls share one switch: snapping stays on until
|
|
52
|
-
* every caller's cleanup has run, and running a cleanup twice releases only
|
|
53
|
-
* once — so two widgets on one page can each opt in and out without turning
|
|
54
|
-
* the other's snapping off.
|
|
55
|
-
*
|
|
56
|
-
* ```ts
|
|
57
|
-
* import { applyGridSnap } from 'vintage-frames'
|
|
58
|
-
* applyGridSnap()
|
|
59
|
-
* ```
|
|
60
|
-
*
|
|
61
|
-
* The whole footprint on your DOM is two reserved custom properties
|
|
62
|
-
* (`--vf-snap-dx`/`--vf-snap-dy`) on each corrected host's inline style; the
|
|
63
|
-
* offset they drive is applied inside the component's own shadow root. Worth
|
|
64
|
-
* knowing: a component's painted box can sit up to half a device pixel outside
|
|
65
|
-
* its layout box while corrected.
|
|
66
|
-
*/
|
|
67
|
-
export declare function applyGridSnap(): () => void;
|
|
68
46
|
/**
|
|
69
47
|
* Re-check every component before the next paint. Call it after moving
|
|
70
48
|
* components in a way nothing observable changed size — the one case the
|
package/dist/grid-snap.js
CHANGED
|
@@ -6,29 +6,14 @@ var n = .05, r = 1 / 64, i = () => e() || 1, a = (e, t) => {
|
|
|
6
6
|
return n - Math.round(n);
|
|
7
7
|
}, o = (e) => Math.round(e / r) * r, s = new class {
|
|
8
8
|
constructor() {
|
|
9
|
-
this.controllers = /* @__PURE__ */ new Set(), this.enabled = !1, this.frame = 0
|
|
9
|
+
this.controllers = /* @__PURE__ */ new Set(), this.enabled = !1, this.frame = 0;
|
|
10
10
|
}
|
|
11
11
|
register(e) {
|
|
12
|
-
|
|
13
|
-
e.reset();
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
this.resizes?.observe(e.host), this.request();
|
|
12
|
+
this.controllers.add(e), !this.enabled && typeof window < "u" ? (this.enabled = !0, this.install()) : this.resizes?.observe(e.host), this.request();
|
|
17
13
|
}
|
|
18
14
|
unregister(e) {
|
|
19
15
|
this.controllers.delete(e), this.resizes?.unobserve(e.host);
|
|
20
16
|
}
|
|
21
|
-
enable() {
|
|
22
|
-
this.enabled || typeof window > "u" || (this.enabled = !0, this.install(), this.request());
|
|
23
|
-
}
|
|
24
|
-
disable() {
|
|
25
|
-
if (this.enabled) {
|
|
26
|
-
this.enabled = !1, this.frame && cancelAnimationFrame(this.frame), this.frame = 0;
|
|
27
|
-
for (let e of this.teardown.splice(0)) e();
|
|
28
|
-
this.resizes?.disconnect(), this.resizes = void 0;
|
|
29
|
-
for (let e of this.controllers) e.reset();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
17
|
request() {
|
|
33
18
|
!this.enabled || this.frame || typeof window > "u" || (this.frame = requestAnimationFrame(() => {
|
|
34
19
|
this.frame = 0, this.sweep();
|
|
@@ -45,17 +30,13 @@ var n = .05, r = 1 / 64, i = () => e() || 1, a = (e, t) => {
|
|
|
45
30
|
window.addEventListener("resize", e), window.addEventListener("orientationchange", e), window.addEventListener("scroll", e, {
|
|
46
31
|
capture: !0,
|
|
47
32
|
passive: !0
|
|
48
|
-
}), this.teardown.push(() => {
|
|
49
|
-
window.removeEventListener("resize", e), window.removeEventListener("orientationchange", e), window.removeEventListener("scroll", e, { capture: !0 });
|
|
50
33
|
});
|
|
51
34
|
let n = document.fonts;
|
|
52
|
-
n && (n.addEventListener("loadingdone", e),
|
|
53
|
-
let r = new MutationObserver(e);
|
|
54
|
-
r.observe(document.documentElement, {
|
|
35
|
+
n && (n.addEventListener("loadingdone", e), n.ready.then(e)), t(e), new MutationObserver(e).observe(document.documentElement, {
|
|
55
36
|
subtree: !0,
|
|
56
37
|
attributes: !0,
|
|
57
38
|
attributeFilter: ["nosnap"]
|
|
58
|
-
})
|
|
39
|
+
});
|
|
59
40
|
}
|
|
60
41
|
}(), c = class {
|
|
61
42
|
constructor(e) {
|
|
@@ -120,16 +101,9 @@ var n = .05, r = 1 / 64, i = () => e() || 1, a = (e, t) => {
|
|
|
120
101
|
let t = 0;
|
|
121
102
|
for (let n = l(e); n; n = l(n)) n.tagName.toLowerCase().startsWith("vf-") && t++;
|
|
122
103
|
return t;
|
|
123
|
-
}
|
|
124
|
-
function
|
|
125
|
-
d++, s.enable();
|
|
126
|
-
let e = !1;
|
|
127
|
-
return () => {
|
|
128
|
-
e || (e = !0, --d === 0 && s.disable());
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
function p() {
|
|
104
|
+
};
|
|
105
|
+
function d() {
|
|
132
106
|
s.request();
|
|
133
107
|
}
|
|
134
108
|
//#endregion
|
|
135
|
-
export { c as GridSnapController,
|
|
109
|
+
export { c as GridSnapController, d as requestGridSnap };
|
package/dist/index.d.ts
CHANGED
|
@@ -65,12 +65,12 @@ export { applyScale, ScaleController, effectiveScale, getScale, onScaleChange, s
|
|
|
65
65
|
*/
|
|
66
66
|
export { getZoom, truePixelRatio, onZoomChange, devicePxPerSystemPx, resetZoomBaseline, } from './zoom.js';
|
|
67
67
|
/**
|
|
68
|
-
* Automatic device-pixel-grid snapping.
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
68
|
+
* Automatic device-pixel-grid snapping. Always on: every component holds its
|
|
69
|
+
* own origin on whole device pixels whatever the surrounding layout does — the
|
|
70
|
+
* half of the layout contract the components keep for you. `requestGridSnap()`
|
|
71
|
+
* re-checks before the next paint, for a move nothing observable resized.
|
|
72
72
|
*/
|
|
73
|
-
export {
|
|
73
|
+
export { requestGridSnap, GridSnapController } from './grid-snap.js';
|
|
74
74
|
/**
|
|
75
75
|
* The page-drawn cursor. `applyCursor()` is the one call an app makes to
|
|
76
76
|
* replace the native pointer with the embedded System 7 set — arrow, I-beam,
|