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.
- package/custom-elements.json +1230 -235
- package/dist/components/vf-desktop.d.ts +34 -0
- package/dist/components/vf-desktop.js +66 -43
- package/dist/components/vf-icon-field.d.ts +144 -0
- package/dist/components/vf-icon-field.js +173 -0
- package/dist/components/vf-icon.d.ts +196 -6
- package/dist/components/vf-icon.js +374 -122
- package/dist/components/vf-scroll-area.d.ts +25 -6
- package/dist/components/vf-scroll-area.js +34 -28
- package/dist/components/vf-window.d.ts +24 -0
- package/dist/components/vf-window.js +66 -59
- package/dist/drag.d.ts +28 -4
- package/dist/drag.js +15 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -12
- package/dist/open-art.d.ts +60 -1
- package/dist/open-art.js +47 -1
- package/dist/position.d.ts +35 -0
- package/dist/position.js +63 -45
- package/docs/SPEC.md +24 -8
- package/editor/vscode.html-custom-data.json +50 -3
- package/editor/web-types.json +132 -4
- package/package.json +2 -1
|
@@ -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
|
|
7
|
-
import { tileImage as
|
|
8
|
-
import { PATTERNS as
|
|
9
|
-
import { patternOverride as
|
|
10
|
-
import { PatternFillController as
|
|
11
|
-
import { GridSnapController as
|
|
12
|
-
import { DocumentListenersController as
|
|
13
|
-
import { SCREEN_CORNER as
|
|
14
|
-
import { LitElement as
|
|
15
|
-
import { property as
|
|
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
|
|
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 =
|
|
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 ??
|
|
24
|
-
height: this.height ??
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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, ${
|
|
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: ${
|
|
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) * ${
|
|
160
|
-
height: calc(var(--vf-scale, 1) * ${
|
|
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: ${
|
|
186
|
+
clip-path: ${D(C(S, "left"))};
|
|
168
187
|
}
|
|
169
188
|
.corner.tr {
|
|
170
189
|
right: 0;
|
|
171
|
-
clip-path: ${
|
|
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 ?
|
|
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 ?
|
|
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 =
|
|
269
|
-
let e =
|
|
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 ${
|
|
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 ??
|
|
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 ??
|
|
281
|
-
${
|
|
282
|
-
cols: Math.ceil(e /
|
|
283
|
-
rows: Math.ceil(t /
|
|
284
|
-
tile:
|
|
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
|
|
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
|
-
|
|
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([
|
|
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 {
|
|
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 };
|