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.
@@ -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. The
26
- * viewport insets its content 8px; {@link flush} drops that inset so content
27
- * runs to the frame and the rails.
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.flush = !1, this.corner = !1, this.label = "", this.scrollState = new c(this, () => this.viewport, () => this.content, (e) => {
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
- 8px inset plus border-floor compensation: engines floor the
66
- fractional frame border to whole CSS px, and the mod() term is
67
- exactly what they floored away so slotted content (and the (0,0)
68
- of placed children) sits exactly 9 system px from the frame box at
69
- every scale, as it did under the borderless-scroller construction.
70
- (mod is 0 at whole scales.) */
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. Inside the viewport's 8px padding, so (0,0) is
109
- where flow content starts. */
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"><slot></slot></div>
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
- * The body's own padding goes; the viewport keeps its 8px inset unless
140
- * `flush`, which passes through. The slotted composition (SPEC §5
141
- * vf-scroll-area) still works for windows that want an inset well instead.
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.flush = !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) => {
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. CSS anchors absolute children at the padding box,
184
- so the 12px inset below governs flow content only. */
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: Boolean,
446
- reflect: !0
447
- })], W.prototype, "flush", void 0), o([L({ reflect: !0 })], W.prototype, "scrollbars", void 0), o([R()], W.prototype, "_hasStatus", void 0), W = o([t("vf-window")], W);
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 };
@@ -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: HTMLElement & {
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: HTMLElement & {
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 = (e) => {
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 o(this);
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), t;
12
- }, o = class {
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.#r) return;
24
- this.#r = !1, r.removeProperty("position"), r.removeProperty("top"), r.removeProperty("left"), r.removeProperty("right"), r.removeProperty("bottom"), r.removeProperty("margin");
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.#r && e === this.#t && n === this.#n || (this.#r = !0, this.#t = e, this.#n = n, r.position = "absolute", r.top = t(e ?? 0), r.left = t(n ?? 0), r.right = "auto", r.bottom = "auto", r.margin = "0");
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
- }, s = class {
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 = r.position === "absolute" || r.position === "fixed", a = i ? parseFloat(r.left) || 0 : t.offsetLeft, o = i ? parseFloat(r.top) || 0 : t.offsetTop;
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
- function c(e, t, n) {
65
- let r = e.top != null || e.left != null, i = getComputedStyle(e).position, a = i === "absolute" || i === "fixed", o = null;
66
- if (!r && !a) o = "unplaced";
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) && (o = "unsized-parent");
125
+ t && t !== e.ownerDocument.body && (t.clientWidth === 0 || t.clientHeight === 0) && (a = "unsized-parent");
70
126
  }
71
- return o !== null && (console.warn(o === "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);
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 { s as PlacementController, a as VfPositioned, c as warnMovableContract };
130
+ export { c as PlacementController, o as VfPositioned, u as warnMovableContract };
@@ -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. */
@@ -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;