vintage-frames 0.6.2 → 0.7.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.
@@ -55,6 +55,20 @@ declare const VfDesktop_base: (new (...args: any[]) => import("../position.js").
55
55
  * paper, one whole-surface raster at one image px per system px, 1-bit at
56
56
  * every density and zoom (src/pattern-fill.ts).
57
57
  *
58
+ * **The drag surface.** The Finder dragged an icon as a dotted outline drawn
59
+ * on the *screen*, clipped at its edge and phase-locked to its raster, so
60
+ * the screen hosts the layer that outline draws on: a full-bleed child of
61
+ * the screen, `pointer-events: none` (never a hit for the page's
62
+ * `elementsFromPoint`), whose children paint one tier above the menu bar
63
+ * and under the corner mask — the layer itself is deliberately no stacking
64
+ * context, since a blend inside one would composite against nothing. An
65
+ * icon reaches it without an import, the menu-handshake idiom: on a drag's
66
+ * first step it dispatches `vf-drag-surface-request` (bubbles, non-composed,
67
+ * `detail.surface` null), and the nearest desktop on its light-DOM path
68
+ * fills the detail with the layer. Not the top layer: stable Safari cannot
69
+ * blend an element there against the page (src/cursor.ts), and a second
70
+ * kit entry in the top layer would need a re-promotion handshake.
71
+ *
58
72
  * Custom properties:
59
73
  * - `--vf-desktop-pattern` — a consumer's own tile art in place of the
60
74
  * pattern (the kit's default is a 1-bit 50% checker, opaque
@@ -137,6 +151,17 @@ export declare class VfDesktop extends VfDesktop_base {
137
151
  width: number;
138
152
  height: number;
139
153
  };
154
+ /**
155
+ * A viewport point (CSS px) as a placement on the screen — `{ left, top }`
156
+ * in whole system px on the placement lattice, measured from the raster's
157
+ * corner with the bezel excluded: the pair a child dropped onto the desktop
158
+ * is written with. The screen sits in the shadow tree, which is why the
159
+ * conversion is a method here.
160
+ */
161
+ placementAt(clientX: number, clientY: number): {
162
+ left: number;
163
+ top: number;
164
+ };
140
165
  /** Slotted `vf-window` children (direct children only). */
141
166
  private _windows;
142
167
  /** Default-on display scaling (true 72dpi size); see src/scale.ts. */
@@ -154,6 +179,8 @@ export declare class VfDesktop extends VfDesktop_base {
154
179
  private _desktopPattern;
155
180
  /** The screen surface the pattern paints on; exists from the first render. */
156
181
  private readonly screen;
182
+ /** The layer a dragged icon's outline draws on; exists from the first render. */
183
+ private readonly dragSurface;
157
184
  /**
158
185
  * The desktop pattern, painted as the screen's own background
159
186
  * (src/pattern-fill.ts) from the declared raster — scale-independent, so
@@ -278,6 +305,13 @@ export declare class VfDesktop extends VfDesktop_base {
278
305
  * change z but never move nodes — see {@link _raise}).
279
306
  */
280
307
  private _onGestureEnd;
308
+ /**
309
+ * A dragged icon asking for the screen's drag surface (see the class
310
+ * doc). Non-composed, so it reaches here only through the light-DOM path
311
+ * the icon is slotted on — a window body included. The nearest desktop
312
+ * answers; an outer one leaves a filled detail alone.
313
+ */
314
+ private _onDragSurfaceRequest;
281
315
  /**
282
316
  * Delegated focusin: raise the window keyboard focus entered, so tabbing
283
317
  * into a background window brings it to front (and reveals its close/zoom
@@ -2,28 +2,28 @@ import { emit as e } from "../events.js";
2
2
  import { vfElement as t } from "../define.js";
3
3
  import { ScaleController as n, effectiveScale as r, sysLength as i } from "../scale.js";
4
4
  import a from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
5
- import { VfPositioned as o } from "../position.js";
6
- import { vfBase as s } from "../styles/recipes/host.js";
7
- import { tileImage as c, tileRects as l, tileSpan as u } from "../styles/recipes/tile.js";
8
- import { PATTERNS as d, parsePattern as f, patternMotif as p } from "../patterns.js";
9
- import { patternOverride as m, tileGrid as h, vfTileGrid as g } from "../tile-grid.js";
10
- import { PatternFillController as _, vfPatternFill as v } from "../pattern-fill.js";
11
- import { GridSnapController as y } from "../grid-snap.js";
12
- import { DocumentListenersController as b } from "../document-listeners.js";
13
- import { SCREEN_CORNER as x, steppedCornerClip as S } from "../pixel-frame.js";
14
- import { LitElement as C, css as w, html as T, unsafeCSS as E } from "lit";
15
- import { property as D, query as O, queryAssignedElements as k } from "lit/decorators.js";
5
+ import { VfPositioned as o, placementIn as s } from "../position.js";
6
+ import { vfBase as c } from "../styles/recipes/host.js";
7
+ import { tileImage as l, tileRects as u, tileSpan as d } from "../styles/recipes/tile.js";
8
+ import { PATTERNS as f, parsePattern as p, patternMotif as m } from "../patterns.js";
9
+ import { patternOverride as h, tileGrid as g, vfTileGrid as _ } from "../tile-grid.js";
10
+ import { PatternFillController as v, vfPatternFill as y } from "../pattern-fill.js";
11
+ import { GridSnapController as b } from "../grid-snap.js";
12
+ import { DocumentListenersController as x } from "../document-listeners.js";
13
+ import { SCREEN_CORNER as S, steppedCornerClip as C } from "../pixel-frame.js";
14
+ import { LitElement as w, css as T, html as E, unsafeCSS as D } from "lit";
15
+ import { property as O, query as k, queryAssignedElements as A } from "lit/decorators.js";
16
16
  //#region src/components/vf-desktop.ts
17
- var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000", "#ffffff"), I = c(F.width, F.height, l(F.rects)), L = u(F.width), R = class extends o(C) {
17
+ var j = 1e6, M = 2 * j, N = 2000001, P = 512, F = 342, I = "gray-50", L = m(f[I], "#000000", "#ffffff"), R = l(L.width, L.height, u(L.rects)), z = d(L.width), B = class extends o(w) {
18
18
  constructor(...e) {
19
- super(...e), this.width = M, this.height = N, this.bezel = 0, this.pattern = P, this.scale = new n(this), this.gridSnap = new y(this), this._token = "", this._desktopPattern = d[P], this.#e = !1, this.patternFill = new _(this, {
19
+ super(...e), this.width = P, this.height = F, this.bezel = 0, this.pattern = I, this.scale = new n(this), this.gridSnap = new b(this), this._token = "", this._desktopPattern = f[I], this.#e = !1, this.patternFill = new v(this, {
20
20
  getBox: () => this.screen,
21
21
  getPattern: () => this._token ? null : this._desktopPattern,
22
22
  getSize: () => ({
23
- width: this.width ?? M,
24
- height: this.height ?? N
23
+ width: this.width ?? P,
24
+ height: this.height ?? F
25
25
  })
26
- }), this._zCounter = 0, this._activeWindow = null, this._deactivated = !1, this._awaitingUpgrade = !1, this._pointerGesture = !1, this._restoringFocus = !1, this._domSyncTimer = 0, this._gestureEnd = new b(this, () => [[
26
+ }), this._zCounter = 0, this._activeWindow = null, this._deactivated = !1, this._awaitingUpgrade = !1, this._pointerGesture = !1, this._restoringFocus = !1, this._domSyncTimer = 0, this._gestureEnd = new x(this, () => [[
27
27
  document,
28
28
  "pointerup",
29
29
  this._onGestureEnd,
@@ -39,6 +39,9 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
39
39
  t && this._raise(t);
40
40
  }, this._onGestureEnd = () => {
41
41
  this._gestureEnd.detach(), this._pointerGesture = !1, this._requestDomSync();
42
+ }, this._onDragSurfaceRequest = (e) => {
43
+ let t = e.detail;
44
+ !t || t.surface || (t.surface = this.dragSurface);
42
45
  }, this._onFocusIn = (e) => {
43
46
  if (this._restoringFocus) return;
44
47
  let t = this._windowFromEvent(e);
@@ -47,10 +50,10 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
47
50
  }
48
51
  static {
49
52
  this.styles = [
50
- s,
51
- g,
52
- v,
53
- w`
53
+ c,
54
+ _,
55
+ y,
56
+ T`
54
57
  :host {
55
58
  display: block;
56
59
  position: relative;
@@ -112,7 +115,7 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
112
115
  position: absolute;
113
116
  inset: 0;
114
117
  z-index: -1;
115
- --_vf-tile-image: var(--vf-desktop-pattern, ${E(I)});
118
+ --_vf-tile-image: var(--vf-desktop-pattern, ${D(R)});
116
119
  /* Forced colors: hidden with the pattern, same posture. */
117
120
  @media (forced-colors: active) {
118
121
  display: none;
@@ -144,7 +147,23 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
144
147
  ::slotted(vf-menu-bar),
145
148
  ::slotted(vf-menu) {
146
149
  position: relative;
147
- z-index: ${j};
150
+ z-index: ${M};
151
+ }
152
+ /* The drag surface (see the class doc): the box an icon's outline is
153
+ positioned in — the screen's own, bezel excluded — clipped with
154
+ everything else at the raster's edge, and never a hit. No z-index
155
+ and no position: relative + z-index of its own: a stacking context
156
+ is an isolated group, and the outline's mix-blend-mode would then
157
+ composite against the empty layer instead of the windows and dither
158
+ beneath. The z-index goes on the outline itself, which is what puts
159
+ it on the tier above the menu bar within .screen's own context. */
160
+ .drag-surface {
161
+ position: absolute;
162
+ inset: 0;
163
+ pointer-events: none;
164
+ }
165
+ .drag-surface > * {
166
+ z-index: ${N};
148
167
  }
149
168
  /* With a bezel, the screen's top corners wear the SCREEN_CORNER mask,
150
169
  rounding into the surrounding black — the top pair only, because the
@@ -156,19 +175,19 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
156
175
  .corner {
157
176
  position: absolute;
158
177
  top: 0;
159
- width: calc(var(--vf-scale, 1) * ${x[0]}px);
160
- height: calc(var(--vf-scale, 1) * ${x.length}px);
178
+ width: calc(var(--vf-scale, 1) * ${S[0]}px);
179
+ height: calc(var(--vf-scale, 1) * ${S.length}px);
161
180
  background: var(--vf-black, #000);
162
181
  pointer-events: none;
163
182
  z-index: 2147483647;
164
183
  }
165
184
  .corner.tl {
166
185
  left: 0;
167
- clip-path: ${E(S(x, "left"))};
186
+ clip-path: ${D(C(S, "left"))};
168
187
  }
169
188
  .corner.tr {
170
189
  right: 0;
171
- clip-path: ${E(S(x, "right"))};
190
+ clip-path: ${D(C(S, "right"))};
172
191
  }
173
192
  `
174
193
  ];
@@ -180,12 +199,15 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
180
199
  height: this.height
181
200
  };
182
201
  }
202
+ placementAt(e, t) {
203
+ return s(this.screen ?? this, e, t, this);
204
+ }
183
205
  #e;
184
206
  connectedCallback() {
185
- super.connectedCallback(), this.addEventListener("pointerdown", this._onPointerDown), this.addEventListener("focusin", this._onFocusIn);
207
+ super.connectedCallback(), this.addEventListener("pointerdown", this._onPointerDown), this.addEventListener("focusin", this._onFocusIn), this.addEventListener("vf-drag-surface-request", this._onDragSurfaceRequest);
186
208
  }
187
209
  disconnectedCallback() {
188
- this.removeEventListener("pointerdown", this._onPointerDown), this.removeEventListener("focusin", this._onFocusIn), this._pointerGesture = !1, this._domSyncTimer &&= (clearTimeout(this._domSyncTimer), 0), super.disconnectedCallback();
210
+ this.removeEventListener("pointerdown", this._onPointerDown), this.removeEventListener("focusin", this._onFocusIn), this.removeEventListener("vf-drag-surface-request", this._onDragSurfaceRequest), this._pointerGesture = !1, this._domSyncTimer &&= (clearTimeout(this._domSyncTimer), 0), super.disconnectedCallback();
189
211
  }
190
212
  _isUtility(e) {
191
213
  return e.getAttribute("variant") === "utility";
@@ -201,7 +223,7 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
201
223
  }
202
224
  _restack(e) {
203
225
  let t = this._isUtility(e);
204
- e.style.zIndex = String(++this._zCounter + (t ? A : 0)), t || this._setActive(e);
226
+ e.style.zIndex = String(++this._zCounter + (t ? j : 0)), t || this._setActive(e);
205
227
  }
206
228
  _requestDomSync() {
207
229
  this._pointerGesture || this._domSyncTimer || (this._domSyncTimer = window.setTimeout(() => {
@@ -220,7 +242,7 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
220
242
  let e = this._windows, t = null;
221
243
  for (let n of e) if (!n.style.zIndex) {
222
244
  let e = this._isUtility(n);
223
- n.style.zIndex = String(++this._zCounter + (e ? A : 0)), e || (t = n);
245
+ n.style.zIndex = String(++this._zCounter + (e ? j : 0)), e || (t = n);
224
246
  }
225
247
  let n = e.filter((e) => !this._isUtility(e)), r = this._activeWindow && n.includes(this._activeWindow) ? this._activeWindow : null;
226
248
  this._setActive(t ?? r ?? (this._deactivated ? null : this._topmost(n))), customElements.get("vf-window") || this._normalizeAfterUpgrade();
@@ -265,26 +287,26 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
265
287
  r && n instanceof HTMLElement && n.isConnected && this._deepActiveElement() !== n && (this._restoringFocus = !0, n.focus({ preventScroll: !0 }), this._restoringFocus = !1);
266
288
  }
267
289
  willUpdate(e) {
268
- if (super.willUpdate(e), this._token = m(this, "--vf-desktop-pattern"), e.has("pattern")) {
269
- let e = f(this.pattern);
270
- e === null && this.pattern?.trim() && !this.#e && (this.#e = !0, console.warn(`vf-desktop: unknown pattern "${this.pattern}" — a library name (docs/PATTERNS.md) or sixteen hex digits. Keeping ${P}.`)), this._desktopPattern = e ?? d[P];
290
+ if (super.willUpdate(e), this._token = h(this, "--vf-desktop-pattern"), e.has("pattern")) {
291
+ let e = p(this.pattern);
292
+ e === null && this.pattern?.trim() && !this.#e && (this.#e = !0, console.warn(`vf-desktop: unknown pattern "${this.pattern}" — a library name (docs/PATTERNS.md) or sixteen hex digits. Keeping ${I}.`)), this._desktopPattern = e ?? f[I];
271
293
  }
272
294
  }
273
295
  updated(e) {
274
296
  if (e.has("bezel") && (this.bezel > 0 ? this.style.setProperty("--vf-desktop-bezel", i(this.bezel)) : this.style.removeProperty("--vf-desktop-bezel")), e.has("width") || e.has("height") || e.has("bezel")) {
275
- let e = this.width ?? M, t = this.height ?? N;
297
+ let e = this.width ?? P, t = this.height ?? F;
276
298
  this.style.width = i(e + 2 * this.bezel), this.style.height = i(t + 2 * this.bezel);
277
299
  }
278
300
  }
279
301
  render() {
280
- let e = this.width ?? M, t = this.height ?? N, n = this._token ? T`<div class="vf-tile-grid">
281
- ${h({
282
- cols: Math.ceil(e / L),
283
- rows: Math.ceil(t / L),
284
- tile: L
302
+ let e = this.width ?? P, t = this.height ?? F, n = this._token ? E`<div class="vf-tile-grid">
303
+ ${g({
304
+ cols: Math.ceil(e / z),
305
+ rows: Math.ceil(t / z),
306
+ tile: z
285
307
  })}
286
308
  </div>` : null;
287
- return T`
309
+ return E`
288
310
  <div class=${this.bezel > 0 ? "desktop vf-snap bezeled" : "desktop vf-snap"}>
289
311
  <div
290
312
  class="screen vf-pattern-fill${this._token ? "" : " vf-patterned"}"
@@ -292,13 +314,14 @@ var A = 1e6, j = 2 * A, M = 512, N = 342, P = "gray-50", F = p(d[P], "#000000",
292
314
  >
293
315
  ${n}
294
316
  <slot @slotchange=${this._onSlotChange}></slot>
295
- ${this.bezel > 0 ? T`<div class="corner tl"></div>
317
+ <div class="drag-surface"></div>
318
+ ${this.bezel > 0 ? E`<div class="corner tl"></div>
296
319
  <div class="corner tr"></div>` : null}
297
320
  </div>
298
321
  </div>
299
322
  `;
300
323
  }
301
324
  };
302
- a([D({ type: Number })], R.prototype, "width", void 0), a([D({ type: Number })], R.prototype, "height", void 0), a([D({ type: Number })], R.prototype, "bezel", void 0), a([D()], R.prototype, "pattern", void 0), a([k({ selector: "vf-window" })], R.prototype, "_windows", void 0), a([O(".screen")], R.prototype, "screen", void 0), R = a([t("vf-desktop")], R);
325
+ a([O({ type: Number })], B.prototype, "width", void 0), a([O({ type: Number })], B.prototype, "height", void 0), a([O({ type: Number })], B.prototype, "bezel", void 0), a([O()], B.prototype, "pattern", void 0), a([A({ selector: "vf-window" })], B.prototype, "_windows", void 0), a([k(".screen")], B.prototype, "screen", void 0), a([k(".drag-surface")], B.prototype, "dragSurface", void 0), B = a([t("vf-desktop")], B);
303
326
  //#endregion
304
- export { R as VfDesktop };
327
+ export { B as VfDesktop };
@@ -0,0 +1,144 @@
1
+ import { LitElement, type PropertyValues } from 'lit';
2
+ import type { VfIconSize } from './vf-icon.js';
3
+ declare const VfIconField_base: (new (...args: any[]) => import("../size.js").VfSizedInterface) & (new (...args: any[]) => import("../position.js").VfPositionedInterface) & typeof LitElement;
4
+ /**
5
+ * `<vf-icon-field>` — the container a field of `vf-icon`s sits in: a desktop's
6
+ * icons, a folder window's contents.
7
+ *
8
+ * Every other set in the kit has its container — `vf-list` owns
9
+ * `vf-list-item`, `vf-radio-group` owns `vf-radio`, `vf-menu` owns
10
+ * `vf-menu-item`, `vf-select` owns `vf-option` — and `vf-icon` was the one
11
+ * item whose container the consumer wrote by hand: a bare
12
+ * `<div role="listbox" aria-multiselectable="true">`, because an `option` is
13
+ * only valid inside a `listbox` that owns it, and every kit container is a
14
+ * positioning anchor by design, so nothing in the kit was neutral enough to
15
+ * stand in. A consumer who forgot the role got icons that silently degraded
16
+ * to pictures. This is that container, as kit furniture:
17
+ *
18
+ * ```html
19
+ * <vf-desktop width="512" height="342">
20
+ * <vf-icon-field label="Desktop" fill-width fill-height>
21
+ * <vf-icon label="Macintosh HD" width="64" selectable movable editable left="16" top="24">…</vf-icon>
22
+ * <vf-icon label="Trash" width="64" selectable movable editable left="16" top="280">…</vf-icon>
23
+ * </vf-icon-field>
24
+ * <vf-window heading="Documents" width="320" height="201" scrollbars="both" top="40" left="120">
25
+ * <vf-icon-field label="Documents">
26
+ * <vf-icon label="Read Me" width="64" selectable movable editable left="16" top="16">…</vf-icon>
27
+ * </vf-icon-field>
28
+ * </vf-window>
29
+ * </vf-desktop>
30
+ * ```
31
+ *
32
+ * ### Semantics through internals
33
+ *
34
+ * `role="listbox"` and `aria-multiselectable="true"` always — the Finder was
35
+ * always multi-select, so there is no attribute for it — and `label` as the
36
+ * accessible name, the way `vf-scroll-area` takes one. Internals values are
37
+ * defaults (SPEC §2): a consumer's own `role`/`aria-*` on the tag still wins.
38
+ * A `selectable` icon inside becomes a real `option` carrying `aria-selected`;
39
+ * `vf-icon` matches this tag directly, since a role written through internals
40
+ * never lands as an attribute for `closest()` to find.
41
+ *
42
+ * ### Layout-neutral, exactly what the div was
43
+ *
44
+ * An in-flow block with no size, no inset and no position of its own,
45
+ * painting nothing — the `vf-stack` posture. A field whose icons are all
46
+ * placed is a zero-height block, so the icons keep anchoring to the desktop's
47
+ * raster or the window's plane, stored coordinates mean what they meant, and
48
+ * a press on the bare dither still reaches the desktop. It takes `top`/`left`
49
+ * and `width`/`height` like the other declared-box containers, so a page that
50
+ * wants the field to be a box places and sizes it, at which point it becomes
51
+ * the anchor — the placement working, as documented for everything.
52
+ * `fill-width`/`fill-height` are the other way to give it a box, read about
53
+ * the host the way `vf-stack` and `vf-container` read them: a filled field
54
+ * stays in flow and static, so its icons keep their anchor, and it has a
55
+ * surface to press — which is what the rubber band needs.
56
+ *
57
+ * ### The rubber band
58
+ *
59
+ * A press on the field's own background — not on an icon — dragged across
60
+ * the field draws the Finder's dotted selection rectangle from the press to
61
+ * the pointer, and the selection follows it live by the Finder's one rule:
62
+ * **an icon is selected exactly when it was selected at the press XOR the
63
+ * rectangle touches it.** The press fixes that anchor: a plain press has
64
+ * already cleared everything (the way any press outside an icon does), so
65
+ * the rectangle simply selects what it touches and releases what it leaves;
66
+ * with Shift or ⌘ held the selection survives the press and the rectangle
67
+ * *toggles* against it — an icon already selected deselects while the
68
+ * rectangle covers it and comes back when it leaves, one that was not
69
+ * selects. Each change reports `vf-select` as a press would. "Touches"
70
+ * means the icon's art cell or its name plate ({@link VfIcon.touches}), not
71
+ * the empty cell beside them, so a rectangle through the gap between two
72
+ * icons selects neither. The release keeps the result; Escape or a
73
+ * `pointercancel` cancels, the rectangle vanishes and the selection goes
74
+ * back to the anchor. A press that never leaves its system pixel draws
75
+ * nothing.
76
+ *
77
+ * The rectangle is drawn with the drag outline's pen ({@link
78
+ * paintSelectionRect}: the dotted one-pixel outline, `filter: invert(1)`
79
+ * under `mix-blend-mode: difference`) at the field's own level, which is
80
+ * where the Finder drew its marquee — on the desktop under the windows, in a
81
+ * window body over that window's content and under any window above it —
82
+ * positioned against the viewport from the field's own shadow, since a
83
+ * layout-neutral field has no box of its own to anchor to. It reaches no
84
+ * further than the field, inside every clip above the field (a window body,
85
+ * a scroll viewport, the raster's edge), so it never paints outside the
86
+ * region the icons are visible in. Its dots are phased against the field's
87
+ * own box; a field at the screen's origin shares the desktop dither's phase.
88
+ *
89
+ * Only a field with a box can be pressed: fill it, or place and size it. An
90
+ * unplaced, unfilled field of placed icons is zero-height and draws no band.
91
+ * `touch-action` is left alone, so on a touch screen a drag on the
92
+ * background pans a scrolling window as it always did; the band is a mouse
93
+ * and pen gesture.
94
+ *
95
+ * ### `size` is the view's
96
+ *
97
+ * `large` or `small`, written onto every icon in the field when set and
98
+ * whenever it changes, so View → by Small Icon is one attribute instead of a
99
+ * loop over every icon. Unset, each icon keeps its own.
100
+ *
101
+ * ### The desktop uses it, and renders none
102
+ *
103
+ * A desktop's icons sit in a field slotted into `vf-desktop` beside the
104
+ * windows, written by the page; a folder window's icons sit in one slotted
105
+ * into the window body. `vf-desktop` builds no field of its own: its furniture
106
+ * is slotted light DOM and the page decides what it is, a desktop with no
107
+ * icons should carry no empty listbox, and a field inside the desktop's
108
+ * shadow is one `closest()` could not reach.
109
+ *
110
+ * @slot - The icons.
111
+ */
112
+ export declare class VfIconField extends VfIconField_base {
113
+ #private;
114
+ static styles: import("lit").CSSResult[];
115
+ /**
116
+ * Accessible name for the field — "Desktop", the folder's name — applied as
117
+ * the listbox's `aria-label` through internals, so a consumer's own
118
+ * `aria-label` on the tag still wins.
119
+ */
120
+ label: string;
121
+ /**
122
+ * The view's icon size, `large` or `small`, written onto every icon in the
123
+ * field — View → by Small Icon as one attribute. Unset, each icon keeps
124
+ * its own `size`.
125
+ */
126
+ size?: VfIconSize | null;
127
+ /**
128
+ * Default-on display scaling (true 72dpi size); see src/scale.ts. A lone
129
+ * field with a declared size on a plain page would otherwise resolve that
130
+ * size against the `var(--vf-scale, 1)` fallback while its icons
131
+ * self-scaled around it — the vf-stack reasoning.
132
+ */
133
+ private readonly scale;
134
+ constructor();
135
+ disconnectedCallback(): void;
136
+ protected updated(changed: PropertyValues<this>): void;
137
+ protected render(): import("lit-html").TemplateResult<1>;
138
+ }
139
+ declare global {
140
+ interface HTMLElementTagNameMap {
141
+ 'vf-icon-field': VfIconField;
142
+ }
143
+ }
144
+ export {};
@@ -0,0 +1,173 @@
1
+ import { vfElement as e } from "../define.js";
2
+ import { ScaleController as t, effectiveScale as n } from "../scale.js";
3
+ import r from "../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
+ import { VfPositioned as i } from "../position.js";
5
+ import { vfBase as a } from "../styles/recipes/host.js";
6
+ import { DocumentListenersController as o } from "../document-listeners.js";
7
+ import { VfSized as s } from "../size.js";
8
+ import { paintSelectionRect as c } from "../open-art.js";
9
+ import { LitElement as l, css as u, html as d } from "lit";
10
+ import { property as f } from "lit/decorators.js";
11
+ //#region src/components/vf-icon-field.ts
12
+ var p = (e, t, n) => Math.min(Math.max(e, t), n), m = class extends s(i(l)) {
13
+ static {
14
+ this.styles = [a, u`
15
+ :host {
16
+ /* A block that shrink-wraps: the field is exactly as big as what it
17
+ holds in flow — nothing, for a field of placed icons — so it can
18
+ never hand a size it never declared to anything, and a zero-height
19
+ block intercepts no press meant for the desktop under it. A
20
+ declared width/height lands on the host's inline style (VfSized)
21
+ and beats this; top/left make it the anchor (VfPositioned). */
22
+ display: block;
23
+ width: fit-content;
24
+ /* Typographic transparency — the vf-stack reset, for the same
25
+ reason: vfBase dresses a host as chrome, and a container that
26
+ holds no text of its own must not change how its content reads. */
27
+ font: inherit;
28
+ -webkit-font-smoothing: inherit;
29
+ color: inherit;
30
+ user-select: inherit;
31
+ -webkit-user-select: inherit;
32
+ text-align: inherit;
33
+ }
34
+ /* The same two words a child uses, read about the host — the
35
+ vf-container rule. A percentage is the one fill a page can always
36
+ express: 100% of a desktop's screen, of a window body (a definite
37
+ flex item). The host stays static, so its icons keep their anchor;
38
+ what it gains is a surface to press. */
39
+ :host([fill-width]) {
40
+ width: 100%;
41
+ }
42
+ :host([fill-height]) {
43
+ height: 100%;
44
+ }
45
+ `];
46
+ }
47
+ #e;
48
+ #t;
49
+ #n;
50
+ constructor() {
51
+ super(), this.label = "", this.scale = new t(this), this.#e = this.attachInternals(), this.#t = null, this.#n = new o(this, () => [[
52
+ document,
53
+ "keydown",
54
+ this.#l,
55
+ !0
56
+ ]]), this.#r = () => {
57
+ this.#i();
58
+ }, this.#o = (e) => {
59
+ if (this.#t || e.button !== 0 || e.target !== this) return;
60
+ let t = this.getBoundingClientRect();
61
+ if (t.width <= 0 || t.height <= 0) return;
62
+ let r = n(this), i = this.#f(t, r);
63
+ this.#t = {
64
+ pointerId: e.pointerId,
65
+ box: t,
66
+ reach: i,
67
+ origin: this.#d(e, t, r, i),
68
+ anchor: new Set(this.#a().filter((e) => e.selected)),
69
+ rect: null,
70
+ canvas: null,
71
+ phase: -1
72
+ }, this.setPointerCapture(e.pointerId), e.preventDefault();
73
+ }, this.#s = (e) => {
74
+ let t = this.#t;
75
+ if (!t || e.pointerId !== t.pointerId) return;
76
+ let r = n(this), i = this.#d(e, t.box, r, t.reach);
77
+ if (!t.rect && i.x === t.origin.x && i.y === t.origin.y) return;
78
+ let a = {
79
+ x: Math.min(t.origin.x, i.x),
80
+ y: Math.min(t.origin.y, i.y),
81
+ width: Math.abs(i.x - t.origin.x),
82
+ height: Math.abs(i.y - t.origin.y)
83
+ }, o = t.rect;
84
+ o && o.x === a.x && o.y === a.y && o.width === a.width && o.height === a.height || (o || (this.#n.attach(), t.canvas = this.#p(), this.renderRoot.append(t.canvas)), t.rect = a, this.#m(t, r), this.#h(t, r));
85
+ }, this.#c = (e) => {
86
+ let t = this.#t;
87
+ !t || e.pointerId !== t.pointerId || this.#u(e.type === "pointercancel");
88
+ }, this.#l = (e) => {
89
+ e.key === "Escape" && (e.preventDefault(), e.stopPropagation(), this.#u(!0));
90
+ }, this.#e.role = "listbox", this.#e.ariaMultiSelectable = "true", this.addEventListener("pointerdown", this.#o), this.addEventListener("pointermove", this.#s), this.addEventListener("pointerup", this.#c), this.addEventListener("pointercancel", this.#c);
91
+ }
92
+ disconnectedCallback() {
93
+ super.disconnectedCallback(), this.#t?.canvas?.remove(), this.#t = null;
94
+ }
95
+ updated(e) {
96
+ e.has("label") && (this.#e.ariaLabel = this.label || null), e.has("size") && this.#i();
97
+ }
98
+ #r;
99
+ #i() {
100
+ let e = this.size;
101
+ if (e != null) for (let t of this.querySelectorAll("vf-icon")) t.size !== e && (t.size = e);
102
+ }
103
+ #a() {
104
+ return [...this.querySelectorAll("vf-icon")].filter((e) => e.selectable);
105
+ }
106
+ #o;
107
+ #s;
108
+ #c;
109
+ #l;
110
+ #u(e) {
111
+ let t = this.#t;
112
+ if (t && (this.#t = null, this.hasPointerCapture(t.pointerId) && this.releasePointerCapture(t.pointerId), t.rect && (this.#n.detach(), t.canvas?.remove(), e))) for (let e of this.#a()) {
113
+ let n = t.anchor.has(e);
114
+ e.selected !== n && e.setSelected(n);
115
+ }
116
+ }
117
+ #d(e, t, n, r) {
118
+ return {
119
+ x: p(Math.round((e.clientX - t.left) / n), r.minX, r.maxX),
120
+ y: p(Math.round((e.clientY - t.top) / n), r.minY, r.maxY)
121
+ };
122
+ }
123
+ #f(e, t) {
124
+ let n = e.left, r = e.top, i = e.right, a = e.bottom, o = this.assignedSlot ?? this.parentNode;
125
+ for (; o;) if (o instanceof Element) {
126
+ let e = getComputedStyle(o);
127
+ if (e.overflowX !== "visible" || e.overflowY !== "visible") {
128
+ let e = o.getBoundingClientRect();
129
+ n = Math.max(n, e.left), r = Math.max(r, e.top), i = Math.min(i, e.right), a = Math.min(a, e.bottom);
130
+ }
131
+ o = o.assignedSlot ?? o.parentNode;
132
+ } else o = o instanceof ShadowRoot ? o.host : null;
133
+ let s = (e) => Math.round(e * 64) / 64;
134
+ return {
135
+ minX: Math.max(0, Math.ceil(s((n - e.left) / t))),
136
+ minY: Math.max(0, Math.ceil(s((r - e.top) / t))),
137
+ maxX: Math.max(0, Math.floor(s((i - e.left) / t))),
138
+ maxY: Math.max(0, Math.floor(s((a - e.top) / t)))
139
+ };
140
+ }
141
+ #p() {
142
+ let e = document.createElement("canvas");
143
+ e.className = "selection-rect";
144
+ let t = e.style;
145
+ return t.position = "fixed", t.display = "block", t.pointerEvents = "none", t.filter = "invert(1)", t.mixBlendMode = "difference", t.imageRendering = "pixelated", e;
146
+ }
147
+ #m(e, t) {
148
+ let { rect: n, canvas: r } = e;
149
+ if (!n || !r) return;
150
+ let i = Math.max(1, n.width), a = Math.max(1, n.height), o = ((n.x + n.y) % 2 + 2) % 2;
151
+ c(r, i, a, o), e.phase = o, r.style.left = `${e.box.left + n.x * t}px`, r.style.top = `${e.box.top + n.y * t}px`, r.style.width = `${i * t}px`, r.style.height = `${a * t}px`;
152
+ }
153
+ #h(e, t) {
154
+ let { rect: n } = e;
155
+ if (!n) return;
156
+ let r = {
157
+ left: e.box.left + n.x * t,
158
+ top: e.box.top + n.y * t,
159
+ right: e.box.left + (n.x + Math.max(1, n.width)) * t,
160
+ bottom: e.box.top + (n.y + Math.max(1, n.height)) * t
161
+ };
162
+ for (let t of this.#a()) {
163
+ let n = e.anchor.has(t) !== t.touches(r);
164
+ t.selected !== n && t.setSelected(n);
165
+ }
166
+ }
167
+ render() {
168
+ return d`<slot @slotchange=${this.#r}></slot>`;
169
+ }
170
+ };
171
+ r([f()], m.prototype, "label", void 0), r([f({ reflect: !0 })], m.prototype, "size", void 0), m = r([e("vf-icon-field")], m);
172
+ //#endregion
173
+ export { m as VfIconField };