vintage-frames 0.5.6 → 0.6.1
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/custom-elements.json +948 -46
- package/dist/components/vf-scroll-area.d.ts +19 -10
- package/dist/components/vf-scroll-area.js +39 -21
- package/dist/components/vf-window.d.ts +23 -10
- package/dist/components/vf-window.js +46 -14
- package/dist/position.d.ts +27 -8
- package/dist/position.js +74 -18
- package/dist/scroll-rail.d.ts +10 -0
- package/dist/scroll-rail.js +3 -0
- package/dist/styles/recipes/scroll-rail.js +8 -1
- package/docs/SPEC.md +14 -12
- package/editor/vscode.html-custom-data.json +158 -13
- package/editor/web-types.json +319 -25
- package/package.json +1 -1
|
@@ -22,9 +22,15 @@ declare const VfScrollArea_base: (new (...args: any[]) => import("../position.js
|
|
|
22
22
|
* and {@link corner} reserves that cell on a single-axis rail too — the
|
|
23
23
|
* rail stops 15px short of the frame, for a grow box to land in.
|
|
24
24
|
*
|
|
25
|
-
* Size the host (width/height) from the outside; the viewport fills it.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* Size the host (width/height) from the outside; the viewport fills it.
|
|
26
|
+
* Content runs to the frame and the rails — the area adds no inset of its
|
|
27
|
+
* own; an inset is the content's (a `vf-stack pad`).
|
|
28
|
+
*
|
|
29
|
+
* The scrolled plane sizes to its content — never narrower than the
|
|
30
|
+
* viewport, as wide as content that cannot wrap — so the rails follow a row
|
|
31
|
+
* that grows sideways the way they follow copy that grows down, and a
|
|
32
|
+
* `slotchange` re-measures. {@link measure} covers a scroll range that
|
|
33
|
+
* changes with no box changing (a placed child moved through `top`/`left`).
|
|
28
34
|
*
|
|
29
35
|
* @slot - Scrollable content.
|
|
30
36
|
* @csspart viewport - The inner scrolling container.
|
|
@@ -43,13 +49,6 @@ export declare class VfScrollArea extends VfScrollArea_base {
|
|
|
43
49
|
* natively (wheel, keyboard) but draws no rail.
|
|
44
50
|
*/
|
|
45
51
|
axis: 'vertical' | 'horizontal' | 'both';
|
|
46
|
-
/**
|
|
47
|
-
* Drop the viewport's 8px inset: slotted content and the (0,0) of placed
|
|
48
|
-
* children sit at the frame's inner edge, one system px from the frame
|
|
49
|
-
* box (the border-floor compensation stays — see the viewport rule).
|
|
50
|
-
* `vf-window[scrollbars]` forwards its own `flush` here.
|
|
51
|
-
*/
|
|
52
|
-
flush: boolean;
|
|
53
52
|
/**
|
|
54
53
|
* Reserve the bottom-right corner cell on a single-axis rail: the rail
|
|
55
54
|
* stops 15px short of the frame and the viewport spans the rest, leaving
|
|
@@ -83,6 +82,16 @@ export declare class VfScrollArea extends VfScrollArea_base {
|
|
|
83
82
|
* scripts silently no-opped on the host.
|
|
84
83
|
*/
|
|
85
84
|
focus(options?: FocusOptions): void;
|
|
85
|
+
/**
|
|
86
|
+
* Re-measure overflow and re-sync the rails. The area tracks its content's
|
|
87
|
+
* box and its slot by itself; call this for a scroll range that changes
|
|
88
|
+
* with no box changing anywhere — a placed child moved through
|
|
89
|
+
* `top`/`left`, a transform — the way the kit's own fields re-measure on
|
|
90
|
+
* input.
|
|
91
|
+
*/
|
|
92
|
+
measure(): void;
|
|
93
|
+
/** Content added or removed under the fixed box — re-measure. */
|
|
94
|
+
private _onSlotChange;
|
|
86
95
|
static styles: import("lit").CSSResult[];
|
|
87
96
|
protected render(): import("lit-html").TemplateResult<1>;
|
|
88
97
|
}
|
|
@@ -13,7 +13,7 @@ import { property as h, query as g, state as _ } from "lit/decorators.js";
|
|
|
13
13
|
//#region src/components/vf-scroll-area.ts
|
|
14
14
|
var v = class extends r(d) {
|
|
15
15
|
constructor(...e) {
|
|
16
|
-
super(...e), this.scale = new t(this), this.gridSnap = new s(this), this.axis = "vertical", this.
|
|
16
|
+
super(...e), this.scale = new t(this), this.gridSnap = new s(this), this.axis = "vertical", this.corner = !1, this.label = "", this.scrollState = new c(this, () => this.viewport, () => this.content, (e) => {
|
|
17
17
|
this._scrollable = e.x || e.y;
|
|
18
18
|
}), this.rail = new l(this, {
|
|
19
19
|
getScroll: () => this.viewport,
|
|
@@ -23,6 +23,12 @@ var v = class extends r(d) {
|
|
|
23
23
|
focus(e) {
|
|
24
24
|
this.viewport?.focus(e);
|
|
25
25
|
}
|
|
26
|
+
measure() {
|
|
27
|
+
this.scrollState.measure(), this.rail.sync();
|
|
28
|
+
}
|
|
29
|
+
_onSlotChange() {
|
|
30
|
+
this.measure();
|
|
31
|
+
}
|
|
26
32
|
static {
|
|
27
33
|
this.styles = [
|
|
28
34
|
i,
|
|
@@ -62,23 +68,15 @@ var v = class extends r(d) {
|
|
|
62
68
|
recipe (.vf-scroll) and the reservation is the rail element, not a
|
|
63
69
|
native gutter. The unreserved axis still scrolls, railless.
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
fractional frame border to whole CSS px, and the mod()
|
|
67
|
-
exactly what they floored away
|
|
68
|
-
of placed children) sits exactly
|
|
69
|
-
every scale
|
|
70
|
-
|
|
71
|
+
No inset of its own — only the border-floor compensation: engines
|
|
72
|
+
floor the fractional frame border to whole CSS px, and the mod()
|
|
73
|
+
term is exactly what they floored away, so slotted content (and
|
|
74
|
+
the (0,0) of placed children) sits exactly one system px from the
|
|
75
|
+
frame box at every scale (mod is 0 at whole scales). An inset is
|
|
76
|
+
the content's (a vf-stack pad). */
|
|
71
77
|
overflow: auto;
|
|
72
78
|
min-width: 0;
|
|
73
79
|
min-height: 0;
|
|
74
|
-
padding: calc(
|
|
75
|
-
var(--vf-scale, 1) * 8px + mod(var(--vf-scale, 1) * 1px, 1px)
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
/* flush: the inset goes, the border-floor term stays — content and the
|
|
79
|
-
(0,0) of placed children sit at the frame's inner edge, exactly one
|
|
80
|
-
system px from the frame box at every scale. */
|
|
81
|
-
:host([flush]) .viewport {
|
|
82
80
|
padding: mod(var(--vf-scale, 1) * 1px, 1px);
|
|
83
81
|
}
|
|
84
82
|
/* A single-axis rail with a reserved corner: the viewport spans the
|
|
@@ -105,10 +103,31 @@ var v = class extends r(d) {
|
|
|
105
103
|
(src/position.ts). It must be THIS wrapper and not .box: .content
|
|
106
104
|
rides the scroll, so positioned children travel with the content —
|
|
107
105
|
anchored to .box they would hang fixed over the rail while the plane
|
|
108
|
-
moved beneath them.
|
|
109
|
-
|
|
106
|
+
moved beneath them. (0,0) is where flow content starts.
|
|
107
|
+
|
|
108
|
+
Sized to its content: never narrower than the viewport, and as wide
|
|
109
|
+
as content that cannot wrap. A block's auto width is the viewport's
|
|
110
|
+
whatever the row inside does, so the controllers' ResizeObserver on
|
|
111
|
+
this box never saw horizontal growth — a row gaining a cell left the
|
|
112
|
+
thumb and the overflow state stale until something else re-measured.
|
|
113
|
+
fit-content is the shrink-to-fit width, not max-content: copy wraps
|
|
114
|
+
exactly as before, and only unwrappable width (a nowrap row, a
|
|
115
|
+
fixed-width box, a wide image) grows the plane — precisely the
|
|
116
|
+
content that overflows sideways. It also makes this box the
|
|
117
|
+
containing block of a sticky child, so a sticky left: 0 strip holds
|
|
118
|
+
across the scroll.
|
|
119
|
+
|
|
120
|
+
Never shorter than the viewport either: a fixed child (position.ts)
|
|
121
|
+
is a sticky box whose containing block is this plane, and a plane
|
|
122
|
+
shorter than the child's stated top clamps the child to the plane's
|
|
123
|
+
bottom edge (measured: top 100 over 50px of content landed at 50).
|
|
124
|
+
Content that fits still fits — the plane grows to the viewport, not
|
|
125
|
+
past it, so nothing overflows that didn't. */
|
|
110
126
|
.content {
|
|
111
127
|
position: relative;
|
|
128
|
+
width: fit-content;
|
|
129
|
+
min-width: 100%;
|
|
130
|
+
min-height: 100%;
|
|
112
131
|
}
|
|
113
132
|
/* Focusable so keyboard users can scroll; inset ring to stay in-box. */
|
|
114
133
|
.viewport:focus-visible {
|
|
@@ -129,7 +148,9 @@ var v = class extends r(d) {
|
|
|
129
148
|
role=${this.label ? "region" : this._scrollable ? "group" : m}
|
|
130
149
|
aria-label=${this.label || m}
|
|
131
150
|
>
|
|
132
|
-
<div class="content"
|
|
151
|
+
<div class="content">
|
|
152
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
153
|
+
</div>
|
|
133
154
|
</div>
|
|
134
155
|
${e ? u(this.rail, "vertical") : m}
|
|
135
156
|
${t ? u(this.rail, "horizontal") : m}
|
|
@@ -141,9 +162,6 @@ var v = class extends r(d) {
|
|
|
141
162
|
n([h({ reflect: !0 })], v.prototype, "axis", void 0), n([h({
|
|
142
163
|
type: Boolean,
|
|
143
164
|
reflect: !0
|
|
144
|
-
})], v.prototype, "flush", void 0), n([h({
|
|
145
|
-
type: Boolean,
|
|
146
|
-
reflect: !0
|
|
147
165
|
})], v.prototype, "corner", void 0), n([h()], v.prototype, "label", void 0), n([g(".viewport")], v.prototype, "viewport", void 0), n([g(".content")], v.prototype, "content", void 0), n([_()], v.prototype, "_scrollable", void 0), v = n([e("vf-scroll-area")], v);
|
|
148
166
|
//#endregion
|
|
149
167
|
export { v as VfScrollArea };
|
|
@@ -30,6 +30,13 @@ declare const VfWindow_base: (new (...args: any[]) => import("../size.js").VfSiz
|
|
|
30
30
|
* says so once in the console.
|
|
31
31
|
*
|
|
32
32
|
* @slot - Default slot: window body content.
|
|
33
|
+
* @slot header - Optional header content — a band between the title bar and
|
|
34
|
+
* the body, the full width of the window (the Finder window's header
|
|
35
|
+
* line): a white interior over a 1px rule, no inset of its own, a
|
|
36
|
+
* positioning anchor for placed children. As tall as its content unless
|
|
37
|
+
* `header-height` states it. Under `scrollbars` the vertical rail begins
|
|
38
|
+
* below it, so the header spans the rail's column. Takes no space until
|
|
39
|
+
* populated.
|
|
33
40
|
* @slot status - Optional status-bar content — the classic bottom readout
|
|
34
41
|
* strip ("40px x 40px"): a 1px rule over a 15px white band under the body,
|
|
35
42
|
* body-face text on its native line. Takes no space until populated; a
|
|
@@ -40,6 +47,8 @@ declare const VfWindow_base: (new (...args: any[]) => import("../size.js").VfSiz
|
|
|
40
47
|
* @csspart close-box - The close widget (left).
|
|
41
48
|
* @csspart zoom-box - The zoom widget (right).
|
|
42
49
|
* @csspart body - The content area.
|
|
50
|
+
* @csspart header - The header strip between the title bar and the body
|
|
51
|
+
* (when the `header` slot is populated).
|
|
43
52
|
* @csspart status-bar - The bottom status strip (when the `status` slot is
|
|
44
53
|
* populated).
|
|
45
54
|
* @csspart grow-box - The resize widget (bottom-right, when `resizable`).
|
|
@@ -120,13 +129,6 @@ export declare class VfWindow extends VfWindow_base {
|
|
|
120
129
|
maxWidth?: number | null;
|
|
121
130
|
/** See {@link minWidth}. Unbounded by default. */
|
|
122
131
|
maxHeight?: number | null;
|
|
123
|
-
/**
|
|
124
|
-
* Remove the default 12px body padding. Under `scrollbars` the body has
|
|
125
|
-
* none to remove, so the flag reaches the built-in scroll area instead
|
|
126
|
-
* and drops its viewport's 8px inset: content runs to the frame and the
|
|
127
|
-
* rails, and the (0,0) of placed children is the content region's corner.
|
|
128
|
-
*/
|
|
129
|
-
flush: boolean;
|
|
130
132
|
/**
|
|
131
133
|
* Put System 7 scroll rails on the window edge — the classic document
|
|
132
134
|
* window. The body slot renders inside a built-in `vf-scroll-area` pulled
|
|
@@ -136,11 +138,20 @@ export declare class VfWindow extends VfWindow_base {
|
|
|
136
138
|
* a populated status strip holds the grow box, when the rail runs edge to
|
|
137
139
|
* edge onto the strip's rule. Values mirror `vf-scroll-area`'s `axis`; the
|
|
138
140
|
* `heading` names the scroll region; the viewport part is re-exported.
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
141
|
+
* Content runs to the frame and the rails; an inset is the content's own.
|
|
142
|
+
* The slotted composition (SPEC §5 vf-scroll-area) still works for a well
|
|
143
|
+
* placed inside the body.
|
|
142
144
|
*/
|
|
143
145
|
scrollbars?: 'vertical' | 'horizontal' | 'both';
|
|
146
|
+
/**
|
|
147
|
+
* The header's height in whole system px, rule included — the way every
|
|
148
|
+
* kit bar counts its rule (an 18px title bar is 17 + 1). Unset, the header
|
|
149
|
+
* is as tall as what is slotted into it, plus the rule. Only matters while
|
|
150
|
+
* the `header` slot is populated.
|
|
151
|
+
*/
|
|
152
|
+
headerHeight?: number | null;
|
|
153
|
+
/** Whether the `header` slot has assigned content (drives the header). */
|
|
154
|
+
private _hasHeader;
|
|
144
155
|
/** Whether the `status` slot has assigned content (drives the strip). */
|
|
145
156
|
private _hasStatus;
|
|
146
157
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
@@ -197,6 +208,8 @@ export declare class VfWindow extends VfWindow_base {
|
|
|
197
208
|
disconnectedCallback(): void;
|
|
198
209
|
/** The `.empty` gate: the strip renders only while the slot is populated. */
|
|
199
210
|
private _onStatusSlotChange;
|
|
211
|
+
/** The header's own gate, on the same terms. */
|
|
212
|
+
private _onHeaderSlotChange;
|
|
200
213
|
private _onCloseClick;
|
|
201
214
|
private _onZoomClick;
|
|
202
215
|
private _onGrowPointerDown;
|
|
@@ -25,7 +25,7 @@ function V(e, t, n) {
|
|
|
25
25
|
}
|
|
26
26
|
var H = 24, U = 8, W = class extends E(c(N)) {
|
|
27
27
|
constructor(...e) {
|
|
28
|
-
super(...e), this.heading = "", this.active = !0, this.closable = !0, this.zoomable = !1, this.movable = !1, this.resizable = !1, this.
|
|
28
|
+
super(...e), this.heading = "", this.active = !0, this.closable = !0, this.zoomable = !1, this.movable = !1, this.resizable = !1, this._hasHeader = !1, this._hasStatus = !1, this.scale = new n(this), this.gridSnap = new T(this), this.titleCenter = new O(this), this._dotsPattern = "", this.#e = new x(), this.#t = (e, t, n) => {
|
|
29
29
|
let r = a(this.offsetWidth, this);
|
|
30
30
|
return {
|
|
31
31
|
x: Math.min(Math.max(e, H - r), n.width - H),
|
|
@@ -180,17 +180,13 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
180
180
|
/* The positioning anchor for slotted children placed with top/left
|
|
181
181
|
(src/position.ts): coordinates measure from the content region's
|
|
182
182
|
corner — the frame's inner edge, below the title bar — exactly the
|
|
183
|
-
DITL convention.
|
|
184
|
-
|
|
183
|
+
DITL convention. No inset of its own: the classic content region
|
|
184
|
+
had none, and flow content starts at the same corner a placed
|
|
185
|
+
child does. An inset is the content's (a vf-stack pad). */
|
|
185
186
|
position: relative;
|
|
186
187
|
flex: 1 1 auto;
|
|
187
188
|
min-height: 0;
|
|
188
189
|
overflow: hidden;
|
|
189
|
-
padding: calc(var(--vf-scale, 1) * 12px);
|
|
190
|
-
}
|
|
191
|
-
:host([flush]) .body,
|
|
192
|
-
:host([scrollbars]) .body {
|
|
193
|
-
padding: 0;
|
|
194
190
|
}
|
|
195
191
|
/* The edge-rail composition pulls the scroll area one system px OUT of
|
|
196
192
|
the body on every side so its frame border repaints the window's
|
|
@@ -208,13 +204,39 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
208
204
|
and a resizable window's grow box (z-index 1) lands over the rail
|
|
209
205
|
corner cell — which the render reserves on a single-axis rail too
|
|
210
206
|
(the area's corner flag), except when the status strip holds the
|
|
211
|
-
grow box instead.
|
|
207
|
+
grow box instead. The same overhang lands the area's top frame line
|
|
208
|
+
on the header's rule, so the vertical rail's top arrow begins
|
|
209
|
+
under the header. */
|
|
212
210
|
.edge-scroll {
|
|
213
211
|
width: calc(100% + var(--vf-scale, 1) * 2px);
|
|
214
212
|
height: calc(100% + var(--vf-scale, 1) * 2px);
|
|
215
213
|
margin: calc(var(--vf-scale, 1) * -1px);
|
|
216
214
|
}
|
|
217
215
|
|
|
216
|
+
/* --- Header (slot="header") --------------------------------------- */
|
|
217
|
+
/* A band between the title bar and the body, the full width of the
|
|
218
|
+
window — the Finder window's header line: a white interior over a
|
|
219
|
+
1px rule (vfRule's vf-rule-bottom on the element). Like the body it
|
|
220
|
+
carries no inset and is a positioning anchor: (0,0) is the header's
|
|
221
|
+
own corner, flow content starts there too, an inset is the content's.
|
|
222
|
+
As tall as its content unless header-height states it — rule
|
|
223
|
+
included, the way every kit bar counts its rule (an 18px title bar
|
|
224
|
+
is 17 + 1). Under the scrollbars parameter the edge rails sit in the
|
|
225
|
+
body below it, so the header spans the rail's column and the
|
|
226
|
+
vertical rail's arrows begin under it. Takes no space until the slot
|
|
227
|
+
is populated. Clipped like the body; a drop-open panel still escapes
|
|
228
|
+
(see the body's note). A div with a class, never a <header>: that
|
|
229
|
+
element maps to a banner landmark (see render()). */
|
|
230
|
+
.header {
|
|
231
|
+
position: relative;
|
|
232
|
+
flex: none;
|
|
233
|
+
background: var(--vf-white, #ffffff);
|
|
234
|
+
overflow: hidden;
|
|
235
|
+
}
|
|
236
|
+
.header.empty {
|
|
237
|
+
display: none;
|
|
238
|
+
}
|
|
239
|
+
|
|
218
240
|
/* --- Status bar (slot="status") ---------------------------------- */
|
|
219
241
|
/* The classic bottom readout strip: a 1px rule over a white interior,
|
|
220
242
|
15px in all — the grow box's own height, so a resizable window's
|
|
@@ -333,6 +355,10 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
333
355
|
let t = e.target;
|
|
334
356
|
this._hasStatus = t.assignedElements().length > 0;
|
|
335
357
|
}
|
|
358
|
+
_onHeaderSlotChange(e) {
|
|
359
|
+
let t = e.target;
|
|
360
|
+
this._hasHeader = t.assignedElements().length > 0;
|
|
361
|
+
}
|
|
336
362
|
_onCloseClick() {
|
|
337
363
|
e(this, "vf-close", { reason: "close" });
|
|
338
364
|
}
|
|
@@ -382,12 +408,18 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
382
408
|
<span class="vf-title" part="title" id="title">${this.heading}</span>
|
|
383
409
|
${this.zoomable ? M(j("Zoom", this.heading), this._onZoomClick) : I}
|
|
384
410
|
`, this.variant === "utility" ? "vf-dots" : "vf-stripes", this.variant === "utility" ? this._dotsTexture() : void 0)}
|
|
411
|
+
<div
|
|
412
|
+
class="header vf-rule-bottom ${this._hasHeader ? "" : "empty"}"
|
|
413
|
+
part="header"
|
|
414
|
+
style=${this.headerHeight == null ? I : `height:${i(this.headerHeight)}`}
|
|
415
|
+
>
|
|
416
|
+
<slot name="header" @slotchange=${this._onHeaderSlotChange}></slot>
|
|
417
|
+
</div>
|
|
385
418
|
<div class="body" part="body">
|
|
386
419
|
${this.scrollbars ? F`
|
|
387
420
|
<vf-scroll-area
|
|
388
421
|
class="edge-scroll"
|
|
389
422
|
axis=${this.scrollbars}
|
|
390
|
-
?flush=${this.flush}
|
|
391
423
|
?corner=${this.resizable && !this._hasStatus}
|
|
392
424
|
label=${this.heading || I}
|
|
393
425
|
exportparts="viewport"
|
|
@@ -441,9 +473,9 @@ o([L({ reflect: !0 })], W.prototype, "variant", void 0), o([L()], W.prototype, "
|
|
|
441
473
|
})], W.prototype, "maxWidth", void 0), o([L({
|
|
442
474
|
type: Number,
|
|
443
475
|
attribute: "max-height"
|
|
444
|
-
})], W.prototype, "maxHeight", void 0), o([L({
|
|
445
|
-
type:
|
|
446
|
-
|
|
447
|
-
})], W.prototype, "
|
|
476
|
+
})], W.prototype, "maxHeight", void 0), o([L({ reflect: !0 })], W.prototype, "scrollbars", void 0), o([L({
|
|
477
|
+
type: Number,
|
|
478
|
+
attribute: "header-height"
|
|
479
|
+
})], W.prototype, "headerHeight", void 0), o([R()], W.prototype, "_hasHeader", void 0), o([R()], W.prototype, "_hasStatus", void 0), W = o([t("vf-window")], W);
|
|
448
480
|
//#endregion
|
|
449
481
|
export { W as VfWindow };
|
package/dist/position.d.ts
CHANGED
|
@@ -9,7 +9,14 @@ type Constructor<T = object> = new (...args: any[]) => T;
|
|
|
9
9
|
export declare abstract class VfPositionedInterface extends LitElement {
|
|
10
10
|
top?: number | null;
|
|
11
11
|
left?: number | null;
|
|
12
|
+
fixed: boolean;
|
|
12
13
|
}
|
|
14
|
+
/** A host that takes the placement trio. */
|
|
15
|
+
type PositionedHost = HTMLElement & {
|
|
16
|
+
top?: number | null;
|
|
17
|
+
left?: number | null;
|
|
18
|
+
fixed?: boolean;
|
|
19
|
+
};
|
|
13
20
|
/**
|
|
14
21
|
* Explicit placement — `top`/`left` in whole system px, on **every** component.
|
|
15
22
|
*
|
|
@@ -76,6 +83,24 @@ export declare abstract class VfPositionedInterface extends LitElement {
|
|
|
76
83
|
* element. The controller re-applies **only when the values changed**, so an
|
|
77
84
|
* unrelated update — a heading change, a desktop toggling `active` — never
|
|
78
85
|
* re-asserts a coordinate and costs nothing.
|
|
86
|
+
*
|
|
87
|
+
* **`fixed`** holds the placement against the *visible* region of the nearest
|
|
88
|
+
* scrolling ancestor instead of its scrolled plane — a tool strip over a
|
|
89
|
+
* document, a column header over rows — and the flag alone places at (0,0).
|
|
90
|
+
* The engine underneath is `position: sticky`, the one way the platform holds
|
|
91
|
+
* a box against a scrollport from inside it on the compositor (a scroll
|
|
92
|
+
* listener counter-translating lags a frame; a layer outside the scroller
|
|
93
|
+
* stops wheel and touch over it). Sticky boxes stay in flow, which is not
|
|
94
|
+
* what a placement is, so the controller erases the footprint: the host is
|
|
95
|
+
* blockified (no line box), shrink-wrapped, and given a negative right/bottom
|
|
96
|
+
* margin equal to its own box — a 0×0 margin box that flow content lays out
|
|
97
|
+
* as if it weren't there, measured by a ResizeObserver so a relabel or a zoom
|
|
98
|
+
* step keeps it exact. `z-index: 1` puts it over the plane's placed children,
|
|
99
|
+
* which is where a fixed strip belongs. Two facts of the engine that the
|
|
100
|
+
* docs state as rules: sticky only ever pushes a box *down* from where the
|
|
101
|
+
* flow put it, so a fixed child comes before the flow content in its parent;
|
|
102
|
+
* and a scroll container that never scrolls (a plain window body is
|
|
103
|
+
* `overflow: hidden`) holds it exactly where placement would.
|
|
79
104
|
*/
|
|
80
105
|
export declare const VfPositioned: <T extends Constructor<LitElement>>(Base: T) => Constructor<VfPositionedInterface> & T;
|
|
81
106
|
/** The box a gesture is clamped into, in system px — see {@link PlacementClamp}. */
|
|
@@ -127,10 +152,7 @@ export type PlacementClamp = (x: number, y: number, bounds: PlacementBounds) =>
|
|
|
127
152
|
*/
|
|
128
153
|
export declare class PlacementController {
|
|
129
154
|
#private;
|
|
130
|
-
constructor(host:
|
|
131
|
-
top?: number | null;
|
|
132
|
-
left?: number | null;
|
|
133
|
-
}, clamp: PlacementClamp);
|
|
155
|
+
constructor(host: PositionedHost, clamp: PlacementClamp);
|
|
134
156
|
/** Whether a gesture has placed this host (as opposed to markup or CSS). */
|
|
135
157
|
get placed(): boolean;
|
|
136
158
|
/**
|
|
@@ -189,8 +211,5 @@ export declare class PlacementController {
|
|
|
189
211
|
* Returns whether it warned, so the caller can latch — one warning per element,
|
|
190
212
|
* not per render.
|
|
191
213
|
*/
|
|
192
|
-
export declare function warnMovableContract(host:
|
|
193
|
-
top?: number | null;
|
|
194
|
-
left?: number | null;
|
|
195
|
-
}, what: string, example: string): boolean;
|
|
214
|
+
export declare function warnMovableContract(host: PositionedHost, what: string, example: string): boolean;
|
|
196
215
|
export {};
|
package/dist/position.js
CHANGED
|
@@ -2,31 +2,83 @@ import { snapSys as e, sysLength as t, toSysExact as n } from "./scale.js";
|
|
|
2
2
|
import r from "./_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
|
|
3
3
|
import { property as i } from "lit/decorators.js";
|
|
4
4
|
//#region src/position.ts
|
|
5
|
-
var a =
|
|
5
|
+
var a = {
|
|
6
|
+
inline: "block",
|
|
7
|
+
"inline-block": "block",
|
|
8
|
+
"inline-flex": "flex",
|
|
9
|
+
"inline-grid": "grid",
|
|
10
|
+
"inline-table": "table"
|
|
11
|
+
}, o = (e) => {
|
|
6
12
|
class t extends e {
|
|
7
13
|
constructor(...e) {
|
|
8
|
-
super(...e), new
|
|
14
|
+
super(...e), this.fixed = !1, new s(this);
|
|
9
15
|
}
|
|
10
16
|
}
|
|
11
|
-
return r([i({ type: Number })], t.prototype, "top", void 0), r([i({ type: Number })], t.prototype, "left", void 0),
|
|
12
|
-
|
|
17
|
+
return r([i({ type: Number })], t.prototype, "top", void 0), r([i({ type: Number })], t.prototype, "left", void 0), r([i({
|
|
18
|
+
type: Boolean,
|
|
19
|
+
reflect: !0
|
|
20
|
+
})], t.prototype, "fixed", void 0), t;
|
|
21
|
+
}, s = class {
|
|
13
22
|
#e;
|
|
14
23
|
#t = null;
|
|
15
24
|
#n = null;
|
|
16
25
|
#r = !1;
|
|
26
|
+
#i = !1;
|
|
27
|
+
#a = null;
|
|
17
28
|
constructor(e) {
|
|
18
29
|
this.#e = e, e.addController(this);
|
|
19
30
|
}
|
|
31
|
+
hostConnected() {
|
|
32
|
+
this.#i && this.#r && this.#c();
|
|
33
|
+
}
|
|
34
|
+
hostDisconnected() {
|
|
35
|
+
this.#a?.disconnect(), this.#a = null;
|
|
36
|
+
}
|
|
20
37
|
hostUpdated() {
|
|
21
|
-
let e = this.#e.top ?? null, n = this.#e.left ?? null, r = this.#e.style;
|
|
22
|
-
if (e === null && n === null) {
|
|
23
|
-
if (!this.#
|
|
24
|
-
this.#r = !1, r.removeProperty("position"),
|
|
38
|
+
let e = this.#e.top ?? null, n = this.#e.left ?? null, r = this.#e.fixed === !0, i = this.#e.style;
|
|
39
|
+
if (e === null && n === null && !r) {
|
|
40
|
+
if (!this.#i) return;
|
|
41
|
+
this.#r && this.#s(), this.#i = !1, this.#r = !1, i.removeProperty("position"), i.removeProperty("top"), i.removeProperty("left"), i.removeProperty("right"), i.removeProperty("bottom"), i.removeProperty("margin");
|
|
25
42
|
return;
|
|
26
43
|
}
|
|
27
|
-
this.#
|
|
44
|
+
if (this.#i && e === this.#t && n === this.#n && r === this.#r) return;
|
|
45
|
+
let a = this.#i && this.#r;
|
|
46
|
+
this.#i = !0, this.#t = e, this.#n = n, this.#r = r, i.position = r ? "sticky" : "absolute", i.top = t(e ?? 0), i.left = t(n ?? 0), i.right = "auto", i.bottom = "auto", r ? a || this.#o() : (a && this.#s(), i.margin = "0");
|
|
28
47
|
}
|
|
29
|
-
|
|
48
|
+
#o() {
|
|
49
|
+
let e = this.#e.style;
|
|
50
|
+
e.maxWidth = "fit-content", e.zIndex = "1", this.#l = !1;
|
|
51
|
+
let t = this.#e.getBoundingClientRect();
|
|
52
|
+
(t.width > 0 || t.height > 0) && this.#u(t.width, t.height), this.#c();
|
|
53
|
+
}
|
|
54
|
+
#s() {
|
|
55
|
+
this.#a?.disconnect(), this.#a = null, this.#l = !1;
|
|
56
|
+
let e = this.#e.style;
|
|
57
|
+
e.removeProperty("display"), e.removeProperty("max-width"), e.removeProperty("z-index");
|
|
58
|
+
}
|
|
59
|
+
#c() {
|
|
60
|
+
this.#a ??= new ResizeObserver((e) => {
|
|
61
|
+
let t = e[e.length - 1]?.borderBoxSize?.[0];
|
|
62
|
+
if (t) this.#u(t.inlineSize, t.blockSize);
|
|
63
|
+
else {
|
|
64
|
+
let e = this.#e.getBoundingClientRect();
|
|
65
|
+
this.#u(e.width, e.height);
|
|
66
|
+
}
|
|
67
|
+
}), this.#a.observe(this.#e);
|
|
68
|
+
}
|
|
69
|
+
#l = !1;
|
|
70
|
+
#u(e, t) {
|
|
71
|
+
let n = this.#e.style;
|
|
72
|
+
if (!this.#l) {
|
|
73
|
+
let e = getComputedStyle(this.#e).display;
|
|
74
|
+
if (e) {
|
|
75
|
+
let t = a[e];
|
|
76
|
+
t && (n.display = t), this.#l = !0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
n.margin = `0 ${-e}px ${-t}px 0`;
|
|
80
|
+
}
|
|
81
|
+
}, c = class {
|
|
30
82
|
#e;
|
|
31
83
|
#t;
|
|
32
84
|
#n = !1;
|
|
@@ -43,7 +95,7 @@ var a = (e) => {
|
|
|
43
95
|
x: t.left ?? 0,
|
|
44
96
|
y: t.top ?? 0
|
|
45
97
|
};
|
|
46
|
-
let r = getComputedStyle(t), i =
|
|
98
|
+
let r = getComputedStyle(t), i = l.has(r.position), a = i ? parseFloat(r.left) || 0 : t.offsetLeft, o = i ? parseFloat(r.top) || 0 : t.offsetTop;
|
|
47
99
|
return {
|
|
48
100
|
x: e(n(a, t), t),
|
|
49
101
|
y: e(n(o, t), t)
|
|
@@ -60,15 +112,19 @@ var a = (e) => {
|
|
|
60
112
|
height: n(r?.clientHeight ?? window.innerHeight, e)
|
|
61
113
|
};
|
|
62
114
|
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
115
|
+
}, l = /* @__PURE__ */ new Set([
|
|
116
|
+
"absolute",
|
|
117
|
+
"fixed",
|
|
118
|
+
"sticky"
|
|
119
|
+
]);
|
|
120
|
+
function u(e, t, n) {
|
|
121
|
+
let r = e.top != null || e.left != null || e.fixed === !0, i = l.has(getComputedStyle(e).position), a = null;
|
|
122
|
+
if (!r && !i) a = "unplaced";
|
|
67
123
|
else {
|
|
68
124
|
let t = e.offsetParent;
|
|
69
|
-
t && t !== e.ownerDocument.body && (t.clientWidth === 0 || t.clientHeight === 0) && (
|
|
125
|
+
t && t !== e.ownerDocument.body && (t.clientWidth === 0 || t.clientHeight === 0) && (a = "unsized-parent");
|
|
70
126
|
}
|
|
71
|
-
return
|
|
127
|
+
return a !== null && (console.warn(a === "unplaced" ? `${t}: movable, but in normal flow — so the first drag has to pull it out, reflowing everything after it on the page. State the origin in system px and it never was in flow: ${n}` : `${t}: the positioning parent has no box, so there is nowhere to drag to. Give it a size (and \`position: relative\`, if it is not a kit container) — the clamp is falling back to the viewport meanwhile.`), !0);
|
|
72
128
|
}
|
|
73
129
|
//#endregion
|
|
74
|
-
export {
|
|
130
|
+
export { c as PlacementController, o as VfPositioned, u as warnMovableContract };
|
package/dist/scroll-rail.d.ts
CHANGED
|
@@ -64,6 +64,16 @@ export declare class ScrollRailController implements ReactiveController {
|
|
|
64
64
|
constructor(host: ReactiveControllerHost & HTMLElement & {
|
|
65
65
|
readonly renderRoot: HTMLElement | DocumentFragment;
|
|
66
66
|
}, opts: ScrollRailOptions);
|
|
67
|
+
/**
|
|
68
|
+
* Wire on connect as well as on update. A host the desktop re-inserts (its
|
|
69
|
+
* DOM-order sync moves a raised window, src/components/vf-desktop.ts) is
|
|
70
|
+
* disconnected and reconnected with no update scheduled — Lit's
|
|
71
|
+
* `enableUpdating` is a no-op after the first — so `hostUpdated` alone
|
|
72
|
+
* left the rail with no scroll listener, no observers, and whatever
|
|
73
|
+
* degenerate state it wrote while hidden. Both calls no-op before the
|
|
74
|
+
* first render, when the scroller query is still null.
|
|
75
|
+
*/
|
|
76
|
+
hostConnected(): void;
|
|
67
77
|
hostUpdated(): void;
|
|
68
78
|
hostDisconnected(): void;
|
|
69
79
|
/** (Re-)attach the scroll listener and observers to the current scroller. */
|
package/dist/scroll-rail.js
CHANGED
|
@@ -77,6 +77,9 @@ var h = 16, g = 16, _ = 14, v = 66, y = class {
|
|
|
77
77
|
!t || e.pointerId !== t.pointerId || (this.press = null, this.clearRepeat(), t.button?.removeAttribute("data-pressed"), t.rail.hasPointerCapture(e.pointerId) && t.rail.releasePointerCapture(e.pointerId));
|
|
78
78
|
}, t.addController(this);
|
|
79
79
|
}
|
|
80
|
+
hostConnected() {
|
|
81
|
+
this.wire(), this.sync();
|
|
82
|
+
}
|
|
80
83
|
hostUpdated() {
|
|
81
84
|
this.wire(), this.sync();
|
|
82
85
|
}
|
|
@@ -5,9 +5,16 @@ import { css as a, unsafeCSS as o } from "lit";
|
|
|
5
5
|
var s = i(r["gray-25"]), c = s.width, l = s.height, u = s.rects, d = e(c, l, t(u)), f = a`
|
|
6
6
|
/* Hide the native bar, keep the native scrolling. Both lines ship:
|
|
7
7
|
scrollbar-width reaches Chromium 121+/Firefox/Safari 18.2+, the pseudo
|
|
8
|
-
covers older WebKit — the last scrollbar pseudo left in the kit.
|
|
8
|
+
covers older WebKit — the last scrollbar pseudo left in the kit.
|
|
9
|
+
|
|
10
|
+
No rubber-band: a bounce moves content the rail cannot express (the
|
|
11
|
+
thumb is clamped to its track), and 1-bit has no smooth motion — System
|
|
12
|
+
7 stopped hard. none rather than contain, which keeps the bounce. The
|
|
13
|
+
cost is scroll chaining, which a page that scrolls restores through the
|
|
14
|
+
scroller's part (the reference page does — SPEC §4 vfScrollRail). */
|
|
9
15
|
.vf-scroll {
|
|
10
16
|
scrollbar-width: none;
|
|
17
|
+
overscroll-behavior: none;
|
|
11
18
|
}
|
|
12
19
|
.vf-scroll::-webkit-scrollbar {
|
|
13
20
|
display: none;
|