vintage-frames 0.1.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/LICENSE +21 -0
- package/README.md +262 -0
- package/custom-elements.json +19807 -0
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js +9 -0
- package/dist/chrome.d.ts +73 -0
- package/dist/chrome.js +94 -0
- package/dist/components/vf-button-group.d.ts +50 -0
- package/dist/components/vf-button-group.js +68 -0
- package/dist/components/vf-button.d.ts +169 -0
- package/dist/components/vf-button.js +234 -0
- package/dist/components/vf-checkbox.d.ts +60 -0
- package/dist/components/vf-checkbox.js +135 -0
- package/dist/components/vf-container.d.ts +95 -0
- package/dist/components/vf-container.js +86 -0
- package/dist/components/vf-desktop.d.ts +243 -0
- package/dist/components/vf-desktop.js +310 -0
- package/dist/components/vf-dialog.d.ts +105 -0
- package/dist/components/vf-dialog.js +280 -0
- package/dist/components/vf-fieldset.d.ts +34 -0
- package/dist/components/vf-fieldset.js +76 -0
- package/dist/components/vf-grid.d.ts +124 -0
- package/dist/components/vf-grid.js +133 -0
- package/dist/components/vf-icon.d.ts +356 -0
- package/dist/components/vf-icon.js +507 -0
- package/dist/components/vf-img.d.ts +63 -0
- package/dist/components/vf-img.js +65 -0
- package/dist/components/vf-label.d.ts +117 -0
- package/dist/components/vf-label.js +151 -0
- package/dist/components/vf-list-item.d.ts +48 -0
- package/dist/components/vf-list-item.js +98 -0
- package/dist/components/vf-list.d.ts +77 -0
- package/dist/components/vf-list.js +256 -0
- package/dist/components/vf-menu-bar.d.ts +64 -0
- package/dist/components/vf-menu-bar.js +236 -0
- package/dist/components/vf-menu-item.d.ts +91 -0
- package/dist/components/vf-menu-item.js +252 -0
- package/dist/components/vf-menu.d.ts +95 -0
- package/dist/components/vf-menu.js +346 -0
- package/dist/components/vf-number-field.d.ts +60 -0
- package/dist/components/vf-number-field.js +248 -0
- package/dist/components/vf-option.d.ts +49 -0
- package/dist/components/vf-option.js +119 -0
- package/dist/components/vf-paragraph.d.ts +73 -0
- package/dist/components/vf-paragraph.js +66 -0
- package/dist/components/vf-progress-bar.d.ts +69 -0
- package/dist/components/vf-progress-bar.js +272 -0
- package/dist/components/vf-radio-group.d.ts +109 -0
- package/dist/components/vf-radio-group.js +105 -0
- package/dist/components/vf-radio.d.ts +59 -0
- package/dist/components/vf-radio.js +135 -0
- package/dist/components/vf-scroll-area.d.ts +75 -0
- package/dist/components/vf-scroll-area.js +124 -0
- package/dist/components/vf-select.d.ts +334 -0
- package/dist/components/vf-select.js +658 -0
- package/dist/components/vf-separator.d.ts +38 -0
- package/dist/components/vf-separator.js +55 -0
- package/dist/components/vf-slider.d.ts +80 -0
- package/dist/components/vf-slider.js +255 -0
- package/dist/components/vf-stack.d.ts +188 -0
- package/dist/components/vf-stack.js +168 -0
- package/dist/components/vf-swatch.d.ts +98 -0
- package/dist/components/vf-swatch.js +201 -0
- package/dist/components/vf-text-area.d.ts +77 -0
- package/dist/components/vf-text-area.js +121 -0
- package/dist/components/vf-text-field.d.ts +53 -0
- package/dist/components/vf-text-field.js +79 -0
- package/dist/components/vf-window.d.ts +157 -0
- package/dist/components/vf-window.js +377 -0
- package/dist/cursor-art.d.ts +37 -0
- package/dist/cursor-art.js +41 -0
- package/dist/cursor.d.ts +49 -0
- package/dist/cursor.js +171 -0
- package/dist/define.d.ts +45 -0
- package/dist/define.js +15 -0
- package/dist/document-listeners.d.ts +62 -0
- package/dist/document-listeners.js +29 -0
- package/dist/drag.d.ts +49 -0
- package/dist/drag.js +30 -0
- package/dist/events.d.ts +79 -0
- package/dist/events.js +25 -0
- package/dist/focus-modality.d.ts +94 -0
- package/dist/focus-modality.js +56 -0
- package/dist/form-control.d.ts +247 -0
- package/dist/form-control.js +127 -0
- package/dist/glyphs.d.ts +117 -0
- package/dist/glyphs.js +100 -0
- package/dist/grid-snap.d.ts +73 -0
- package/dist/grid-snap.js +135 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.js +71 -0
- package/dist/menu-press.d.ts +60 -0
- package/dist/menu-press.js +97 -0
- package/dist/modal-dialog.d.ts +148 -0
- package/dist/modal-dialog.js +155 -0
- package/dist/motion.d.ts +72 -0
- package/dist/motion.js +22 -0
- package/dist/number.d.ts +24 -0
- package/dist/number.js +12 -0
- package/dist/open-art.d.ts +40 -0
- package/dist/open-art.js +35 -0
- package/dist/pixel-frame.d.ts +102 -0
- package/dist/pixel-frame.js +77 -0
- package/dist/popup-overflow.d.ts +157 -0
- package/dist/popup-overflow.js +45 -0
- package/dist/position.d.ts +187 -0
- package/dist/position.js +74 -0
- package/dist/scale.d.ts +259 -0
- package/dist/scale.js +133 -0
- package/dist/scroll-rail.d.ts +112 -0
- package/dist/scroll-rail.js +194 -0
- package/dist/scroll-state.d.ts +113 -0
- package/dist/scroll-state.js +55 -0
- package/dist/size.d.ts +47 -0
- package/dist/size.js +30 -0
- package/dist/styles/base.d.ts +38 -0
- package/dist/styles/body-font.d.ts +7 -0
- package/dist/styles/body-font.js +9 -0
- package/dist/styles/display-font.d.ts +7 -0
- package/dist/styles/display-font.js +9 -0
- package/dist/styles/recipes/body-face.d.ts +14 -0
- package/dist/styles/recipes/body-face.js +6 -0
- package/dist/styles/recipes/display-face.d.ts +23 -0
- package/dist/styles/recipes/display-face.js +10 -0
- package/dist/styles/recipes/field.d.ts +20 -0
- package/dist/styles/recipes/field.js +68 -0
- package/dist/styles/recipes/focus.d.ts +76 -0
- package/dist/styles/recipes/focus.js +39 -0
- package/dist/styles/recipes/host.d.ts +5 -0
- package/dist/styles/recipes/host.js +62 -0
- package/dist/styles/recipes/pattern.d.ts +80 -0
- package/dist/styles/recipes/pattern.js +82 -0
- package/dist/styles/recipes/scroll-rail.d.ts +69 -0
- package/dist/styles/recipes/scroll-rail.js +222 -0
- package/dist/styles/recipes/shadow.d.ts +13 -0
- package/dist/styles/recipes/shadow.js +5 -0
- package/dist/styles/recipes/static-text.d.ts +24 -0
- package/dist/styles/recipes/static-text.js +17 -0
- package/dist/styles/recipes/surface.d.ts +28 -0
- package/dist/styles/recipes/surface.js +27 -0
- package/dist/styles/recipes/tile.d.ts +122 -0
- package/dist/styles/recipes/tile.js +40 -0
- package/dist/styles/recipes/title-bar.d.ts +49 -0
- package/dist/styles/recipes/title-bar.js +134 -0
- package/dist/styles/recipes/toggle.d.ts +10 -0
- package/dist/styles/recipes/toggle.js +20 -0
- package/dist/styles/register-embedded-font.d.ts +64 -0
- package/dist/styles/register-embedded-font.js +30 -0
- package/dist/text-control.d.ts +131 -0
- package/dist/text-control.js +83 -0
- package/dist/tile-grid.d.ts +111 -0
- package/dist/tile-grid.js +53 -0
- package/dist/toggle-control.d.ts +60 -0
- package/dist/toggle-control.js +41 -0
- package/dist/track-width.d.ts +42 -0
- package/dist/track-width.js +30 -0
- package/dist/type-ahead.d.ts +35 -0
- package/dist/type-ahead.js +20 -0
- package/dist/zoom.d.ts +204 -0
- package/dist/zoom.js +105 -0
- package/docs/SPEC.md +2471 -0
- package/editor/vscode.html-custom-data.json +1320 -0
- package/editor/web-types.json +3237 -0
- package/package.json +118 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { DocumentListenersController as e } from "./document-listeners.js";
|
|
2
|
+
import "./motion.js";
|
|
3
|
+
//#region src/menu-press.ts
|
|
4
|
+
var t = class {
|
|
5
|
+
#e;
|
|
6
|
+
#t;
|
|
7
|
+
#n;
|
|
8
|
+
#r;
|
|
9
|
+
#i;
|
|
10
|
+
#a;
|
|
11
|
+
#o;
|
|
12
|
+
constructor(t, n) {
|
|
13
|
+
this.target = n, this.#e = !1, this.#t = !1, this.#n = !1, this.#r = null, this.#i = 0, this.#a = null, this.onPointerDown = (e) => {
|
|
14
|
+
if (e.button > 0 || !e.isPrimary) return;
|
|
15
|
+
let t = this.#p(e.clientX, e.clientY);
|
|
16
|
+
if (t) {
|
|
17
|
+
if (e.preventDefault(), this.#e = !0, this.#n = !1, this.#r = t.item ?? t.menu, this.#i = e.timeStamp, this.#t = !t.menu.open, this.#o.attach(), t.item) {
|
|
18
|
+
this.#f(t.item);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
t.menu.focus(), this.#t && this.target.open(t.menu);
|
|
22
|
+
}
|
|
23
|
+
}, this.#s = (e) => {
|
|
24
|
+
if (!this.#e) return;
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
let t = this.#p(e.clientX, e.clientY), n = t ? t.item ?? t.menu : null;
|
|
27
|
+
!this.#n && n !== this.#r && (this.#n = !0), t && !t.item && !t.menu.open && this.target.open(t.menu), this.#f(t?.item ?? null);
|
|
28
|
+
}, this.#c = (e) => {
|
|
29
|
+
if (!this.#e) return;
|
|
30
|
+
let t = this.#p(e.clientX, e.clientY), n = this.#t, r = !this.#n, i = e.timeStamp - this.#i < 200;
|
|
31
|
+
this.#u(), !(n && r && i) && this.#d(t?.item ?? null);
|
|
32
|
+
}, this.#l = () => {
|
|
33
|
+
this.#u(), this.#f(null);
|
|
34
|
+
}, t.addController(this), this.#o = new e(t, () => [
|
|
35
|
+
[
|
|
36
|
+
document,
|
|
37
|
+
"pointermove",
|
|
38
|
+
this.#s,
|
|
39
|
+
!0
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
document,
|
|
43
|
+
"pointerup",
|
|
44
|
+
this.#c,
|
|
45
|
+
!0
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
document,
|
|
49
|
+
"pointercancel",
|
|
50
|
+
this.#l,
|
|
51
|
+
!0
|
|
52
|
+
]
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
hostDisconnected() {
|
|
56
|
+
this.#u(), this.#f(null);
|
|
57
|
+
}
|
|
58
|
+
#s;
|
|
59
|
+
#c;
|
|
60
|
+
#l;
|
|
61
|
+
#u() {
|
|
62
|
+
this.#e && (this.#e = !1, this.#r = null, this.#o.detach());
|
|
63
|
+
}
|
|
64
|
+
#d(e) {
|
|
65
|
+
if (!e || e.disabled) {
|
|
66
|
+
this.#f(null), this.target.close();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.#f(e), this.#a = null, e.activate();
|
|
70
|
+
}
|
|
71
|
+
#f(e) {
|
|
72
|
+
let t = e && !e.disabled ? e : null;
|
|
73
|
+
t !== this.#a && (this.#a && (this.#a.active = !1), this.#a = t, t && (t.active = !0));
|
|
74
|
+
}
|
|
75
|
+
#p(e, t) {
|
|
76
|
+
let r = this.target.menus(), i = r.find((e) => e.open);
|
|
77
|
+
if (i) {
|
|
78
|
+
for (let r of i.allItems) if (n(r.getBoundingClientRect(), e, t)) return {
|
|
79
|
+
menu: i,
|
|
80
|
+
item: r
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
for (let i of r) {
|
|
84
|
+
let r = i.labelRect;
|
|
85
|
+
if (r && n(r, e, t)) return {
|
|
86
|
+
menu: i,
|
|
87
|
+
item: null
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function n(e, t, n) {
|
|
94
|
+
return t >= e.left && t < e.right && n >= e.top && n < e.bottom;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { t as MenuPressController };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import type { PropertyValues } from 'lit';
|
|
3
|
+
import { ScaleController } from './scale.js';
|
|
4
|
+
/** Reason a modal closed, carried by the `vf-close` event's detail. */
|
|
5
|
+
export type VfCloseReason = 'escape' | 'close';
|
|
6
|
+
/**
|
|
7
|
+
* The width an undeclared modal falls back to, in system px — a classic
|
|
8
|
+
* dialog's own measure, wide enough for a line of chrome text and a button row.
|
|
9
|
+
* It exists so an unsized modal still renders, and still owns a *definite*
|
|
10
|
+
* width so dragging cannot squeeze it; the console says what to declare.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MODAL_FALLBACK_WIDTH = 260;
|
|
13
|
+
/**
|
|
14
|
+
* Shared native-`<dialog>` styles for the modal shells: a chromeless top-layer
|
|
15
|
+
* dialog (the frame is drawn by the subclass) with a fully transparent
|
|
16
|
+
* backdrop — pure System 7, no dimming.
|
|
17
|
+
*/
|
|
18
|
+
export declare const modalDialogStyles: import("lit").CSSResult;
|
|
19
|
+
/**
|
|
20
|
+
* Base class for the modal shell `vf-dialog` — and for a consumer's own
|
|
21
|
+
* modal (an alert box, say) authored against the kit.
|
|
22
|
+
*
|
|
23
|
+
* Owns the native `<dialog>` lifecycle every modal shares: `open` sync, `show()` /
|
|
24
|
+
* `close()`, the {@link top}/{@link left} placement (stated or centered) and the
|
|
25
|
+
* single `close` funnel that drops the written origin and fires `vf-close` with
|
|
26
|
+
* the reason. Because every close path — Escape, `close()`, backdrop — routes
|
|
27
|
+
* through the native `close` event, an Escape-close no longer leaves a stale
|
|
28
|
+
* origin behind, so the next open re-derives it.
|
|
29
|
+
*
|
|
30
|
+
* Removing an open modal from the DOM is a close path too. HTML's dialog
|
|
31
|
+
* *removing steps* take the element out of the top layer **without** running
|
|
32
|
+
* the close algorithm — no `close` event, no focus restoration — which is
|
|
33
|
+
* exactly what the standard framework pattern of unmounting a dialog instead
|
|
34
|
+
* of calling `close()` does. `disconnectedCallback` routes that path through
|
|
35
|
+
* the same funnel: the removed element still fires `vf-close` (heard by
|
|
36
|
+
* listeners on the element itself — it has left the tree, so nothing
|
|
37
|
+
* bubbles), `open` and the pinned margins reconcile so a re-append mounts it
|
|
38
|
+
* closed and re-centered, and focus returns to the element that was focused
|
|
39
|
+
* when the modal opened.
|
|
40
|
+
*
|
|
41
|
+
* Subclasses supply only the frame chrome: a `render()` returning
|
|
42
|
+
* `<dialog @cancel=${this._onNativeCancel} @close=${this._onNativeClose}>` with
|
|
43
|
+
* their role/ARIA and body, and {@link modalDialogStyles} in `static styles`.
|
|
44
|
+
*
|
|
45
|
+
* @fires vf-close - The modal closed. `detail: { reason: 'escape' | 'close' }`.
|
|
46
|
+
*/
|
|
47
|
+
export declare class VfModalDialog extends LitElement {
|
|
48
|
+
#private;
|
|
49
|
+
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
50
|
+
protected readonly scale: ScaleController;
|
|
51
|
+
/** Whether the modal is open. Kept in sync with the native `<dialog>`. */
|
|
52
|
+
open: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Modal width in whole system px — the art's own unit, so the box holds its
|
|
55
|
+
* proportion to the chrome inside it at every display density.
|
|
56
|
+
*
|
|
57
|
+
* **Declare it.** A modal owns its width; it is not a shape its content
|
|
58
|
+
* happens to fall into. The platform's own default is `fit-content` measured
|
|
59
|
+
* against the space *left over* beside its offsets — and stating an offset is
|
|
60
|
+
* exactly how the box is placed and dragged ({@link top}), so an undeclared
|
|
61
|
+
* modal squeezes itself and reflows its text as it moves toward an edge.
|
|
62
|
+
* Unset, it falls back to {@link MODAL_FALLBACK_WIDTH} and says so once in
|
|
63
|
+
* the console.
|
|
64
|
+
*/
|
|
65
|
+
width?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Modal height in whole system px.
|
|
68
|
+
*
|
|
69
|
+
* **Declare it,** with {@link width}. A modal is a fixed box in both axes —
|
|
70
|
+
* the same rule `vf-window` follows, for the same reason: the classic shells
|
|
71
|
+
* carried both dimensions in the resource, and a box that grows with its body
|
|
72
|
+
* is one whose buttons move under the pointer as its text changes. Unset, the
|
|
73
|
+
* body sizes to its content — the old behavior — and it says so once in the
|
|
74
|
+
* console.
|
|
75
|
+
*
|
|
76
|
+
* Content taller than the declared box is clipped at the frame.
|
|
77
|
+
*/
|
|
78
|
+
height?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Offset from the top of the **viewport**, in whole system px — the same
|
|
81
|
+
* `top`/`left` pair every other component takes ({@link VfPositioned}), in
|
|
82
|
+
* the same unit, with one difference the platform forces: `showModal()` puts
|
|
83
|
+
* the box in the top layer, whose containing block is the viewport rather
|
|
84
|
+
* than the nearest positioned ancestor. So these coordinates are screen
|
|
85
|
+
* coordinates, not the parent's.
|
|
86
|
+
*
|
|
87
|
+
* Leave both unset and the modal is **centered** — recomputed on open, and
|
|
88
|
+
* again whenever its own box or the viewport changes, which is what keeps a
|
|
89
|
+
* dialog whose slotted content upgrades after `showModal()` from opening at
|
|
90
|
+
* the offset its smaller first render centered at. Dragging the title bar
|
|
91
|
+
* states the pair; setting either back to `null` returns the modal to
|
|
92
|
+
* centering.
|
|
93
|
+
*/
|
|
94
|
+
top?: number | null;
|
|
95
|
+
/** Offset from the left of the viewport, in whole system px. See {@link top}. */
|
|
96
|
+
left?: number | null;
|
|
97
|
+
protected _dialog: HTMLDialogElement;
|
|
98
|
+
/**
|
|
99
|
+
* The declared box as inline styles for the subclass's `<dialog>`:
|
|
100
|
+
* `style=${styleMap(this.dialogSize)}`. Width always resolves to something
|
|
101
|
+
* definite — the declaration or the fallback — because that is what keeps a
|
|
102
|
+
* drag from squeezing the box.
|
|
103
|
+
*/
|
|
104
|
+
protected get dialogSize(): Record<string, string | null>;
|
|
105
|
+
/** Open the modal (native `showModal()`), pinned onto the device grid. */
|
|
106
|
+
show(): void;
|
|
107
|
+
/** Close the modal. Fires `vf-close` with `{ reason: 'close' }`. */
|
|
108
|
+
close(): void;
|
|
109
|
+
protected updated(changed: PropertyValues<this>): void;
|
|
110
|
+
/**
|
|
111
|
+
* State the origin, in system px — clamped on screen and snapped onto the
|
|
112
|
+
* placement lattice, so what `top`/`left` report is what you can see. This is
|
|
113
|
+
* what a title-bar drag calls; a modal that has been placed this way stays
|
|
114
|
+
* placed, and re-centers only if the pair is cleared.
|
|
115
|
+
*/
|
|
116
|
+
protected placeAt(x: number, y: number): void;
|
|
117
|
+
/**
|
|
118
|
+
* Write the origin onto the top-layer box: the stated one, or the centered
|
|
119
|
+
* one while the pair is unset. Both go on as a live `calc()` in system px
|
|
120
|
+
* ({@link sysLength}), so a zoom re-resolves the offset with every other
|
|
121
|
+
* metric instead of leaving the box behind at a stale CSS-px constant — and
|
|
122
|
+
* both are re-clamped here, which is what catches a viewport that shrank
|
|
123
|
+
* (zooming in leaves fewer system px on the screen) without forgetting where
|
|
124
|
+
* the modal was put.
|
|
125
|
+
*
|
|
126
|
+
* `position` is deliberately untouched: the UA already makes an open modal
|
|
127
|
+
* `position: fixed`, and the four inset/margin declarations are what turn its
|
|
128
|
+
* `inset: 0; margin: auto` centering into a stated origin.
|
|
129
|
+
*/
|
|
130
|
+
protected settle(): void;
|
|
131
|
+
/**
|
|
132
|
+
* Torn out of the DOM while open (the framework-unmount path — see the
|
|
133
|
+
* class doc). `close()` routes the teardown through the same native-`close`
|
|
134
|
+
* funnel every other path uses; the focus restore is ours to do, since the
|
|
135
|
+
* removing steps already dropped focus to `<body>` before this ran.
|
|
136
|
+
*/
|
|
137
|
+
disconnectedCallback(): void;
|
|
138
|
+
/** Native `cancel` (Escape): remember the reason; `close` follows. */
|
|
139
|
+
protected _onNativeCancel(): void;
|
|
140
|
+
/**
|
|
141
|
+
* Native `close` — the single teardown funnel for every close path. Drops
|
|
142
|
+
* the written origin so the next open re-derives it from the box it will
|
|
143
|
+
* actually have (a stated `top`/`left` is the author's or the user's and
|
|
144
|
+
* survives, and is re-applied by that open), syncs `open`, and fires
|
|
145
|
+
* `vf-close` with the reason.
|
|
146
|
+
*/
|
|
147
|
+
protected _onNativeClose(): void;
|
|
148
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { ScaleController as e, onScaleChange as t, snapSys as n, sysLength as r, toSysExact as i } from "./scale.js";
|
|
2
|
+
import a from "./_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
|
|
3
|
+
import { emit as o } from "./events.js";
|
|
4
|
+
import { LitElement as s, css as c } from "lit";
|
|
5
|
+
import { property as l, query as u } from "lit/decorators.js";
|
|
6
|
+
var d = 24, f = c`
|
|
7
|
+
dialog {
|
|
8
|
+
padding: 0;
|
|
9
|
+
margin: auto;
|
|
10
|
+
border: none;
|
|
11
|
+
background: transparent;
|
|
12
|
+
overflow: visible;
|
|
13
|
+
color: inherit;
|
|
14
|
+
font: inherit;
|
|
15
|
+
/* showModal()'s focusing steps fall back to focusing the dialog itself
|
|
16
|
+
when no descendant is chosen as the focus delegate — WebKit and Firefox
|
|
17
|
+
reach that with the kit's controls hidden inside shadow roots — and
|
|
18
|
+
both then draw their native ring around the top-layer box. The modal
|
|
19
|
+
frame already announces the surface, and focus marks inside it are the
|
|
20
|
+
kit's own (1-bit, on the controls), so the UA ring is suppressed rather
|
|
21
|
+
than restyled. */
|
|
22
|
+
outline: none;
|
|
23
|
+
}
|
|
24
|
+
/* Scoped to [open]: an unqualified display here would out-cascade the UA's
|
|
25
|
+
dialog:not([open]) { display: none } (author origin beats UA origin) and
|
|
26
|
+
paint every closed dialog. Flex, so the frame child can resolve against
|
|
27
|
+
the dialog's real box even when no height is declared and the UA's
|
|
28
|
+
dialog:modal max-height is what caps it — a percentage height resolves
|
|
29
|
+
to auto against that (indefinite) box, which left the frame content-tall
|
|
30
|
+
and spilling past the border with its buttons unreachable; a flex child
|
|
31
|
+
with min-height: 0 shrinks to the capped box instead, and the shells'
|
|
32
|
+
inner scroll region takes over from there. */
|
|
33
|
+
dialog[open] {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
}
|
|
37
|
+
dialog::backdrop {
|
|
38
|
+
background: transparent;
|
|
39
|
+
/* The backdrop is top-layer: no page cursor rule can reach it, so the
|
|
40
|
+
kit-wide cursor token (SPEC §3) is read here — \`::backdrop\` inherits
|
|
41
|
+
custom properties from its dialog in current engines; where it doesn't,
|
|
42
|
+
the fallback is the arrow it always showed. */
|
|
43
|
+
cursor: var(--vf-cursor, default);
|
|
44
|
+
}
|
|
45
|
+
`, p = class extends s {
|
|
46
|
+
constructor(...t) {
|
|
47
|
+
super(...t), this.scale = new e(this), this.open = !1, this.#e = !1, this.#n = null, this.#r = null, this.#l = () => this.settle();
|
|
48
|
+
}
|
|
49
|
+
get dialogSize() {
|
|
50
|
+
return {
|
|
51
|
+
width: r(this.width ?? 260),
|
|
52
|
+
height: this.height === void 0 ? null : r(this.height)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
#e;
|
|
56
|
+
#t() {
|
|
57
|
+
if (this.#e || !this.open) return;
|
|
58
|
+
let e = [];
|
|
59
|
+
if (this.width === void 0 && e.push("width (falling back to 260 system px)"), this.height === void 0 && e.push("height (falling back to the content)"), e.length === 0) return;
|
|
60
|
+
this.#e = !0;
|
|
61
|
+
let t = this.getAttribute("heading") ?? this.getAttribute("label") ?? "";
|
|
62
|
+
console.warn(`${this.localName}${t ? ` ("${t}")` : ""}: no ${e.join(", no ")}. A modal owns its whole box — declare both in system px: <${this.localName} width="320" height="140">.`);
|
|
63
|
+
}
|
|
64
|
+
#n;
|
|
65
|
+
#r;
|
|
66
|
+
show() {
|
|
67
|
+
this.open = !0, this.hasUpdated && this.#c();
|
|
68
|
+
}
|
|
69
|
+
close() {
|
|
70
|
+
this.open = !1, this.hasUpdated && this.#c();
|
|
71
|
+
}
|
|
72
|
+
updated(e) {
|
|
73
|
+
e.has("open") && this.#c(), (e.has("top") || e.has("left")) && this.settle(), this.#t();
|
|
74
|
+
}
|
|
75
|
+
#i() {
|
|
76
|
+
let e = document.documentElement;
|
|
77
|
+
return {
|
|
78
|
+
width: i(e.clientWidth || window.innerWidth, this),
|
|
79
|
+
height: i(e.clientHeight || window.innerHeight, this)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
#a(e, t) {
|
|
83
|
+
let n = this.#i(), r = i(this._dialog?.offsetWidth ?? 0, this);
|
|
84
|
+
return {
|
|
85
|
+
x: Math.min(Math.max(e, d - r), n.width - d),
|
|
86
|
+
y: Math.min(Math.max(t, 0), Math.max(0, n.height - d))
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
#o() {
|
|
90
|
+
let e = this.#i(), t = this._dialog?.getBoundingClientRect();
|
|
91
|
+
return {
|
|
92
|
+
x: (e.width - i(t?.width ?? 0, this)) / 2,
|
|
93
|
+
y: (e.height - i(t?.height ?? 0, this)) / 2
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
placeAt(e, t) {
|
|
97
|
+
let r = this.#a(e, t);
|
|
98
|
+
this.left = n(r.x, this), this.top = n(r.y, this);
|
|
99
|
+
}
|
|
100
|
+
settle() {
|
|
101
|
+
let e = this._dialog;
|
|
102
|
+
if (!e?.open) return;
|
|
103
|
+
let t = this.left != null || this.top != null ? {
|
|
104
|
+
x: this.left ?? 0,
|
|
105
|
+
y: this.top ?? 0
|
|
106
|
+
} : this.#o(), i = this.#a(t.x, t.y);
|
|
107
|
+
e.style.left = r(n(i.x, this)), e.style.top = r(n(i.y, this)), e.style.right = "auto", e.style.bottom = "auto", e.style.margin = "0";
|
|
108
|
+
}
|
|
109
|
+
#s() {
|
|
110
|
+
let e = this._dialog?.style;
|
|
111
|
+
if (e) for (let t of [
|
|
112
|
+
"left",
|
|
113
|
+
"top",
|
|
114
|
+
"right",
|
|
115
|
+
"bottom",
|
|
116
|
+
"margin"
|
|
117
|
+
]) e.removeProperty(t);
|
|
118
|
+
}
|
|
119
|
+
#c() {
|
|
120
|
+
let e = this._dialog;
|
|
121
|
+
e && (this.open && !e.open ? (this.#r = document.activeElement, e.showModal(), this.settle(), this.#f(e)) : !this.open && e.open && e.close());
|
|
122
|
+
}
|
|
123
|
+
#l;
|
|
124
|
+
#u;
|
|
125
|
+
#d;
|
|
126
|
+
#f(e) {
|
|
127
|
+
typeof ResizeObserver < "u" && !this.#u && (this.#u = new ResizeObserver(this.#l), this.#u.observe(e)), window.addEventListener("resize", this.#l), this.#d ??= t(this.#l);
|
|
128
|
+
}
|
|
129
|
+
#p() {
|
|
130
|
+
this.#u?.disconnect(), this.#u = void 0, window.removeEventListener("resize", this.#l), this.#d?.(), this.#d = void 0;
|
|
131
|
+
}
|
|
132
|
+
disconnectedCallback() {
|
|
133
|
+
super.disconnectedCallback(), this.#p();
|
|
134
|
+
let e = this._dialog;
|
|
135
|
+
if (e?.open) {
|
|
136
|
+
e.close();
|
|
137
|
+
let t = this.#r, n = document.activeElement;
|
|
138
|
+
t instanceof HTMLElement && t.isConnected && (n === null || n === document.body) && t.focus();
|
|
139
|
+
}
|
|
140
|
+
this.#r = null;
|
|
141
|
+
}
|
|
142
|
+
_onNativeCancel() {
|
|
143
|
+
this.#n = "escape";
|
|
144
|
+
}
|
|
145
|
+
_onNativeClose() {
|
|
146
|
+
let e = this.#n ?? "close";
|
|
147
|
+
this.#n = null, this.#r = null, this.#p(), this.open = !1, this.#s(), o(this, "vf-close", { reason: e });
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
a([l({
|
|
151
|
+
type: Boolean,
|
|
152
|
+
reflect: !0
|
|
153
|
+
})], p.prototype, "open", void 0), a([l({ type: Number })], p.prototype, "width", void 0), a([l({ type: Number })], p.prototype, "height", void 0), a([l({ type: Number })], p.prototype, "top", void 0), a([l({ type: Number })], p.prototype, "left", void 0), a([u("dialog")], p.prototype, "_dialog", void 0);
|
|
154
|
+
//#endregion
|
|
155
|
+
export { p as VfModalDialog, f as modalDialogStyles };
|
package/dist/motion.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility: honor the user's "reduce motion" preference.
|
|
3
|
+
*
|
|
4
|
+
* Components use this to skip non-essential animation — chiefly the classic
|
|
5
|
+
* ~250ms selection "blink" on menu items and popup options — and act instantly
|
|
6
|
+
* instead, so keyboard and assistive-tech users aren't made to wait through an
|
|
7
|
+
* effect they've asked the system to suppress. Guarded for SSR / environments
|
|
8
|
+
* without `matchMedia`, where it reports `false` (no reduction).
|
|
9
|
+
*/
|
|
10
|
+
export declare function prefersReducedMotion(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* How long (ms) an *in-place* press may last and still read as a modern
|
|
13
|
+
* click-to-open tap rather than a completed System 7 press.
|
|
14
|
+
*
|
|
15
|
+
* Shared by the two surfaces that offer the classic press-drag-release gesture
|
|
16
|
+
* — `vf-select`'s popup and the pull-down menus (`src/menu-press.ts`) — so the
|
|
17
|
+
* one timing a user can feel across both can't drift between them. Only an
|
|
18
|
+
* in-place release consults it: a press that travels to another row is a
|
|
19
|
+
* drag-pick regardless of duration.
|
|
20
|
+
*/
|
|
21
|
+
export declare const PRESS_HOLD_MS = 200;
|
|
22
|
+
/**
|
|
23
|
+
* How long (ms) a press on a selected icon's name waits before it opens the
|
|
24
|
+
* rename box — long enough that a double-click has already declared itself.
|
|
25
|
+
*
|
|
26
|
+
* The Finder's two pointer gestures on a name are the same press: click once
|
|
27
|
+
* and it renames, click twice and it opens. Only the *second* press tells them
|
|
28
|
+
* apart, so the rename cannot commit to opening until the window for one has
|
|
29
|
+
* passed — which is what makes this a delay rather than an immediate response.
|
|
30
|
+
* A press landing inside the window calls the pending rename off outright, so
|
|
31
|
+
* the number is only an upper bound on how slow a double-click may be and still
|
|
32
|
+
* read as one.
|
|
33
|
+
*
|
|
34
|
+
* No API reports what the *browser* counts as a double-click: it follows a
|
|
35
|
+
* platform setting the user can move (Windows offers up to 900 ms, macOS a
|
|
36
|
+
* comparable slider, both defaulting near 500). Overshooting it costs a wait
|
|
37
|
+
* before a rename box appears; undershooting it renames when the user meant to
|
|
38
|
+
* open. So the number leans long deliberately.
|
|
39
|
+
*/
|
|
40
|
+
export declare const RENAME_DELAY_MS = 800;
|
|
41
|
+
/**
|
|
42
|
+
* Milliseconds per row while the pointer rests on a menu scroll arrow — the
|
|
43
|
+
* pace a clipped popup rolls its list through the panel at (~15 rows/s).
|
|
44
|
+
*
|
|
45
|
+
* Deliberately *not* gated on `prefers-reduced-motion`, unlike the blink below.
|
|
46
|
+
* The blink is decoration played at the kit's discretion; this stepping is the
|
|
47
|
+
* interaction itself — user-held, discrete, and stopped the moment the pointer
|
|
48
|
+
* leaves — and suppressing it would leave the clipped rows unreachable by
|
|
49
|
+
* pointer. Same reasoning the drag gestures are ungated.
|
|
50
|
+
*/
|
|
51
|
+
export declare const MENU_SCROLL_INTERVAL_MS = 66;
|
|
52
|
+
/** Milliseconds between selection-blink phase flips. */
|
|
53
|
+
export declare const BLINK_INTERVAL_MS = 42;
|
|
54
|
+
/** Phase flips in one selection blink (6 = three off/on cycles, ~250ms). */
|
|
55
|
+
export declare const BLINK_FLIPS = 6;
|
|
56
|
+
/** Handle for an in-flight {@link runSelectionBlink}; {@link cancel} stops it. */
|
|
57
|
+
export interface BlinkHandle {
|
|
58
|
+
cancel(): void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The classic System 7 selection "blink": the highlighted row inverts on/off a
|
|
62
|
+
* few times (~250ms) before the action commits. Owns the shared timing
|
|
63
|
+
* ({@link BLINK_INTERVAL_MS}/{@link BLINK_FLIPS}) and the reduced-motion
|
|
64
|
+
* short-circuit so `vf-menu-item` and `vf-select` can't drift apart on either.
|
|
65
|
+
*
|
|
66
|
+
* `setPhase(on)` is invoked with the highlight state for each frame — the caller
|
|
67
|
+
* maps it onto its own representation (a CSS class, an option's `active` flag).
|
|
68
|
+
* The blink starts in the OFF phase and finishes by calling `onDone()`; under
|
|
69
|
+
* `prefers-reduced-motion` it skips straight to `onDone()` and returns a no-op
|
|
70
|
+
* handle. `setPhase` is never called after `onDone()`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function runSelectionBlink(setPhase: (on: boolean) => void, onDone: () => void): BlinkHandle;
|
package/dist/motion.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/motion.ts
|
|
2
|
+
function e() {
|
|
3
|
+
return typeof window < "u" && typeof window.matchMedia == "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
4
|
+
}
|
|
5
|
+
var t = 200, n = 800, r = 42, i = 6;
|
|
6
|
+
function a(t, n) {
|
|
7
|
+
if (e()) return n(), { cancel() {} };
|
|
8
|
+
let r = 0;
|
|
9
|
+
t(!1);
|
|
10
|
+
let i = window.setInterval(() => {
|
|
11
|
+
if (r += 1, r >= 6) {
|
|
12
|
+
window.clearInterval(i), n();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
t(r % 2 == 1);
|
|
16
|
+
}, 42);
|
|
17
|
+
return { cancel() {
|
|
18
|
+
window.clearInterval(i);
|
|
19
|
+
} };
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { i as BLINK_FLIPS, r as BLINK_INTERVAL_MS, t as PRESS_HOLD_MS, n as RENAME_DELAY_MS, e as prefersReducedMotion, a as runSelectionBlink };
|
package/dist/number.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared numeric helpers for the stepped controls (`vf-number-field`,
|
|
3
|
+
* `vf-slider`), which both snap a value to a `step` grid and then fix the
|
|
4
|
+
* floating-point residue with `toFixed()`.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Fraction digits implied by a `step`, i.e. the precision a stepped value
|
|
8
|
+
* should be rounded to.
|
|
9
|
+
*
|
|
10
|
+
* Naively counting the digits after `String(step)`'s decimal point is wrong for
|
|
11
|
+
* steps the runtime stringifies in exponential notation: `String(1e-7)` is
|
|
12
|
+
* `'1e-7'`, which has no dot at all, so a digit count reports 0 and
|
|
13
|
+
* `toFixed(0)` then snaps every stepped value to a whole number. Steps at or
|
|
14
|
+
* below `1e-7` (and, in the other direction, at or above `1e21`) take that
|
|
15
|
+
* form, so both branches are handled here:
|
|
16
|
+
*
|
|
17
|
+
* 0.25 → 2 1e-7 → 7
|
|
18
|
+
* 1 → 0 1.5e-7 → 8
|
|
19
|
+
* 0.001 → 3 1e21 → 0
|
|
20
|
+
*
|
|
21
|
+
* The result is clamped to `[0, MAX_DECIMALS]` so a subnormal step (down to
|
|
22
|
+
* `5e-324`) can't hand `toFixed()` an out-of-range argument.
|
|
23
|
+
*/
|
|
24
|
+
export declare function decimalsOf(step: number): number;
|
package/dist/number.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function e(e) {
|
|
2
|
+
if (!Number.isFinite(e)) return 0;
|
|
3
|
+
let t = String(Math.abs(e)), n = t.indexOf("e");
|
|
4
|
+
if (n < 0) {
|
|
5
|
+
let e = t.indexOf(".");
|
|
6
|
+
return e < 0 ? 0 : Math.min(100, t.length - e - 1);
|
|
7
|
+
}
|
|
8
|
+
let r = t.slice(0, n), i = Number(t.slice(n + 1)), a = r.indexOf("."), o = a < 0 ? 0 : r.length - a - 1;
|
|
9
|
+
return Math.min(100, Math.max(0, o - i));
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { e as decimalsOf };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Finder "open" ghost — an icon's art redrawn as outline plus dither,
|
|
3
|
+
* derived from the art itself in the client.
|
|
4
|
+
*
|
|
5
|
+
* When a folder or application is open, the Finder redraws its icon as a
|
|
6
|
+
* ghost: the outline stays, and everything inside it becomes pattern. The kit
|
|
7
|
+
* ships no second raster for that state and asks the consumer for none — a
|
|
8
|
+
* System 7 icon's alpha channel IS its mask resource, so both halves of the
|
|
9
|
+
* ghost are derivable from the art already slotted: the outline is the mask's
|
|
10
|
+
* boundary, the fill is the mask's interior.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is draw-and-composite — never `getImageData`, deliberately.
|
|
13
|
+
* A canvas that has drawn a cross-origin image is *tainted*, which forbids
|
|
14
|
+
* reading pixels back but not drawing or displaying them, so a pipeline built
|
|
15
|
+
* purely from `drawImage` and `globalCompositeOperation` behaves identically
|
|
16
|
+
* for data URIs, same-origin files and CORS-less CDN images. Taint propagates
|
|
17
|
+
* through the layers below and none of it matters: the result is only ever
|
|
18
|
+
* displayed. (`ctx.filter` is avoided the same way — compositing operators
|
|
19
|
+
* are universal where canvas filters are Safari's shaky corner.)
|
|
20
|
+
*
|
|
21
|
+
* The one "smart" step, finding the outline, is a 1px erosion, and erosion is
|
|
22
|
+
* expressible as compositing: `destination-in` keeps the destination only
|
|
23
|
+
* where the source is opaque, so intersecting the silhouette with itself
|
|
24
|
+
* shifted one pixel each way leaves exactly the pixels whose neighbors are
|
|
25
|
+
* all opaque — the interior. The ring the erosion removes is the outline.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Derive the open ghost from an icon's art: a canvas at the art's natural
|
|
29
|
+
* size holding its outline in solid black over an interior of dither on
|
|
30
|
+
* opaque white, with the transparent surround untouched.
|
|
31
|
+
*
|
|
32
|
+
* Opaque white matters as much as the black: it keeps the ghost exactly the
|
|
33
|
+
* shape selection expects — ink and opaque fill on a transparent surround —
|
|
34
|
+
* so `filter: invert(1)` produces the selected-open appearance with no
|
|
35
|
+
* second treatment.
|
|
36
|
+
*
|
|
37
|
+
* Returns null when there is nothing to derive: art with no pixels yet, or
|
|
38
|
+
* an environment refusing a 2d context.
|
|
39
|
+
*/
|
|
40
|
+
export declare function deriveOpenArt(art: HTMLImageElement | HTMLCanvasElement): HTMLCanvasElement | null;
|
package/dist/open-art.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/open-art.ts
|
|
2
|
+
var e = (e, t) => {
|
|
3
|
+
let n = document.createElement("canvas");
|
|
4
|
+
n.width = e, n.height = t;
|
|
5
|
+
let r = n.getContext("2d");
|
|
6
|
+
return r ? (r.imageSmoothingEnabled = !1, {
|
|
7
|
+
canvas: n,
|
|
8
|
+
ctx: r
|
|
9
|
+
}) : null;
|
|
10
|
+
}, t = null, n = () => {
|
|
11
|
+
if (!t) {
|
|
12
|
+
let n = e(4, 2);
|
|
13
|
+
if (!n) return null;
|
|
14
|
+
n.ctx.fillStyle = "#000", n.ctx.fillRect(0, 0, 1, 1), n.ctx.fillRect(2, 1, 1, 1), t = n.canvas;
|
|
15
|
+
}
|
|
16
|
+
return t;
|
|
17
|
+
};
|
|
18
|
+
function r(t) {
|
|
19
|
+
let r = t instanceof HTMLImageElement ? t.naturalWidth : t.width, i = t instanceof HTMLImageElement ? t.naturalHeight : t.height;
|
|
20
|
+
if (!r || !i) return null;
|
|
21
|
+
let a = e(r, i), o = e(r, i), s = n();
|
|
22
|
+
if (!a || !o || !s) return null;
|
|
23
|
+
a.ctx.drawImage(t, 0, 0), a.ctx.globalCompositeOperation = "source-in", a.ctx.fillStyle = "#000", a.ctx.fillRect(0, 0, r, i), o.ctx.drawImage(a.canvas, 0, 0), o.ctx.globalCompositeOperation = "destination-in";
|
|
24
|
+
for (let [e, t] of [
|
|
25
|
+
[1, 0],
|
|
26
|
+
[-1, 0],
|
|
27
|
+
[0, 1],
|
|
28
|
+
[0, -1]
|
|
29
|
+
]) o.ctx.drawImage(a.canvas, e, t);
|
|
30
|
+
o.ctx.globalCompositeOperation = "source-in", o.ctx.fillStyle = "#fff", o.ctx.fillRect(0, 0, r, i);
|
|
31
|
+
let c = o.ctx.createPattern(s, "repeat");
|
|
32
|
+
return c && (o.ctx.globalCompositeOperation = "source-atop", o.ctx.fillStyle = c, o.ctx.fillRect(0, 0, r, i)), a.ctx.globalCompositeOperation = "source-over", a.ctx.drawImage(o.canvas, 0, 0), a.canvas;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { r as deriveOpenArt };
|