vintage-frames 0.14.1 → 0.15.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.
@@ -1,10 +1,10 @@
1
- import { emit as e } from "./events.js";
2
- import { ScaleController as t, onScaleChange as n, snapSys as r, sysLength as i, toSysExact as a } from "./scale.js";
3
- import o from "./_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
- import { DocumentListenersController as s } from "./document-listeners.js";
5
- import { LitElement as c, css as l } from "lit";
6
- import { property as u, query as d } from "lit/decorators.js";
7
- var f = 24, p = /* @__PURE__ */ new Set([
1
+ import { deferActivation as e, emit as t } from "./events.js";
2
+ import { ScaleController as n, onScaleChange as r, snapSys as i, sysLength as a, toSysExact as o } from "./scale.js";
3
+ import s from "./_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
4
+ import { DocumentListenersController as c } from "./document-listeners.js";
5
+ import { LitElement as l, css as u } from "lit";
6
+ import { property as d, query as f } from "lit/decorators.js";
7
+ var p = 24, m = /* @__PURE__ */ new Set([
8
8
  "text",
9
9
  "search",
10
10
  "url",
@@ -12,7 +12,7 @@ var f = 24, p = /* @__PURE__ */ new Set([
12
12
  "email",
13
13
  "password",
14
14
  "number"
15
- ]), m = (e) => e instanceof HTMLInputElement ? p.has(e.type) : !0, h = l`
15
+ ]), h = (e) => e instanceof HTMLInputElement ? m.has(e.type) : !0, g = u`
16
16
  dialog {
17
17
  padding: 0;
18
18
  margin: auto;
@@ -51,110 +51,119 @@ var f = 24, p = /* @__PURE__ */ new Set([
51
51
  the fallback is the arrow it always showed. */
52
52
  cursor: var(--vf-cursor, default);
53
53
  }
54
- `, g = class extends c {
55
- constructor(...e) {
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, () => [
54
+ `, _ = class extends l {
55
+ get dialogSize() {
56
+ return {
57
+ width: a(this.width ?? 260),
58
+ height: this.height === void 0 ? null : a(this.height)
59
+ };
60
+ }
61
+ #e;
62
+ #t() {
63
+ if (this.#e || !this.open) return;
64
+ let e = [];
65
+ 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;
66
+ this.#e = !0;
67
+ let t = this.getAttribute("heading") ?? this.getAttribute("label") ?? "";
68
+ 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">.`);
69
+ }
70
+ #n;
71
+ #r;
72
+ constructor() {
73
+ super(), this.scale = new n(this), this.open = !1, this.returnValue = "", this.lightDismiss = !1, this.#e = !1, this.#n = null, this.#r = null, this.#i = (t) => {
74
+ let n = t.target;
75
+ if (!(n instanceof HTMLFormElement) || v(n) !== this) return;
76
+ let r = t.submitter;
77
+ if ((r?.hasAttribute("formmethod") ? r.getAttribute("formmethod") : n.getAttribute("method"))?.toLowerCase() !== "dialog") return;
78
+ let i = r instanceof HTMLButtonElement || r instanceof HTMLInputElement ? r.value : void 0;
79
+ e(this, t, () => this.close(i));
80
+ }, this.#a = null, this.#o = new c(this, () => [
57
81
  [
58
82
  this._dialog,
59
83
  "pointerdown",
60
- this.#s
84
+ this.#l
61
85
  ],
62
86
  [
63
87
  this._dialog,
64
88
  "pointerup",
65
- this.#c
89
+ this.#u
66
90
  ],
67
91
  [
68
92
  this._dialog,
69
93
  "pointercancel",
70
- this.#l
94
+ this.#d
71
95
  ],
72
96
  [
73
97
  this._dialog,
74
98
  "keydown",
75
- this.#o
99
+ this.#c
76
100
  ]
77
- ]), this.#o = (e) => {
101
+ ]), this.#c = (e) => {
78
102
  if (e.key !== "Enter" || e.defaultPrevented || e.isComposing || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
79
103
  let t = e.composedPath();
80
104
  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
105
  let n = this.defaultButton;
82
106
  n && (e.preventDefault(), n.click());
83
- }, this.#s = (e) => {
84
- this.#r = e.target === this._dialog ? e.pointerId : null;
85
- }, this.#c = (e) => {
86
- let t = this.#r === e.pointerId;
87
- this.#r = null, !(!t || e.target !== this._dialog || !this.lightDismiss) && (this.#n = "outside", this.close());
88
- }, this.#l = () => {
89
- this.#r = null;
90
- }, this.#u = null, this.#g = () => this.settle();
91
- }
92
- get dialogSize() {
93
- return {
94
- width: i(this.width ?? 260),
95
- height: this.height === void 0 ? null : i(this.height)
96
- };
97
- }
98
- #e;
99
- #t() {
100
- if (this.#e || !this.open) return;
101
- let e = [];
102
- 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;
103
- this.#e = !0;
104
- let t = this.getAttribute("heading") ?? this.getAttribute("label") ?? "";
105
- 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">.`);
107
+ }, this.#l = (e) => {
108
+ this.#a = e.target === this._dialog ? e.pointerId : null;
109
+ }, this.#u = (e) => {
110
+ let t = this.#a === e.pointerId;
111
+ this.#a = null, !(!t || e.target !== this._dialog || !this.lightDismiss) && (this.#n = "outside", this.close());
112
+ }, this.#d = () => {
113
+ this.#a = null;
114
+ }, this.#f = null, this.#v = () => this.settle(), this.addEventListener("submit", this.#i);
106
115
  }
107
- #n;
108
- #r;
109
116
  #i;
117
+ #a;
118
+ #o;
110
119
  get defaultButton() {
111
120
  let e = "vf-button[variant=\"default\"]";
112
121
  return [...this.querySelectorAll(e), ...this.renderRoot.querySelectorAll(e)].find((e) => !e.matches(":disabled")) ?? null;
113
122
  }
114
123
  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);
124
+ return this.querySelector("[autofocus]") || ([...this.querySelectorAll("vf-text-field, vf-number-field, vf-text-area, textarea, input")].find((e) => h(e) && !e.matches(":disabled")) ?? this.defaultButton);
116
125
  }
117
- #a() {
126
+ #s() {
118
127
  this.initialFocusTarget?.focus();
119
128
  }
120
- #o;
121
- #s;
122
129
  #c;
123
130
  #l;
124
131
  #u;
132
+ #d;
133
+ #f;
125
134
  show() {
126
- this.open = !0, this.hasUpdated && this.#h();
135
+ this.open = !0, this.hasUpdated && this.#_();
127
136
  }
128
- close() {
129
- this.open = !1, this.hasUpdated && this.#h();
137
+ close(e) {
138
+ this.open && e !== void 0 && (this.#r = e), this.open = !1, this.hasUpdated && this.#_();
130
139
  }
131
140
  updated(e) {
132
- e.has("open") && this.#h(), (e.has("top") || e.has("left")) && this.settle(), this.#t();
141
+ e.has("open") && this.#_(), (e.has("top") || e.has("left")) && this.settle(), this.#t();
133
142
  }
134
- #d() {
143
+ #p() {
135
144
  let e = document.documentElement;
136
145
  return {
137
- width: a(e.clientWidth || window.innerWidth, this),
138
- height: a(e.clientHeight || window.innerHeight, this)
146
+ width: o(e.clientWidth || window.innerWidth, this),
147
+ height: o(e.clientHeight || window.innerHeight, this)
139
148
  };
140
149
  }
141
- #f(e, t) {
142
- let n = this.#d(), r = a(this._dialog?.offsetWidth ?? 0, this);
150
+ #m(e, t) {
151
+ let n = this.#p(), r = o(this._dialog?.offsetWidth ?? 0, this);
143
152
  return {
144
- x: Math.min(Math.max(e, f - r), n.width - f),
145
- y: Math.min(Math.max(t, 0), Math.max(0, n.height - f))
153
+ x: Math.min(Math.max(e, p - r), n.width - p),
154
+ y: Math.min(Math.max(t, 0), Math.max(0, n.height - p))
146
155
  };
147
156
  }
148
- #p() {
149
- let e = this.#d(), t = this._dialog?.getBoundingClientRect();
157
+ #h() {
158
+ let e = this.#p(), t = this._dialog?.getBoundingClientRect();
150
159
  return {
151
- x: (e.width - a(t?.width ?? 0, this)) / 2,
152
- y: (e.height - a(t?.height ?? 0, this)) / 2
160
+ x: (e.width - o(t?.width ?? 0, this)) / 2,
161
+ y: (e.height - o(t?.height ?? 0, this)) / 2
153
162
  };
154
163
  }
155
164
  placeAt(e, t) {
156
- let n = this.#f(e, t);
157
- this.left = r(n.x, this), this.top = r(n.y, this);
165
+ let n = this.#m(e, t);
166
+ this.left = i(n.x, this), this.top = i(n.y, this);
158
167
  }
159
168
  settle() {
160
169
  let e = this._dialog;
@@ -162,10 +171,10 @@ var f = 24, p = /* @__PURE__ */ new Set([
162
171
  let t = this.left != null || this.top != null ? {
163
172
  x: this.left ?? 0,
164
173
  y: this.top ?? 0
165
- } : this.#p(), n = this.#f(t.x, t.y);
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";
174
+ } : this.#h(), n = this.#m(t.x, t.y);
175
+ e.style.left = a(i(n.x, this)), e.style.top = a(i(n.y, this)), e.style.right = "auto", e.style.bottom = "auto", e.style.margin = "0";
167
176
  }
168
- #m() {
177
+ #g() {
169
178
  let e = this._dialog?.style;
170
179
  if (e) for (let t of [
171
180
  "left",
@@ -175,44 +184,51 @@ var f = 24, p = /* @__PURE__ */ new Set([
175
184
  "margin"
176
185
  ]) e.removeProperty(t);
177
186
  }
178
- #h() {
187
+ #_() {
179
188
  let e = this._dialog;
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());
189
+ e && (this.open && !e.open ? (this.#f = document.activeElement, this.returnValue = "", e.showModal(), this.#s(), this.settle(), this.#x(e), this.#o.attach()) : !this.open && e.open && e.close());
181
190
  }
182
- #g;
183
- #_;
184
191
  #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);
192
+ #y;
193
+ #b;
194
+ #x(e) {
195
+ typeof ResizeObserver < "u" && !this.#y && (this.#y = new ResizeObserver(this.#v), this.#y.observe(e)), window.addEventListener("resize", this.#v), this.#b ??= r(this.#v);
187
196
  }
188
- #b() {
189
- this.#_?.disconnect(), this.#_ = void 0, window.removeEventListener("resize", this.#g), this.#v?.(), this.#v = void 0;
197
+ #S() {
198
+ this.#y?.disconnect(), this.#y = void 0, window.removeEventListener("resize", this.#v), this.#b?.(), this.#b = void 0;
190
199
  }
191
200
  disconnectedCallback() {
192
- super.disconnectedCallback(), this.#b();
201
+ super.disconnectedCallback(), this.#S();
193
202
  let e = this._dialog;
194
203
  if (e?.open) {
195
204
  e.close();
196
- let t = this.#u, n = document.activeElement;
205
+ let t = this.#f, n = document.activeElement;
197
206
  t instanceof HTMLElement && t.isConnected && (n === null || n === document.body) && t.focus();
198
207
  }
199
- this.#u = null;
208
+ this.#f = null;
200
209
  }
201
210
  _onNativeCancel() {
202
211
  this.#n = "escape";
203
212
  }
204
213
  _onNativeClose() {
205
- let t = this.#n ?? "close";
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 });
214
+ let e = this.#n ?? "close", n = this.#r;
215
+ this.#n = null, this.#r = null, n !== null && (this.returnValue = n), this.#f = null, this.#a = null, this.#o.detach(), this.#S(), this.open = !1, this.#g(), t(this, "vf-close", {
216
+ reason: e,
217
+ returnValue: n
218
+ });
207
219
  }
208
220
  };
209
- o([u({
221
+ s([d({
210
222
  type: Boolean,
211
223
  reflect: !0
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({
224
+ })], _.prototype, "open", void 0), s([d({ type: Number })], _.prototype, "width", void 0), s([d({ type: Number })], _.prototype, "height", void 0), s([d({ type: Number })], _.prototype, "top", void 0), s([d({ type: Number })], _.prototype, "left", void 0), s([d({
213
225
  type: Boolean,
214
226
  reflect: !0,
215
227
  attribute: "light-dismiss"
216
- })], g.prototype, "lightDismiss", void 0), o([d("dialog")], g.prototype, "_dialog", void 0);
228
+ })], _.prototype, "lightDismiss", void 0), s([f("dialog")], _.prototype, "_dialog", void 0);
229
+ function v(e) {
230
+ for (let t = e.parentElement; t; t = t.parentElement) if (t instanceof _) return t;
231
+ return null;
232
+ }
217
233
  //#endregion
218
- export { g as VfModalDialog, h as modalDialogStyles };
234
+ export { _ as VfModalDialog, g as modalDialogStyles };
package/dist/position.js CHANGED
@@ -70,25 +70,25 @@ var h = class {
70
70
  return u(e.width, e.height, this.#s, t(this.#e));
71
71
  }
72
72
  hostConnected() {
73
- this.#a && (this.#r || this.#l) && this.#y();
73
+ this.#a && (this.#r || this.#l) && this.#b();
74
74
  }
75
75
  hostDisconnected() {
76
- this.#b();
76
+ this.#x();
77
77
  }
78
78
  hostUpdated() {
79
79
  let e = this.#e.top ?? null, t = this.#e.left ?? null, n = this.#e.fixed === !0, r = this.#e.origin ?? null, i = this.#e.style;
80
80
  if (r !== this.#o && (this.#o = r, this.#s = this.#p(r)), e === null && t === null && !n) {
81
81
  if (!this.#a) return;
82
- this.#r && this.#v(), this.#b(), this.#a = !1, this.#r = !1, this.#l = !1, this.#u = 0, this.#d = 0, i.removeProperty("position"), i.removeProperty("top"), i.removeProperty("left"), i.removeProperty("right"), i.removeProperty("bottom"), i.removeProperty("margin"), this.#g(null, null);
82
+ this.#r && this.#y(), this.#x(), this.#a = !1, this.#r = !1, this.#l = !1, this.#u = 0, this.#d = 0, i.removeProperty("position"), i.removeProperty("top"), i.removeProperty("left"), i.removeProperty("right"), i.removeProperty("bottom"), i.removeProperty("margin"), this.#_(null, null);
83
83
  return;
84
84
  }
85
85
  if (this.#a && e === this.#t && t === this.#n && n === this.#r && r === this.#i) return;
86
86
  let a = this.#a && this.#r, o = this.#s !== c;
87
87
  if (this.#a = !0, this.#t = e, this.#n = t, this.#r = n, this.#i = r, this.#l = o, i.position = n ? "sticky" : "absolute", o) {
88
88
  let e = this.#e.getBoundingClientRect();
89
- (e.width > 0 || e.height > 0) && this.#h(e.width, e.height);
89
+ (e.width > 0 || e.height > 0) && this.#g(e.width, e.height);
90
90
  } else this.#u = 0, this.#d = 0;
91
- this.#m(), i.right = "auto", i.bottom = "auto", n ? a || this.#_() : (a && this.#v(), i.margin = "0"), n || o ? this.#y() : this.#b(), this.#g(t ?? 0, e ?? 0);
91
+ this.#m(), i.right = "auto", i.bottom = "auto", n ? a || this.#v() : (a && this.#y(), i.margin = "0", this.#h()), n || o ? this.#b() : this.#x(), this.#_(t ?? 0, e ?? 0);
92
92
  }
93
93
  #p(e) {
94
94
  if (e == null) return c;
@@ -97,49 +97,54 @@ var h = class {
97
97
  }
98
98
  #m() {
99
99
  let e = this.#e.style;
100
- e.top = r((this.#t ?? 0) - this.#d), e.left = r((this.#n ?? 0) - this.#u);
100
+ e.top = r((this.#t ?? 0) - this.#d), e.left = r((this.#n ?? 0) - this.#u), this.#h();
101
101
  }
102
- #h(e, n) {
102
+ #h() {
103
+ !this.#l || this.#r || (this.#e.style.marginRight = r(-((this.#n ?? 0) - this.#u)));
104
+ }
105
+ #g(e, n) {
103
106
  let r = u(e, n, this.#s, t(this.#e));
104
107
  return r.x === this.#u && r.y === this.#d ? !1 : (this.#u = r.x, this.#d = r.y, !0);
105
108
  }
106
- #g(t, n) {
109
+ #_(t, n) {
107
110
  e(this.#e, "vf-placement-change", {
108
111
  left: t,
109
112
  top: n
110
113
  }, { composed: !1 });
111
114
  }
112
- #_() {
115
+ #v() {
113
116
  let e = this.#e.style;
114
- e.maxWidth = "fit-content", e.zIndex = "1", this.#x = !1;
117
+ e.maxWidth = "fit-content", e.zIndex = "1", this.#S = !1;
115
118
  let t = this.#e.getBoundingClientRect();
116
- (t.width > 0 || t.height > 0) && this.#S(t.width, t.height);
119
+ (t.width > 0 || t.height > 0) && this.#C(t.width, t.height);
117
120
  }
118
- #v() {
119
- this.#x = !1;
121
+ #y() {
122
+ this.#S = !1;
120
123
  let e = this.#e.style;
121
124
  e.removeProperty("display"), e.removeProperty("max-width"), e.removeProperty("z-index");
122
125
  }
123
- #y() {
126
+ #b() {
124
127
  this.#f ??= new ResizeObserver((e) => {
125
128
  let t = e[e.length - 1]?.borderBoxSize?.[0], n = t ? {
126
129
  width: t.inlineSize,
127
130
  height: t.blockSize
128
131
  } : this.#e.getBoundingClientRect();
129
- this.#r && this.#S(n.width, n.height), this.#l && this.#h(n.width, n.height) && (this.#m(), this.#g(this.#n ?? 0, this.#t ?? 0));
132
+ this.#r && this.#C(n.width, n.height);
133
+ let r = n.width > 0 || n.height > 0;
134
+ this.#l && r && this.#g(n.width, n.height) && (this.#m(), this.#_(this.#n ?? 0, this.#t ?? 0));
130
135
  }), this.#f.observe(this.#e);
131
136
  }
132
- #b() {
137
+ #x() {
133
138
  this.#f?.disconnect(), this.#f = null;
134
139
  }
135
- #x = !1;
136
- #S(e, t) {
140
+ #S = !1;
141
+ #C(e, t) {
137
142
  let n = this.#e.style;
138
- if (!this.#x) {
143
+ if (!this.#S) {
139
144
  let e = getComputedStyle(this.#e).display;
140
145
  if (e) {
141
146
  let t = d[e];
142
- t && (n.display = t), this.#x = !0;
147
+ t && (n.display = t), this.#S = !0;
143
148
  }
144
149
  }
145
150
  n.margin = `0 ${-e}px ${-t}px 0`;
@@ -1,16 +1,19 @@
1
1
  /**
2
- * The shell's alert: a plain-framed `vf-dialog` with the site's caution art,
2
+ * The Finder's alert: a plain-framed `vf-dialog` with the site's caution art,
3
3
  * a message and a row of buttons, sized to the message — composed from kit
4
- * elements, as every alert in the kit is.
4
+ * elements, as every alert in the kit is. Its buttons submit a
5
+ * `<form method="dialog">`, so it answers with the pressed button's `value`
6
+ * like any dialog an application holds.
5
7
  *
6
8
  * The layout follows the plain frame's body, its width and height less 10:
7
9
  * the art and the message 16 in from the body's top-left, the buttons'
8
10
  * bottom-right corner 16 in from the body's.
9
11
  */
12
+ import type { VfDialog } from '../index.js';
10
13
  /** One button of an alert. */
11
14
  export interface AlertButton {
12
15
  label: string;
13
- /** What the alert resolves to when it is pressed. */
16
+ /** What the alert answers when it is pressed. */
14
17
  value: string;
15
18
  /** The default button, which Return presses. */
16
19
  default?: boolean;
@@ -20,13 +23,18 @@ export interface AlertOptions {
20
23
  buttons?: AlertButton[];
21
24
  /** The dialog's width, system px. Default 400. */
22
25
  width?: number;
23
- /** The 32×32 art beside the message; default the shell's caution art. */
26
+ /** The 32×32 art beside the message, or null for none. */
24
27
  art?: string | null;
25
28
  /** The dialog's accessible name. Default "Alert". */
26
29
  label?: string;
27
30
  }
28
- /**
29
- * Show an alert inside `host` (the desktop), and resolve the value of the
30
- * button pressed — or null when it closed any other way, Escape say.
31
- */
32
- export declare function showAlert(host: Element, message: string, options?: AlertOptions): Promise<string | null>;
31
+ /** An alert, made but not yet shown. */
32
+ export interface ComposedAlert {
33
+ dialog: VfDialog;
34
+ /** Resolves once its parts have rendered, so `show()` opens it at once. Call it once the dialog is in the page. */
35
+ rendered(): Promise<unknown>;
36
+ /** Size the box to the message. Call it right after `show()`: a closed dialog lays out nothing. */
37
+ fit(): void;
38
+ }
39
+ /** Make an alert for `message`. */
40
+ export declare function composeAlert(message: string, options?: AlertOptions): ComposedAlert;
@@ -2,43 +2,40 @@ import { effectiveScale as e } from "../scale.js";
2
2
  import "../index.js";
3
3
  //#region src/shell/alert.ts
4
4
  var t = 10, n = 16, r = 32, i = 28;
5
- async function a(a, o, s = {}) {
6
- let { buttons: c = [{
5
+ function a(a, o = {}) {
6
+ let { buttons: s = [{
7
7
  label: "OK",
8
8
  value: "ok",
9
9
  default: !0
10
- }], width: l = 400, art: u = null, label: d = "Alert" } = s, f = document.createElement("vf-dialog");
11
- f.frame = "plain", f.label = d, f.width = l, f.height = 120;
12
- let p = u ? 64 : n;
13
- if (u) {
10
+ }], width: c = 400, art: l = null, label: u = "Alert" } = o, d = document.createElement("vf-dialog");
11
+ d.frame = "plain", d.label = u, d.width = c, d.height = 120;
12
+ let f = document.createElement("form");
13
+ f.method = "dialog", f.noValidate = !0;
14
+ let p = l ? 64 : n;
15
+ if (l) {
14
16
  let e = document.createElement("vf-img");
15
17
  e.width = r, e.height = r, e.left = n, e.top = n;
16
18
  let t = document.createElement("img");
17
- t.src = u, t.alt = "", e.append(t), f.append(e);
19
+ t.src = l, t.alt = "", e.append(t), f.append(e);
18
20
  }
19
21
  let m = document.createElement("vf-paragraph");
20
- m.face = "display", m.width = l - t - p - n, m.left = p, m.top = n, m.textContent = o, f.append(m);
22
+ m.face = "display", m.width = c - t - p - n, m.left = p, m.top = n, m.textContent = a, f.append(m);
21
23
  let h = document.createElement("vf-button-group");
22
- h.origin = "bottom right";
23
- let g = c.map((e) => {
24
+ return h.origin = "bottom right", h.append(...s.map((e) => {
24
25
  let t = document.createElement("vf-button");
25
- return t.textContent = e.label, t.dataset.value = e.value, e.default && (t.variant = "default"), t;
26
- });
27
- h.append(...g), f.append(h), a.append(f), await Promise.all([
28
- f.updateComplete,
29
- m.updateComplete,
30
- h.updateComplete
31
- ]);
32
- let _ = Math.ceil(m.getBoundingClientRect().height / e(m)), v = Math.max(u ? 48 : 0, n + _) + n + i + n;
33
- return f.height = v + t, h.left = l - t - n, h.top = v - n, new Promise((e) => {
34
- let t = null;
35
- for (let e of g) e.addEventListener("click", () => {
36
- t = e.dataset.value ?? null, f.close();
37
- });
38
- f.addEventListener("vf-close", () => {
39
- f.remove(), e(t);
40
- }, { once: !0 }), f.show();
41
- });
26
+ return t.textContent = e.label, t.type = "submit", t.value = e.value, e.default && (t.variant = "default"), t;
27
+ })), f.append(h), d.append(f), {
28
+ dialog: d,
29
+ rendered: () => Promise.all([
30
+ d.updateComplete,
31
+ m.updateComplete,
32
+ h.updateComplete
33
+ ]),
34
+ fit() {
35
+ let r = Math.ceil(m.getBoundingClientRect().height / e(m)), a = Math.max(l ? 48 : 0, n + r) + n + i + n;
36
+ d.height = a + t, h.left = c - t - n, h.top = a - n;
37
+ }
38
+ };
42
39
  }
43
40
  //#endregion
44
- export { a as showAlert };
41
+ export { a as composeAlert };
@@ -23,6 +23,9 @@
23
23
  * View (Arrange Windows), Special (Clean Up, Empty Trash…), each disabled
24
24
  * while it has nothing to act on. A site switches commands off and adds
25
25
  * its own.
26
+ * - Alerts are the Finder's own, composed with the site's caution art and
27
+ * held by it while they are up. A site answers any of them with its own
28
+ * dialog through `alertWith`.
26
29
  */
27
30
  import type { VfIcon, VfMenuItem, VfViewportBox } from '../index.js';
28
31
  import type { Catalog, CatalogStorage, Item, Volumes } from './catalog.js';
@@ -43,7 +46,40 @@ export interface FinderArt {
43
46
  disk?: string;
44
47
  /** 12×12: the mark before the item count in the Trash's window and trashed folders. */
45
48
  trashMark?: string;
49
+ /** 32×32: beside the message in the Finder's alerts. Without it they have no art. */
50
+ caution?: string;
46
51
  }
52
+ /** What a failed command was, in the `failed` alert. */
53
+ export type FinderFailedAction = 'new-folder' | 'empty-trash' | 'paste' | 'add-file';
54
+ /** The Finder's alerts, by id, with the details each one's handler gets. */
55
+ export interface FinderAlerts {
56
+ /** Special → Empty Trash…: an answer of `'ok'` empties the Trash. */
57
+ 'empty-trash': {
58
+ count: number;
59
+ bytes: number;
60
+ };
61
+ /** New Folder, Paste or a dropped file, without storage. */
62
+ 'storage-unavailable': Record<string, never>;
63
+ /** A rename past the limit. */
64
+ 'name-too-long': {
65
+ limit: number;
66
+ };
67
+ /** A rename to nothing. */
68
+ 'name-rejected': Record<string, never>;
69
+ /** A filing storage refused. */
70
+ 'move-failed': {
71
+ error: Error;
72
+ };
73
+ /** New Folder, Empty Trash, Paste or a dropped file failed. */
74
+ failed: {
75
+ action: FinderFailedAction;
76
+ error: Error;
77
+ };
78
+ }
79
+ /** One of the Finder's alerts. */
80
+ export type FinderAlert = keyof FinderAlerts;
81
+ /** Answers one of the Finder's alerts: resolves the answer, or null. */
82
+ export type FinderAlertHandler<K extends FinderAlert> = (details: FinderAlerts[K]) => string | null | Promise<string | null>;
47
83
  /** The Finder's own commands, each of which a site can switch off. */
48
84
  export type FinderCommand = 'open' | 'new-folder' | 'close' | 'copy' | 'paste' | 'select-all' | 'arrange' | 'clean-up' | 'empty-trash';
49
85
  /** The Finder's menus, by `data-menu`. */
@@ -85,6 +121,8 @@ export interface FinderApi {
85
121
  iconFor(id: string): VfIcon | null;
86
122
  /** Add a command to the Finder's menus. Call it from `extend`, before the menus first reach the bar. */
87
123
  addCommand(spec: FinderCommandSpec): VfMenuItem;
124
+ /** Answer one of the Finder's alerts with the site's own dialog. Call it from `extend`. */
125
+ alertWith<K extends FinderAlert>(id: K, handler: FinderAlertHandler<K>): void;
88
126
  }
89
127
  export interface FinderOptions {
90
128
  /** Where the catalog is kept: `memoryStorage()`, `indexedDbStorage(name)`, the site's own, or null for nothing. */
@@ -107,7 +145,9 @@ export interface FinderOptions {
107
145
  cleanUpAfterResize?: boolean;
108
146
  /** Switch commands off: `{ 'empty-trash': false }`. */
109
147
  commands?: Partial<Record<FinderCommand, boolean>>;
110
- /** Add the site's own commands. */
148
+ /** The site's dialogs for the Finder, each with a `data-dialog` name, held by it: `ctx.dialog(name)` in `extend`. */
149
+ dialogs?: string;
150
+ /** Add the site's own commands, and answer the Finder's alerts. */
111
151
  extend?(finder: FinderApi, ctx: AppContext): void;
112
152
  /** A folder window's size, system px. Default 320 × 223: three columns and two rows of icons. */
113
153
  window?: Size;