vintage-frames 0.6.0 → 0.6.2
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 +1035 -24
- package/dist/components/vf-dialog.d.ts +9 -0
- package/dist/components/vf-number-field.js +1 -1
- package/dist/components/vf-scroll-area.js +9 -1
- package/dist/components/vf-text-field.d.ts +2 -1
- package/dist/components/vf-text-field.js +1 -1
- package/dist/components/vf-window.d.ts +20 -0
- package/dist/components/vf-window.js +43 -3
- package/dist/modal-dialog.d.ts +33 -0
- package/dist/modal-dialog.js +66 -36
- package/dist/position.d.ts +27 -8
- package/dist/position.js +74 -18
- package/dist/text-control.d.ts +8 -1
- package/dist/text-control.js +2 -7
- package/docs/SPEC.md +9 -8
- package/editor/vscode.html-custom-data.json +157 -2
- package/editor/web-types.json +317 -3
- package/package.json +1 -1
|
@@ -27,6 +27,15 @@ import './vf-button-group.js';
|
|
|
27
27
|
* closes it too, with `{ reason: 'outside' }` — for the About box; off by
|
|
28
28
|
* default, since the classic modal ignored an outside click.
|
|
29
29
|
*
|
|
30
|
+
* Keyboard, the classic Dialog Manager's two rules ({@link VfModalDialog}):
|
|
31
|
+
* on open, focus goes to the first text-entry control — a slotted control
|
|
32
|
+
* with `autofocus` first — or, with none, to the default button
|
|
33
|
+
* (`vf-button variant="default"`). Return or Enter activates the default
|
|
34
|
+
* button from anywhere in the dialog, a focused Cancel included; Space
|
|
35
|
+
* presses the focused control. A link keeps its own Enter, and in a
|
|
36
|
+
* multi-line editor Return inserts the newline while the keypad's Enter
|
|
37
|
+
* activates the button.
|
|
38
|
+
*
|
|
30
39
|
* @slot - Default slot: dialog body content.
|
|
31
40
|
* @slot buttons - Optional action buttons. Rendered as a bottom-right
|
|
32
41
|
* `vf-button-group` (equal-width, faces aligned); the footer only takes
|
|
@@ -26,7 +26,7 @@ var y = class extends r(l) {
|
|
|
26
26
|
this.#c(), this.pressed = null;
|
|
27
27
|
}, this.#d = (e) => {
|
|
28
28
|
if (this.isSubmitEnter(e)) {
|
|
29
|
-
this.requestImplicitSubmit();
|
|
29
|
+
this.requestImplicitSubmit() && e.preventDefault();
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
if (!(this.isDisabled || this.readonly) && !(e.isComposing || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) switch (e.key) {
|
|
@@ -115,11 +115,19 @@ var v = class extends r(d) {
|
|
|
115
115
|
fixed-width box, a wide image) grows the plane — precisely the
|
|
116
116
|
content that overflows sideways. It also makes this box the
|
|
117
117
|
containing block of a sticky child, so a sticky left: 0 strip holds
|
|
118
|
-
across the scroll.
|
|
118
|
+
across the scroll.
|
|
119
|
+
|
|
120
|
+
Never shorter than the viewport either: a fixed child (position.ts)
|
|
121
|
+
is a sticky box whose containing block is this plane, and a plane
|
|
122
|
+
shorter than the child's stated top clamps the child to the plane's
|
|
123
|
+
bottom edge (measured: top 100 over 50px of content landed at 50).
|
|
124
|
+
Content that fits still fits — the plane grows to the viewport, not
|
|
125
|
+
past it, so nothing overflows that didn't. */
|
|
119
126
|
.content {
|
|
120
127
|
position: relative;
|
|
121
128
|
width: fit-content;
|
|
122
129
|
min-width: 100%;
|
|
130
|
+
min-height: 100%;
|
|
123
131
|
}
|
|
124
132
|
/* Focusable so keyboard users can scroll; inset ring to stay in-box. */
|
|
125
133
|
.viewport:focus-visible {
|
|
@@ -40,7 +40,8 @@ export declare class VfTextField extends VfTextField_base {
|
|
|
40
40
|
/**
|
|
41
41
|
* Enter in a single-line field triggers the associated form's implicit
|
|
42
42
|
* submission. The native `<input>` is shadow-encapsulated, so its form owner
|
|
43
|
-
* is null and the browser won't do this itself.
|
|
43
|
+
* is null and the browser won't do this itself. A press the form took is
|
|
44
|
+
* cancelled, so an enclosing modal does not route it again.
|
|
44
45
|
*/
|
|
45
46
|
private handleKeydown;
|
|
46
47
|
protected render(): import("lit-html").TemplateResult<1>;
|
|
@@ -41,7 +41,7 @@ var d = class extends n(a) {
|
|
|
41
41
|
this.forwardedAttributes = [...a.forwardedAttributes, "pattern"];
|
|
42
42
|
}
|
|
43
43
|
handleKeydown(e) {
|
|
44
|
-
this.isSubmitEnter(e) && this.requestImplicitSubmit();
|
|
44
|
+
this.isSubmitEnter(e) && this.requestImplicitSubmit() && e.preventDefault();
|
|
45
45
|
}
|
|
46
46
|
render() {
|
|
47
47
|
return s`
|
|
@@ -30,6 +30,13 @@ declare const VfWindow_base: (new (...args: any[]) => import("../size.js").VfSiz
|
|
|
30
30
|
* says so once in the console.
|
|
31
31
|
*
|
|
32
32
|
* @slot - Default slot: window body content.
|
|
33
|
+
* @slot header - Optional header content — a band between the title bar and
|
|
34
|
+
* the body, the full width of the window (the Finder window's header
|
|
35
|
+
* line): a white interior over a 1px rule, no inset of its own, a
|
|
36
|
+
* positioning anchor for placed children. As tall as its content unless
|
|
37
|
+
* `header-height` states it. Under `scrollbars` the vertical rail begins
|
|
38
|
+
* below it, so the header spans the rail's column. Takes no space until
|
|
39
|
+
* populated.
|
|
33
40
|
* @slot status - Optional status-bar content — the classic bottom readout
|
|
34
41
|
* strip ("40px x 40px"): a 1px rule over a 15px white band under the body,
|
|
35
42
|
* body-face text on its native line. Takes no space until populated; a
|
|
@@ -40,6 +47,8 @@ declare const VfWindow_base: (new (...args: any[]) => import("../size.js").VfSiz
|
|
|
40
47
|
* @csspart close-box - The close widget (left).
|
|
41
48
|
* @csspart zoom-box - The zoom widget (right).
|
|
42
49
|
* @csspart body - The content area.
|
|
50
|
+
* @csspart header - The header strip between the title bar and the body
|
|
51
|
+
* (when the `header` slot is populated).
|
|
43
52
|
* @csspart status-bar - The bottom status strip (when the `status` slot is
|
|
44
53
|
* populated).
|
|
45
54
|
* @csspart grow-box - The resize widget (bottom-right, when `resizable`).
|
|
@@ -134,6 +143,15 @@ export declare class VfWindow extends VfWindow_base {
|
|
|
134
143
|
* placed inside the body.
|
|
135
144
|
*/
|
|
136
145
|
scrollbars?: 'vertical' | 'horizontal' | 'both';
|
|
146
|
+
/**
|
|
147
|
+
* The header's height in whole system px, rule included — the way every
|
|
148
|
+
* kit bar counts its rule (an 18px title bar is 17 + 1). Unset, the header
|
|
149
|
+
* is as tall as what is slotted into it, plus the rule. Only matters while
|
|
150
|
+
* the `header` slot is populated.
|
|
151
|
+
*/
|
|
152
|
+
headerHeight?: number | null;
|
|
153
|
+
/** Whether the `header` slot has assigned content (drives the header). */
|
|
154
|
+
private _hasHeader;
|
|
137
155
|
/** Whether the `status` slot has assigned content (drives the strip). */
|
|
138
156
|
private _hasStatus;
|
|
139
157
|
/** Default-on display scaling (true 72dpi size); see src/scale.ts. */
|
|
@@ -190,6 +208,8 @@ export declare class VfWindow extends VfWindow_base {
|
|
|
190
208
|
disconnectedCallback(): void;
|
|
191
209
|
/** The `.empty` gate: the strip renders only while the slot is populated. */
|
|
192
210
|
private _onStatusSlotChange;
|
|
211
|
+
/** The header's own gate, on the same terms. */
|
|
212
|
+
private _onHeaderSlotChange;
|
|
193
213
|
private _onCloseClick;
|
|
194
214
|
private _onZoomClick;
|
|
195
215
|
private _onGrowPointerDown;
|
|
@@ -25,7 +25,7 @@ function V(e, t, n) {
|
|
|
25
25
|
}
|
|
26
26
|
var H = 24, U = 8, W = class extends E(c(N)) {
|
|
27
27
|
constructor(...e) {
|
|
28
|
-
super(...e), this.heading = "", this.active = !0, this.closable = !0, this.zoomable = !1, this.movable = !1, this.resizable = !1, this._hasStatus = !1, this.scale = new n(this), this.gridSnap = new T(this), this.titleCenter = new O(this), this._dotsPattern = "", this.#e = new x(), this.#t = (e, t, n) => {
|
|
28
|
+
super(...e), this.heading = "", this.active = !0, this.closable = !0, this.zoomable = !1, this.movable = !1, this.resizable = !1, this._hasHeader = !1, this._hasStatus = !1, this.scale = new n(this), this.gridSnap = new T(this), this.titleCenter = new O(this), this._dotsPattern = "", this.#e = new x(), this.#t = (e, t, n) => {
|
|
29
29
|
let r = a(this.offsetWidth, this);
|
|
30
30
|
return {
|
|
31
31
|
x: Math.min(Math.max(e, H - r), n.width - H),
|
|
@@ -204,13 +204,39 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
204
204
|
and a resizable window's grow box (z-index 1) lands over the rail
|
|
205
205
|
corner cell — which the render reserves on a single-axis rail too
|
|
206
206
|
(the area's corner flag), except when the status strip holds the
|
|
207
|
-
grow box instead.
|
|
207
|
+
grow box instead. The same overhang lands the area's top frame line
|
|
208
|
+
on the header's rule, so the vertical rail's top arrow begins
|
|
209
|
+
under the header. */
|
|
208
210
|
.edge-scroll {
|
|
209
211
|
width: calc(100% + var(--vf-scale, 1) * 2px);
|
|
210
212
|
height: calc(100% + var(--vf-scale, 1) * 2px);
|
|
211
213
|
margin: calc(var(--vf-scale, 1) * -1px);
|
|
212
214
|
}
|
|
213
215
|
|
|
216
|
+
/* --- Header (slot="header") --------------------------------------- */
|
|
217
|
+
/* A band between the title bar and the body, the full width of the
|
|
218
|
+
window — the Finder window's header line: a white interior over a
|
|
219
|
+
1px rule (vfRule's vf-rule-bottom on the element). Like the body it
|
|
220
|
+
carries no inset and is a positioning anchor: (0,0) is the header's
|
|
221
|
+
own corner, flow content starts there too, an inset is the content's.
|
|
222
|
+
As tall as its content unless header-height states it — rule
|
|
223
|
+
included, the way every kit bar counts its rule (an 18px title bar
|
|
224
|
+
is 17 + 1). Under the scrollbars parameter the edge rails sit in the
|
|
225
|
+
body below it, so the header spans the rail's column and the
|
|
226
|
+
vertical rail's arrows begin under it. Takes no space until the slot
|
|
227
|
+
is populated. Clipped like the body; a drop-open panel still escapes
|
|
228
|
+
(see the body's note). A div with a class, never a <header>: that
|
|
229
|
+
element maps to a banner landmark (see render()). */
|
|
230
|
+
.header {
|
|
231
|
+
position: relative;
|
|
232
|
+
flex: none;
|
|
233
|
+
background: var(--vf-white, #ffffff);
|
|
234
|
+
overflow: hidden;
|
|
235
|
+
}
|
|
236
|
+
.header.empty {
|
|
237
|
+
display: none;
|
|
238
|
+
}
|
|
239
|
+
|
|
214
240
|
/* --- Status bar (slot="status") ---------------------------------- */
|
|
215
241
|
/* The classic bottom readout strip: a 1px rule over a white interior,
|
|
216
242
|
15px in all — the grow box's own height, so a resizable window's
|
|
@@ -329,6 +355,10 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
329
355
|
let t = e.target;
|
|
330
356
|
this._hasStatus = t.assignedElements().length > 0;
|
|
331
357
|
}
|
|
358
|
+
_onHeaderSlotChange(e) {
|
|
359
|
+
let t = e.target;
|
|
360
|
+
this._hasHeader = t.assignedElements().length > 0;
|
|
361
|
+
}
|
|
332
362
|
_onCloseClick() {
|
|
333
363
|
e(this, "vf-close", { reason: "close" });
|
|
334
364
|
}
|
|
@@ -378,6 +408,13 @@ var H = 24, U = 8, W = class extends E(c(N)) {
|
|
|
378
408
|
<span class="vf-title" part="title" id="title">${this.heading}</span>
|
|
379
409
|
${this.zoomable ? M(j("Zoom", this.heading), this._onZoomClick) : I}
|
|
380
410
|
`, this.variant === "utility" ? "vf-dots" : "vf-stripes", this.variant === "utility" ? this._dotsTexture() : void 0)}
|
|
411
|
+
<div
|
|
412
|
+
class="header vf-rule-bottom ${this._hasHeader ? "" : "empty"}"
|
|
413
|
+
part="header"
|
|
414
|
+
style=${this.headerHeight == null ? I : `height:${i(this.headerHeight)}`}
|
|
415
|
+
>
|
|
416
|
+
<slot name="header" @slotchange=${this._onHeaderSlotChange}></slot>
|
|
417
|
+
</div>
|
|
381
418
|
<div class="body" part="body">
|
|
382
419
|
${this.scrollbars ? F`
|
|
383
420
|
<vf-scroll-area
|
|
@@ -436,6 +473,9 @@ o([L({ reflect: !0 })], W.prototype, "variant", void 0), o([L()], W.prototype, "
|
|
|
436
473
|
})], W.prototype, "maxWidth", void 0), o([L({
|
|
437
474
|
type: Number,
|
|
438
475
|
attribute: "max-height"
|
|
439
|
-
})], W.prototype, "maxHeight", void 0), o([L({ reflect: !0 })], W.prototype, "scrollbars", void 0), o([
|
|
476
|
+
})], W.prototype, "maxHeight", void 0), o([L({ reflect: !0 })], W.prototype, "scrollbars", void 0), o([L({
|
|
477
|
+
type: Number,
|
|
478
|
+
attribute: "header-height"
|
|
479
|
+
})], W.prototype, "headerHeight", void 0), o([R()], W.prototype, "_hasHeader", void 0), o([R()], W.prototype, "_hasStatus", void 0), W = o([t("vf-window")], W);
|
|
440
480
|
//#endregion
|
|
441
481
|
export { W as VfWindow };
|
package/dist/modal-dialog.d.ts
CHANGED
|
@@ -32,6 +32,12 @@ export declare const modalDialogStyles: import("lit").CSSResult;
|
|
|
32
32
|
* event, an Escape-close no longer leaves a stale origin behind, so the next
|
|
33
33
|
* open re-derives it.
|
|
34
34
|
*
|
|
35
|
+
* It also owns the two keyboard rules every classic dialog followed (see
|
|
36
|
+
* {@link defaultButton} and {@link initialFocusTarget}): Return or Enter
|
|
37
|
+
* activates the default button from anywhere in the dialog, and the box opens
|
|
38
|
+
* with the insertion point in its first text field — or, with none, focus on
|
|
39
|
+
* the default button.
|
|
40
|
+
*
|
|
35
41
|
* Removing an open modal from the DOM is a close path too. HTML's dialog
|
|
36
42
|
* *removing steps* take the element out of the top layer **without** running
|
|
37
43
|
* the close algorithm — no `close` event, no focus restoration — which is
|
|
@@ -124,6 +130,33 @@ export declare class VfModalDialog extends LitElement {
|
|
|
124
130
|
* drag from squeezing the box.
|
|
125
131
|
*/
|
|
126
132
|
protected get dialogSize(): Record<string, string | null>;
|
|
133
|
+
/**
|
|
134
|
+
* The dialog's default button — the one wearing the bold ring
|
|
135
|
+
* (`vf-button[variant="default"]`) — or null with none, or none enabled.
|
|
136
|
+
* Looked for among the slotted content first, then in the shadow tree, so a
|
|
137
|
+
* consumer's own modal that renders its buttons itself is covered too.
|
|
138
|
+
* Override to name it some other way.
|
|
139
|
+
*/
|
|
140
|
+
protected get defaultButton(): HTMLElement | null;
|
|
141
|
+
/**
|
|
142
|
+
* Where focus lands on open. The classic Dialog Manager put the insertion
|
|
143
|
+
* point in the first editable text item and gave nothing else focus at all;
|
|
144
|
+
* the web needs a focused control for the keyboard to have anywhere to be,
|
|
145
|
+
* so with no text field the default button takes it (which is also what
|
|
146
|
+
* makes Return and Space work there without a Tab). In order:
|
|
147
|
+
*
|
|
148
|
+
* 1. a slotted control carrying `autofocus` — the author's say;
|
|
149
|
+
* 2. the first enabled text-entry control (`vf-text-field`,
|
|
150
|
+
* `vf-number-field`, `vf-text-area`, or a native text input/textarea);
|
|
151
|
+
* 3. the {@link defaultButton};
|
|
152
|
+
* 4. null — leave the browser's own choice alone.
|
|
153
|
+
*
|
|
154
|
+
* Left to itself, `showModal()` focuses the first focusable thing in flat
|
|
155
|
+
* tree order, which for a Cancel/OK row is Cancel, and for a body with a
|
|
156
|
+
* link in it is the link — so Return did the one thing a classic dialog's
|
|
157
|
+
* Return never did. Override to choose differently.
|
|
158
|
+
*/
|
|
159
|
+
protected get initialFocusTarget(): HTMLElement | null;
|
|
127
160
|
/** Open the modal (native `showModal()`), pinned onto the device grid. */
|
|
128
161
|
show(): void;
|
|
129
162
|
/** Close the modal. Fires `vf-close` with `{ reason: 'close' }`. */
|
package/dist/modal-dialog.js
CHANGED
|
@@ -4,7 +4,15 @@ import o from "./_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js"
|
|
|
4
4
|
import { DocumentListenersController as s } from "./document-listeners.js";
|
|
5
5
|
import { LitElement as c, css as l } from "lit";
|
|
6
6
|
import { property as u, query as d } from "lit/decorators.js";
|
|
7
|
-
var f = 24, p =
|
|
7
|
+
var f = 24, p = /* @__PURE__ */ new Set([
|
|
8
|
+
"text",
|
|
9
|
+
"search",
|
|
10
|
+
"url",
|
|
11
|
+
"tel",
|
|
12
|
+
"email",
|
|
13
|
+
"password",
|
|
14
|
+
"number"
|
|
15
|
+
]), m = (e) => e instanceof HTMLInputElement ? p.has(e.type) : !0, h = l`
|
|
8
16
|
dialog {
|
|
9
17
|
padding: 0;
|
|
10
18
|
margin: auto;
|
|
@@ -43,32 +51,43 @@ var f = 24, p = l`
|
|
|
43
51
|
the fallback is the arrow it always showed. */
|
|
44
52
|
cursor: var(--vf-cursor, default);
|
|
45
53
|
}
|
|
46
|
-
`,
|
|
54
|
+
`, g = class extends c {
|
|
47
55
|
constructor(...e) {
|
|
48
56
|
super(...e), this.scale = new t(this), this.open = !1, this.lightDismiss = !1, this.#e = !1, this.#n = null, this.#r = null, this.#i = new s(this, () => [
|
|
49
57
|
[
|
|
50
58
|
this._dialog,
|
|
51
59
|
"pointerdown",
|
|
52
|
-
this.#
|
|
60
|
+
this.#s
|
|
53
61
|
],
|
|
54
62
|
[
|
|
55
63
|
this._dialog,
|
|
56
64
|
"pointerup",
|
|
57
|
-
this.#
|
|
65
|
+
this.#c
|
|
58
66
|
],
|
|
59
67
|
[
|
|
60
68
|
this._dialog,
|
|
61
69
|
"pointercancel",
|
|
62
|
-
this.#
|
|
70
|
+
this.#l
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
this._dialog,
|
|
74
|
+
"keydown",
|
|
75
|
+
this.#o
|
|
63
76
|
]
|
|
64
|
-
]), this.#
|
|
77
|
+
]), this.#o = (e) => {
|
|
78
|
+
if (e.key !== "Enter" || e.defaultPrevented || e.isComposing || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
79
|
+
let t = e.composedPath();
|
|
80
|
+
if (t.some((e) => e instanceof HTMLAnchorElement && e.hasAttribute("href")) || t.some((e) => e instanceof HTMLTextAreaElement || e instanceof HTMLElement && e.isContentEditable) && e.code !== "NumpadEnter") return;
|
|
81
|
+
let n = this.defaultButton;
|
|
82
|
+
n && (e.preventDefault(), n.click());
|
|
83
|
+
}, this.#s = (e) => {
|
|
65
84
|
this.#r = e.target === this._dialog ? e.pointerId : null;
|
|
66
|
-
}, this.#
|
|
85
|
+
}, this.#c = (e) => {
|
|
67
86
|
let t = this.#r === e.pointerId;
|
|
68
87
|
this.#r = null, !(!t || e.target !== this._dialog || !this.lightDismiss) && (this.#n = "outside", this.close());
|
|
69
|
-
}, this.#
|
|
88
|
+
}, this.#l = () => {
|
|
70
89
|
this.#r = null;
|
|
71
|
-
}, this.#
|
|
90
|
+
}, this.#u = null, this.#g = () => this.settle();
|
|
72
91
|
}
|
|
73
92
|
get dialogSize() {
|
|
74
93
|
return {
|
|
@@ -88,42 +107,53 @@ var f = 24, p = l`
|
|
|
88
107
|
#n;
|
|
89
108
|
#r;
|
|
90
109
|
#i;
|
|
91
|
-
|
|
110
|
+
get defaultButton() {
|
|
111
|
+
let e = "vf-button[variant=\"default\"]";
|
|
112
|
+
return [...this.querySelectorAll(e), ...this.renderRoot.querySelectorAll(e)].find((e) => !e.matches(":disabled")) ?? null;
|
|
113
|
+
}
|
|
114
|
+
get initialFocusTarget() {
|
|
115
|
+
return this.querySelector("[autofocus]") || ([...this.querySelectorAll("vf-text-field, vf-number-field, vf-text-area, textarea, input")].find((e) => m(e) && !e.matches(":disabled")) ?? this.defaultButton);
|
|
116
|
+
}
|
|
117
|
+
#a() {
|
|
118
|
+
this.initialFocusTarget?.focus();
|
|
119
|
+
}
|
|
92
120
|
#o;
|
|
93
121
|
#s;
|
|
94
122
|
#c;
|
|
123
|
+
#l;
|
|
124
|
+
#u;
|
|
95
125
|
show() {
|
|
96
|
-
this.open = !0, this.hasUpdated && this.#
|
|
126
|
+
this.open = !0, this.hasUpdated && this.#h();
|
|
97
127
|
}
|
|
98
128
|
close() {
|
|
99
|
-
this.open = !1, this.hasUpdated && this.#
|
|
129
|
+
this.open = !1, this.hasUpdated && this.#h();
|
|
100
130
|
}
|
|
101
131
|
updated(e) {
|
|
102
|
-
e.has("open") && this.#
|
|
132
|
+
e.has("open") && this.#h(), (e.has("top") || e.has("left")) && this.settle(), this.#t();
|
|
103
133
|
}
|
|
104
|
-
#
|
|
134
|
+
#d() {
|
|
105
135
|
let e = document.documentElement;
|
|
106
136
|
return {
|
|
107
137
|
width: a(e.clientWidth || window.innerWidth, this),
|
|
108
138
|
height: a(e.clientHeight || window.innerHeight, this)
|
|
109
139
|
};
|
|
110
140
|
}
|
|
111
|
-
#
|
|
112
|
-
let n = this.#
|
|
141
|
+
#f(e, t) {
|
|
142
|
+
let n = this.#d(), r = a(this._dialog?.offsetWidth ?? 0, this);
|
|
113
143
|
return {
|
|
114
144
|
x: Math.min(Math.max(e, f - r), n.width - f),
|
|
115
145
|
y: Math.min(Math.max(t, 0), Math.max(0, n.height - f))
|
|
116
146
|
};
|
|
117
147
|
}
|
|
118
|
-
#
|
|
119
|
-
let e = this.#
|
|
148
|
+
#p() {
|
|
149
|
+
let e = this.#d(), t = this._dialog?.getBoundingClientRect();
|
|
120
150
|
return {
|
|
121
151
|
x: (e.width - a(t?.width ?? 0, this)) / 2,
|
|
122
152
|
y: (e.height - a(t?.height ?? 0, this)) / 2
|
|
123
153
|
};
|
|
124
154
|
}
|
|
125
155
|
placeAt(e, t) {
|
|
126
|
-
let n = this.#
|
|
156
|
+
let n = this.#f(e, t);
|
|
127
157
|
this.left = r(n.x, this), this.top = r(n.y, this);
|
|
128
158
|
}
|
|
129
159
|
settle() {
|
|
@@ -132,10 +162,10 @@ var f = 24, p = l`
|
|
|
132
162
|
let t = this.left != null || this.top != null ? {
|
|
133
163
|
x: this.left ?? 0,
|
|
134
164
|
y: this.top ?? 0
|
|
135
|
-
} : this.#
|
|
165
|
+
} : this.#p(), n = this.#f(t.x, t.y);
|
|
136
166
|
e.style.left = i(r(n.x, this)), e.style.top = i(r(n.y, this)), e.style.right = "auto", e.style.bottom = "auto", e.style.margin = "0";
|
|
137
167
|
}
|
|
138
|
-
#
|
|
168
|
+
#m() {
|
|
139
169
|
let e = this._dialog?.style;
|
|
140
170
|
if (e) for (let t of [
|
|
141
171
|
"left",
|
|
@@ -145,44 +175,44 @@ var f = 24, p = l`
|
|
|
145
175
|
"margin"
|
|
146
176
|
]) e.removeProperty(t);
|
|
147
177
|
}
|
|
148
|
-
#
|
|
178
|
+
#h() {
|
|
149
179
|
let e = this._dialog;
|
|
150
|
-
e && (this.open && !e.open ? (this.#
|
|
180
|
+
e && (this.open && !e.open ? (this.#u = document.activeElement, e.showModal(), this.#a(), this.settle(), this.#y(e), this.#i.attach()) : !this.open && e.open && e.close());
|
|
151
181
|
}
|
|
152
|
-
#m;
|
|
153
|
-
#h;
|
|
154
182
|
#g;
|
|
155
|
-
#_
|
|
156
|
-
|
|
183
|
+
#_;
|
|
184
|
+
#v;
|
|
185
|
+
#y(e) {
|
|
186
|
+
typeof ResizeObserver < "u" && !this.#_ && (this.#_ = new ResizeObserver(this.#g), this.#_.observe(e)), window.addEventListener("resize", this.#g), this.#v ??= n(this.#g);
|
|
157
187
|
}
|
|
158
|
-
#
|
|
159
|
-
this.#
|
|
188
|
+
#b() {
|
|
189
|
+
this.#_?.disconnect(), this.#_ = void 0, window.removeEventListener("resize", this.#g), this.#v?.(), this.#v = void 0;
|
|
160
190
|
}
|
|
161
191
|
disconnectedCallback() {
|
|
162
|
-
super.disconnectedCallback(), this.#
|
|
192
|
+
super.disconnectedCallback(), this.#b();
|
|
163
193
|
let e = this._dialog;
|
|
164
194
|
if (e?.open) {
|
|
165
195
|
e.close();
|
|
166
|
-
let t = this.#
|
|
196
|
+
let t = this.#u, n = document.activeElement;
|
|
167
197
|
t instanceof HTMLElement && t.isConnected && (n === null || n === document.body) && t.focus();
|
|
168
198
|
}
|
|
169
|
-
this.#
|
|
199
|
+
this.#u = null;
|
|
170
200
|
}
|
|
171
201
|
_onNativeCancel() {
|
|
172
202
|
this.#n = "escape";
|
|
173
203
|
}
|
|
174
204
|
_onNativeClose() {
|
|
175
205
|
let t = this.#n ?? "close";
|
|
176
|
-
this.#n = null, this.#
|
|
206
|
+
this.#n = null, this.#u = null, this.#r = null, this.#i.detach(), this.#b(), this.open = !1, this.#m(), e(this, "vf-close", { reason: t });
|
|
177
207
|
}
|
|
178
208
|
};
|
|
179
209
|
o([u({
|
|
180
210
|
type: Boolean,
|
|
181
211
|
reflect: !0
|
|
182
|
-
})],
|
|
212
|
+
})], g.prototype, "open", void 0), o([u({ type: Number })], g.prototype, "width", void 0), o([u({ type: Number })], g.prototype, "height", void 0), o([u({ type: Number })], g.prototype, "top", void 0), o([u({ type: Number })], g.prototype, "left", void 0), o([u({
|
|
183
213
|
type: Boolean,
|
|
184
214
|
reflect: !0,
|
|
185
215
|
attribute: "light-dismiss"
|
|
186
|
-
})],
|
|
216
|
+
})], g.prototype, "lightDismiss", void 0), o([d("dialog")], g.prototype, "_dialog", void 0);
|
|
187
217
|
//#endregion
|
|
188
|
-
export {
|
|
218
|
+
export { g as VfModalDialog, h as modalDialogStyles };
|
package/dist/position.d.ts
CHANGED
|
@@ -9,7 +9,14 @@ type Constructor<T = object> = new (...args: any[]) => T;
|
|
|
9
9
|
export declare abstract class VfPositionedInterface extends LitElement {
|
|
10
10
|
top?: number | null;
|
|
11
11
|
left?: number | null;
|
|
12
|
+
fixed: boolean;
|
|
12
13
|
}
|
|
14
|
+
/** A host that takes the placement trio. */
|
|
15
|
+
type PositionedHost = HTMLElement & {
|
|
16
|
+
top?: number | null;
|
|
17
|
+
left?: number | null;
|
|
18
|
+
fixed?: boolean;
|
|
19
|
+
};
|
|
13
20
|
/**
|
|
14
21
|
* Explicit placement — `top`/`left` in whole system px, on **every** component.
|
|
15
22
|
*
|
|
@@ -76,6 +83,24 @@ export declare abstract class VfPositionedInterface extends LitElement {
|
|
|
76
83
|
* element. The controller re-applies **only when the values changed**, so an
|
|
77
84
|
* unrelated update — a heading change, a desktop toggling `active` — never
|
|
78
85
|
* re-asserts a coordinate and costs nothing.
|
|
86
|
+
*
|
|
87
|
+
* **`fixed`** holds the placement against the *visible* region of the nearest
|
|
88
|
+
* scrolling ancestor instead of its scrolled plane — a tool strip over a
|
|
89
|
+
* document, a column header over rows — and the flag alone places at (0,0).
|
|
90
|
+
* The engine underneath is `position: sticky`, the one way the platform holds
|
|
91
|
+
* a box against a scrollport from inside it on the compositor (a scroll
|
|
92
|
+
* listener counter-translating lags a frame; a layer outside the scroller
|
|
93
|
+
* stops wheel and touch over it). Sticky boxes stay in flow, which is not
|
|
94
|
+
* what a placement is, so the controller erases the footprint: the host is
|
|
95
|
+
* blockified (no line box), shrink-wrapped, and given a negative right/bottom
|
|
96
|
+
* margin equal to its own box — a 0×0 margin box that flow content lays out
|
|
97
|
+
* as if it weren't there, measured by a ResizeObserver so a relabel or a zoom
|
|
98
|
+
* step keeps it exact. `z-index: 1` puts it over the plane's placed children,
|
|
99
|
+
* which is where a fixed strip belongs. Two facts of the engine that the
|
|
100
|
+
* docs state as rules: sticky only ever pushes a box *down* from where the
|
|
101
|
+
* flow put it, so a fixed child comes before the flow content in its parent;
|
|
102
|
+
* and a scroll container that never scrolls (a plain window body is
|
|
103
|
+
* `overflow: hidden`) holds it exactly where placement would.
|
|
79
104
|
*/
|
|
80
105
|
export declare const VfPositioned: <T extends Constructor<LitElement>>(Base: T) => Constructor<VfPositionedInterface> & T;
|
|
81
106
|
/** The box a gesture is clamped into, in system px — see {@link PlacementClamp}. */
|
|
@@ -127,10 +152,7 @@ export type PlacementClamp = (x: number, y: number, bounds: PlacementBounds) =>
|
|
|
127
152
|
*/
|
|
128
153
|
export declare class PlacementController {
|
|
129
154
|
#private;
|
|
130
|
-
constructor(host:
|
|
131
|
-
top?: number | null;
|
|
132
|
-
left?: number | null;
|
|
133
|
-
}, clamp: PlacementClamp);
|
|
155
|
+
constructor(host: PositionedHost, clamp: PlacementClamp);
|
|
134
156
|
/** Whether a gesture has placed this host (as opposed to markup or CSS). */
|
|
135
157
|
get placed(): boolean;
|
|
136
158
|
/**
|
|
@@ -189,8 +211,5 @@ export declare class PlacementController {
|
|
|
189
211
|
* Returns whether it warned, so the caller can latch — one warning per element,
|
|
190
212
|
* not per render.
|
|
191
213
|
*/
|
|
192
|
-
export declare function warnMovableContract(host:
|
|
193
|
-
top?: number | null;
|
|
194
|
-
left?: number | null;
|
|
195
|
-
}, what: string, example: string): boolean;
|
|
214
|
+
export declare function warnMovableContract(host: PositionedHost, what: string, example: string): boolean;
|
|
196
215
|
export {};
|
package/dist/position.js
CHANGED
|
@@ -2,31 +2,83 @@ import { snapSys as e, sysLength as t, toSysExact as n } from "./scale.js";
|
|
|
2
2
|
import r from "./_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
|
|
3
3
|
import { property as i } from "lit/decorators.js";
|
|
4
4
|
//#region src/position.ts
|
|
5
|
-
var a =
|
|
5
|
+
var a = {
|
|
6
|
+
inline: "block",
|
|
7
|
+
"inline-block": "block",
|
|
8
|
+
"inline-flex": "flex",
|
|
9
|
+
"inline-grid": "grid",
|
|
10
|
+
"inline-table": "table"
|
|
11
|
+
}, o = (e) => {
|
|
6
12
|
class t extends e {
|
|
7
13
|
constructor(...e) {
|
|
8
|
-
super(...e), new
|
|
14
|
+
super(...e), this.fixed = !1, new s(this);
|
|
9
15
|
}
|
|
10
16
|
}
|
|
11
|
-
return r([i({ type: Number })], t.prototype, "top", void 0), r([i({ type: Number })], t.prototype, "left", void 0),
|
|
12
|
-
|
|
17
|
+
return r([i({ type: Number })], t.prototype, "top", void 0), r([i({ type: Number })], t.prototype, "left", void 0), r([i({
|
|
18
|
+
type: Boolean,
|
|
19
|
+
reflect: !0
|
|
20
|
+
})], t.prototype, "fixed", void 0), t;
|
|
21
|
+
}, s = class {
|
|
13
22
|
#e;
|
|
14
23
|
#t = null;
|
|
15
24
|
#n = null;
|
|
16
25
|
#r = !1;
|
|
26
|
+
#i = !1;
|
|
27
|
+
#a = null;
|
|
17
28
|
constructor(e) {
|
|
18
29
|
this.#e = e, e.addController(this);
|
|
19
30
|
}
|
|
31
|
+
hostConnected() {
|
|
32
|
+
this.#i && this.#r && this.#c();
|
|
33
|
+
}
|
|
34
|
+
hostDisconnected() {
|
|
35
|
+
this.#a?.disconnect(), this.#a = null;
|
|
36
|
+
}
|
|
20
37
|
hostUpdated() {
|
|
21
|
-
let e = this.#e.top ?? null, n = this.#e.left ?? null, r = this.#e.style;
|
|
22
|
-
if (e === null && n === null) {
|
|
23
|
-
if (!this.#
|
|
24
|
-
this.#r = !1, r.removeProperty("position"),
|
|
38
|
+
let e = this.#e.top ?? null, n = this.#e.left ?? null, r = this.#e.fixed === !0, i = this.#e.style;
|
|
39
|
+
if (e === null && n === null && !r) {
|
|
40
|
+
if (!this.#i) return;
|
|
41
|
+
this.#r && this.#s(), this.#i = !1, this.#r = !1, i.removeProperty("position"), i.removeProperty("top"), i.removeProperty("left"), i.removeProperty("right"), i.removeProperty("bottom"), i.removeProperty("margin");
|
|
25
42
|
return;
|
|
26
43
|
}
|
|
27
|
-
this.#
|
|
44
|
+
if (this.#i && e === this.#t && n === this.#n && r === this.#r) return;
|
|
45
|
+
let a = this.#i && this.#r;
|
|
46
|
+
this.#i = !0, this.#t = e, this.#n = n, this.#r = r, i.position = r ? "sticky" : "absolute", i.top = t(e ?? 0), i.left = t(n ?? 0), i.right = "auto", i.bottom = "auto", r ? a || this.#o() : (a && this.#s(), i.margin = "0");
|
|
28
47
|
}
|
|
29
|
-
|
|
48
|
+
#o() {
|
|
49
|
+
let e = this.#e.style;
|
|
50
|
+
e.maxWidth = "fit-content", e.zIndex = "1", this.#l = !1;
|
|
51
|
+
let t = this.#e.getBoundingClientRect();
|
|
52
|
+
(t.width > 0 || t.height > 0) && this.#u(t.width, t.height), this.#c();
|
|
53
|
+
}
|
|
54
|
+
#s() {
|
|
55
|
+
this.#a?.disconnect(), this.#a = null, this.#l = !1;
|
|
56
|
+
let e = this.#e.style;
|
|
57
|
+
e.removeProperty("display"), e.removeProperty("max-width"), e.removeProperty("z-index");
|
|
58
|
+
}
|
|
59
|
+
#c() {
|
|
60
|
+
this.#a ??= new ResizeObserver((e) => {
|
|
61
|
+
let t = e[e.length - 1]?.borderBoxSize?.[0];
|
|
62
|
+
if (t) this.#u(t.inlineSize, t.blockSize);
|
|
63
|
+
else {
|
|
64
|
+
let e = this.#e.getBoundingClientRect();
|
|
65
|
+
this.#u(e.width, e.height);
|
|
66
|
+
}
|
|
67
|
+
}), this.#a.observe(this.#e);
|
|
68
|
+
}
|
|
69
|
+
#l = !1;
|
|
70
|
+
#u(e, t) {
|
|
71
|
+
let n = this.#e.style;
|
|
72
|
+
if (!this.#l) {
|
|
73
|
+
let e = getComputedStyle(this.#e).display;
|
|
74
|
+
if (e) {
|
|
75
|
+
let t = a[e];
|
|
76
|
+
t && (n.display = t), this.#l = !0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
n.margin = `0 ${-e}px ${-t}px 0`;
|
|
80
|
+
}
|
|
81
|
+
}, c = class {
|
|
30
82
|
#e;
|
|
31
83
|
#t;
|
|
32
84
|
#n = !1;
|
|
@@ -43,7 +95,7 @@ var a = (e) => {
|
|
|
43
95
|
x: t.left ?? 0,
|
|
44
96
|
y: t.top ?? 0
|
|
45
97
|
};
|
|
46
|
-
let r = getComputedStyle(t), i =
|
|
98
|
+
let r = getComputedStyle(t), i = l.has(r.position), a = i ? parseFloat(r.left) || 0 : t.offsetLeft, o = i ? parseFloat(r.top) || 0 : t.offsetTop;
|
|
47
99
|
return {
|
|
48
100
|
x: e(n(a, t), t),
|
|
49
101
|
y: e(n(o, t), t)
|
|
@@ -60,15 +112,19 @@ var a = (e) => {
|
|
|
60
112
|
height: n(r?.clientHeight ?? window.innerHeight, e)
|
|
61
113
|
};
|
|
62
114
|
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
115
|
+
}, l = /* @__PURE__ */ new Set([
|
|
116
|
+
"absolute",
|
|
117
|
+
"fixed",
|
|
118
|
+
"sticky"
|
|
119
|
+
]);
|
|
120
|
+
function u(e, t, n) {
|
|
121
|
+
let r = e.top != null || e.left != null || e.fixed === !0, i = l.has(getComputedStyle(e).position), a = null;
|
|
122
|
+
if (!r && !i) a = "unplaced";
|
|
67
123
|
else {
|
|
68
124
|
let t = e.offsetParent;
|
|
69
|
-
t && t !== e.ownerDocument.body && (t.clientWidth === 0 || t.clientHeight === 0) && (
|
|
125
|
+
t && t !== e.ownerDocument.body && (t.clientWidth === 0 || t.clientHeight === 0) && (a = "unsized-parent");
|
|
70
126
|
}
|
|
71
|
-
return
|
|
127
|
+
return a !== null && (console.warn(a === "unplaced" ? `${t}: movable, but in normal flow — so the first drag has to pull it out, reflowing everything after it on the page. State the origin in system px and it never was in flow: ${n}` : `${t}: the positioning parent has no box, so there is nowhere to drag to. Give it a size (and \`position: relative\`, if it is not a kit container) — the clamp is falling back to the viewport meanwhile.`), !0);
|
|
72
128
|
}
|
|
73
129
|
//#endregion
|
|
74
|
-
export {
|
|
130
|
+
export { c as PlacementController, o as VfPositioned, u as warnMovableContract };
|