vintage-frames 0.15.0 → 0.16.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.
Files changed (47) hide show
  1. package/custom-elements.json +681 -65
  2. package/dist/chrome.d.ts +10 -1
  3. package/dist/chrome.js +8 -3
  4. package/dist/components/vf-desktop.d.ts +5 -2
  5. package/dist/components/vf-desktop.js +5 -5
  6. package/dist/components/vf-dialog.d.ts +7 -2
  7. package/dist/components/vf-dialog.js +27 -27
  8. package/dist/components/vf-menu-bar.js +9 -10
  9. package/dist/components/vf-menu-item.js +18 -13
  10. package/dist/components/vf-menu.d.ts +8 -3
  11. package/dist/components/vf-menu.js +85 -61
  12. package/dist/components/vf-number-field.d.ts +4 -0
  13. package/dist/components/vf-number-field.js +28 -25
  14. package/dist/components/vf-paragraph.d.ts +3 -1
  15. package/dist/components/vf-paragraph.js +7 -0
  16. package/dist/components/vf-scroll-area.d.ts +16 -1
  17. package/dist/components/vf-scroll-area.js +39 -29
  18. package/dist/components/vf-select.d.ts +5 -4
  19. package/dist/components/vf-select.js +26 -17
  20. package/dist/components/vf-text-field.d.ts +5 -5
  21. package/dist/components/vf-text-field.js +1 -1
  22. package/dist/components/vf-window.d.ts +22 -5
  23. package/dist/components/vf-window.js +113 -98
  24. package/dist/cursor.d.ts +8 -0
  25. package/dist/cursor.js +70 -73
  26. package/dist/index.d.ts +2 -1
  27. package/dist/modal-dialog.d.ts +20 -2
  28. package/dist/modal-dialog.js +63 -51
  29. package/dist/shell/catalog.d.ts +1 -1
  30. package/dist/shell/catalog.js +32 -18
  31. package/dist/shell/finder.d.ts +6 -1
  32. package/dist/shell/finder.js +182 -176
  33. package/dist/shell/index.d.ts +1 -1
  34. package/dist/shell/pure.d.ts +10 -0
  35. package/dist/shell/pure.js +4 -0
  36. package/dist/shell/shell.d.ts +16 -3
  37. package/dist/shell/shell.js +141 -126
  38. package/dist/shell/windows.d.ts +48 -10
  39. package/dist/shell/windows.js +142 -85
  40. package/dist/text-control.d.ts +24 -1
  41. package/dist/text-control.js +25 -4
  42. package/dist/top-layer.d.ts +13 -0
  43. package/dist/top-layer.js +16 -0
  44. package/docs/SPEC.md +23 -22
  45. package/editor/vscode.html-custom-data.json +21 -11
  46. package/editor/web-types.json +51 -23
  47. package/package.json +5 -1
@@ -12,7 +12,7 @@
12
12
  export { createShell, defineApp, appIdOf, APP_KIND } from './shell.js';
13
13
  export type { AppContext, AppDefinition, CatalogSetup, KindDefinition, Shell, ShellOptions } from './shell.js';
14
14
  export { createWindowManager, focusInto } from './windows.js';
15
- export type { AdoptOptions, ArrangeGroup, BoxInput, FocusHome, HomeBox, OpenOptions, PlacePolicy, Placed, WindowManager, WindowManagerOptions, } from './windows.js';
15
+ export type { AdoptOptions, ArrangeGroup, BoxInput, CloseHandler, FocusHome, HomeBox, OpenOptions, PlacePolicy, Placed, WindowManager, WindowManagerOptions, } from './windows.js';
16
16
  export { finder, FINDER } from './finder.js';
17
17
  export type { FinderAlert, FinderAlertHandler, FinderAlerts, FinderApi, FinderArt, FinderCommand, FinderCommandSpec, FinderFailedAction, FinderMenu, FinderOpen, FinderOptions, } from './finder.js';
18
18
  export { fileByDrag } from './filing.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `vintage-frames/shell/pure` — the shell's pure modules on their own: the
3
+ * catalog, the geometry and the saved session. They import nothing from the
4
+ * kit and touch no DOM at import, so a site's own pure modules and their
5
+ * tests import them under Node. `vintage-frames/shell` exports the same
6
+ * names.
7
+ */
8
+ export * from './catalog.js';
9
+ export * from './geometry.js';
10
+ export * from './state.js';
@@ -0,0 +1,4 @@
1
+ import { DISK as e, FOLDER as t, TRASH as n, UNTITLED_FOLDER as r, childrenOf as i, containerOf as a, copyName as o, createCatalog as s, descendantsOf as c, enclosingFolders as l, isContainerKind as u, isInside as d, isTrashed as f, isVolume as p, itemCount as m, itemOf as h, memoryStorage as g, nextFolderName as _ } from "./catalog.js";
2
+ import { BAND as v, CASCADE_SLOTS as y, CASCADE_STEP as b, ICON_CELL as x, ICON_COLUMN as S, ICON_INSET as C, ICON_ROW as w, NEAR as T, WINDOW_ORIGIN as E, cascadeFrom as D, cascadeSlot as O, cascadedBox as k, centeredBox as A, cleanUp as j, collisions as M, desktopLattice as N, fieldExtent as P, fillOrder as F, folderLattice as I, frameOf as L, isPin as R, latticeCell as z, latticeSlot as B, nearBox as V, nextFreeCell as H, pinOf as U, pinTo as W, trashCell as G, windowOrigin as K } from "./geometry.js";
3
+ import { localStorageState as q, mergeSession as J, openWindowsOf as Y, readSession as X } from "./state.js";
4
+ export { v as BAND, y as CASCADE_SLOTS, b as CASCADE_STEP, e as DISK, t as FOLDER, x as ICON_CELL, S as ICON_COLUMN, C as ICON_INSET, w as ICON_ROW, T as NEAR, n as TRASH, r as UNTITLED_FOLDER, E as WINDOW_ORIGIN, D as cascadeFrom, O as cascadeSlot, k as cascadedBox, A as centeredBox, i as childrenOf, j as cleanUp, M as collisions, a as containerOf, o as copyName, s as createCatalog, c as descendantsOf, N as desktopLattice, l as enclosingFolders, P as fieldExtent, F as fillOrder, I as folderLattice, L as frameOf, u as isContainerKind, d as isInside, R as isPin, f as isTrashed, p as isVolume, m as itemCount, h as itemOf, z as latticeCell, B as latticeSlot, q as localStorageState, g as memoryStorage, J as mergeSession, V as nearBox, _ as nextFolderName, H as nextFreeCell, Y as openWindowsOf, U as pinOf, W as pinTo, X as readSession, G as trashCell, K as windowOrigin };
@@ -10,16 +10,18 @@
10
10
  * - While an application's held dialog is open, the bar shows that
11
11
  * application's name and menus instead; nothing else changes.
12
12
  * - Each application's `init(ctx)` gets the desktop, the window manager, its
13
- * own menus and dialogs, the system menu, the other applications' actions
13
+ * own menus, dialogs and window markup, the system menu, the other applications' actions
14
14
  * (read at the call), the catalog when a Finder runs, the site's services,
15
15
  * `ask()`, `gate()`, `onFront()` and `onDispose()`. Everything it sets up
16
16
  * comes down through `onDispose()`, and its dialogs with it, so a hot
17
17
  * reload starts clean. The shell composes no UI of its own.
18
18
  * - A window showing a catalog item follows it: its title follows a rename,
19
19
  * and it closes when the item is removed.
20
- * - The boot reads the catalog, seeds it once, reopens the last session's
21
- * windows deepest first and the active one last, then starts saving.
20
+ * - The boot waits for the desktop and its menu bar to render, reads the
21
+ * catalog, seeds it once, reopens the last session's windows deepest
22
+ * first and the active one last, then starts saving.
22
23
  */
24
+ import { VfWindow } from '../index.js';
23
25
  import type { VfDesktop, VfDialog, VfMenu, VfMenuItem, VfModalDialog, VfViewportBox } from '../index.js';
24
26
  import type { Catalog, CatalogStorage, Item, Volumes } from './catalog.js';
25
27
  import type { WindowManager } from './windows.js';
@@ -64,6 +66,11 @@ export interface AppDefinition<Actions = unknown> {
64
66
  * an element that renders one inside itself. Held under the application.
65
67
  */
66
68
  dialogs?: string;
69
+ /**
70
+ * A fragment of windows, each with a `data-window` name, kept inert:
71
+ * `ctx.window(name)` makes a fresh copy of one on each call.
72
+ */
73
+ windows?: string;
67
74
  /** The catalog kinds it opens. */
68
75
  kinds?: Record<string, KindDefinition>;
69
76
  /** The Finder's: the catalog every application shares. */
@@ -107,6 +114,12 @@ export interface AppContext {
107
114
  systemItem(value: string, label: string, fn: () => void): VfMenuItem;
108
115
  /** One of its dialogs by `data-dialog`; throws when the markup drifts. */
109
116
  dialog<T extends HTMLElement = VfDialog>(name: string): T;
117
+ /**
118
+ * A fresh copy of one of its windows by `data-window`, upgraded and not yet
119
+ * appended: `open({ create: () => ctx.window('viewer') })`. Throws when the
120
+ * markup drifts.
121
+ */
122
+ window<T extends HTMLElement = VfWindow>(name: string): T;
110
123
  /** Hold a dialog it made in code: appended to the desktop, held under it, removed with it. Returns it. */
111
124
  hold<T extends Element>(dialog: T): T;
112
125
  /** Let a dialog it holds go, and remove it. */
@@ -2,99 +2,103 @@ import { onScaleChange as e } from "../scale.js";
2
2
  import { VfWindow as t } from "../components/vf-window.js";
3
3
  import "../index.js";
4
4
  import { createCatalog as n } from "./catalog.js";
5
- import { createWindowManager as r, deepActiveElement as i } from "./windows.js";
6
- import { openWindowsOf as a } from "./state.js";
7
- import { startClock as o } from "./clock.js";
5
+ import { createWindowManager as r, deepActiveElement as i, desktopRendered as a } from "./windows.js";
6
+ import { openWindowsOf as o } from "./state.js";
7
+ import { startClock as s } from "./clock.js";
8
8
  //#region src/shell/shell.ts
9
- function s(e) {
9
+ function c(e) {
10
10
  return e;
11
11
  }
12
- var c = "app", l = (e) => e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement || e?.isContentEditable === !0;
13
- function u(s, c) {
14
- let u = c.apps, f = u.find((e) => e.catalog), p = c.defaultApp ?? f?.id ?? u[0]?.id ?? "", m = c.state ?? null, h = c.services ?? {}, g = [], _ = s.querySelector(":scope > vf-menu-bar");
15
- if (!_) throw Error("vintage-frames/shell: the desktop has no vf-menu-bar");
16
- let v = _.querySelector(":scope > vf-menu");
17
- if (!v) throw Error("vintage-frames/shell: the menu bar has no system menu");
18
- _.shortcuts = !0;
19
- let y = m?.saved?.pattern;
20
- y && (s.pattern = y);
21
- let b = null;
22
- if (c.fit === "viewport") {
12
+ var l = "app", u = (e) => e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement || e?.isContentEditable === !0;
13
+ function d(c, l) {
14
+ let d = l.apps, p = d.find((e) => e.catalog), m = l.defaultApp ?? p?.id ?? d[0]?.id ?? "", h = l.state ?? null, g = l.services ?? {}, _ = [], v = c.querySelector(":scope > vf-menu-bar");
15
+ if (!v) throw Error("vintage-frames/shell: the desktop has no vf-menu-bar");
16
+ let y = v.querySelector(":scope > vf-menu");
17
+ if (!y) throw Error("vintage-frames/shell: the menu bar has no system menu");
18
+ v.shortcuts = !0;
19
+ let b = h?.saved?.pattern;
20
+ b && (c.pattern = b);
21
+ let x = null;
22
+ if (l.fit === "viewport") {
23
23
  let t = () => {
24
24
  let e = {
25
- width: s.width,
26
- height: s.height
25
+ width: c.width,
26
+ height: c.height
27
27
  };
28
- s.fitWithin(document.documentElement.clientWidth, document.documentElement.clientHeight), b?.resized(e);
28
+ c.fitWithin(document.documentElement.clientWidth, document.documentElement.clientHeight), x?.resized(e);
29
29
  };
30
30
  t(), window.addEventListener("resize", t);
31
31
  let n = e(t);
32
- g.push(() => {
32
+ _.push(() => {
33
33
  window.removeEventListener("resize", t), n();
34
34
  });
35
35
  }
36
- let x = r(s, {
37
- defaultApp: p,
38
- savedPin: (e) => m?.pin(e) ?? null
36
+ let S = r(c, {
37
+ defaultApp: m,
38
+ savedPin: (e) => h?.pin(e) ?? null
39
39
  });
40
- b = x;
41
- let S = (e) => {
42
- e.target === s && s.clearActive();
40
+ x = S;
41
+ let C = (e) => {
42
+ e.target === c && c.clearActive();
43
43
  };
44
- s.addEventListener("pointerdown", S), g.push(() => s.removeEventListener("pointerdown", S));
45
- let C = {}, w = /* @__PURE__ */ new Map();
46
- w.set("app", {
47
- art: (e) => u.find((t) => t.id === d(e))?.icon ?? "",
48
- open: (e, t) => void C[d(e) ?? ""]?.open?.({ from: t }),
44
+ c.addEventListener("pointerdown", C), _.push(() => c.removeEventListener("pointerdown", C));
45
+ let w = {}, T = /* @__PURE__ */ new Map();
46
+ T.set("app", {
47
+ art: (e) => d.find((t) => t.id === f(e))?.icon ?? "",
48
+ open: (e, t) => void w[f(e) ?? ""]?.open?.({ from: t }),
49
49
  editable: !1
50
50
  });
51
- for (let e of u) for (let [t, n] of Object.entries(e.kinds ?? {})) w.set(t, n);
52
- let T = f?.catalog ? n({
53
- storage: f.catalog.storage,
54
- volumes: f.catalog.volumes,
55
- listed: (e) => w.has(e),
56
- hooks: (e) => w.get(e)
57
- }) : null, E = () => !!document.querySelector("vf-dialog[open]"), D = () => l(i()), O = /* @__PURE__ */ new Map(), k = () => {
58
- for (let [e, t] of O) {
51
+ for (let e of d) for (let [t, n] of Object.entries(e.kinds ?? {})) T.set(t, n);
52
+ let E = p?.catalog ? n({
53
+ storage: p.catalog.storage,
54
+ volumes: p.catalog.volumes,
55
+ listed: (e) => T.has(e),
56
+ hooks: (e) => T.get(e)
57
+ }) : null, D = () => !!document.querySelector("vf-dialog[open]"), O = () => u(i()), k = /* @__PURE__ */ new Map(), A = () => {
58
+ for (let [e, t] of k) {
59
59
  let n = !t();
60
60
  e.disabled !== n && (e.disabled = n);
61
61
  }
62
62
  };
63
- document.addEventListener("pointerdown", k, !0), document.addEventListener("keydown", k, !0), s.addEventListener("vf-select", k), s.addEventListener("vf-activate", k), g.push(() => document.removeEventListener("pointerdown", k, !0), () => document.removeEventListener("keydown", k, !0), () => s.removeEventListener("vf-select", k), () => s.removeEventListener("vf-activate", k), x.onWindows(k), x.onLayout(k)), T && g.push(T.subscribe(k));
64
- let A = (e) => {
63
+ document.addEventListener("pointerdown", A, !0), document.addEventListener("keydown", A, !0), c.addEventListener("vf-select", A), c.addEventListener("vf-activate", A), _.push(() => document.removeEventListener("pointerdown", A, !0), () => document.removeEventListener("keydown", A, !0), () => c.removeEventListener("vf-select", A), () => c.removeEventListener("vf-activate", A), S.onWindows(A), S.onLayout(A)), E && _.push(E.subscribe(A));
64
+ let j = (e) => {
65
65
  if (!e) return [];
66
66
  let t = document.createElement("div");
67
67
  t.innerHTML = e, customElements.upgrade(t);
68
68
  let n = [...t.children].filter((e) => e instanceof HTMLElement);
69
69
  for (let e of n) e.remove();
70
70
  return n;
71
- }, j = u.map((e) => ({
71
+ }, M = d.map((e) => ({
72
72
  def: e,
73
- menus: A(e.menus).filter((e) => e.localName === "vf-menu"),
74
- dialogs: A(e.dialogs).filter((e) => e.dataset.dialog != null)
75
- })), M = [];
76
- for (let { def: e, menus: t, dialogs: n } of j) {
77
- let r = [], i = `vintage-frames/shell (${e.id})`, a = /* @__PURE__ */ new Set();
78
- for (let t of n) x.holdDialog(t, { app: e.id }), a.add(t);
79
- r.push(() => {
80
- for (let e of a) x.releaseDialog(e), e.remove();
81
- a.clear();
73
+ menus: j(e.menus).filter((e) => e.localName === "vf-menu"),
74
+ dialogs: j(e.dialogs).filter((e) => e.dataset.dialog != null),
75
+ windows: (() => {
76
+ let t = document.createElement("template");
77
+ return t.innerHTML = e.windows ?? "", t.content;
78
+ })()
79
+ })), N = [];
80
+ for (let { def: e, menus: t, dialogs: n, windows: r } of M) {
81
+ let i = [], a = `vintage-frames/shell (${e.id})`, o = /* @__PURE__ */ new Set();
82
+ for (let t of n) S.holdDialog(t, { app: e.id }), o.add(t);
83
+ i.push(() => {
84
+ for (let e of o) S.releaseDialog(e), e.remove();
85
+ o.clear();
82
86
  });
83
- let o = {
84
- desktop: s,
85
- windows: x,
87
+ let s = {
88
+ desktop: c,
89
+ windows: S,
86
90
  menus: t,
87
- systemMenu: v,
88
- apps: C,
89
- catalog: T,
90
- services: h,
91
- state: m,
92
- kinds: w,
93
- kind: (e) => w.get(e),
94
- app: (e) => u.find((t) => t.id === e),
91
+ systemMenu: y,
92
+ apps: w,
93
+ catalog: E,
94
+ services: g,
95
+ state: h,
96
+ kinds: T,
97
+ kind: (e) => T.get(e),
98
+ app: (e) => d.find((t) => t.id === e),
95
99
  menu(e) {
96
100
  let n = t.find((t) => t.dataset.menu === e);
97
- if (!n) throw Error(`${i}: missing menu ${e}`);
101
+ if (!n) throw Error(`${a}: missing menu ${e}`);
98
102
  return n;
99
103
  },
100
104
  item(e) {
@@ -102,34 +106,40 @@ function u(s, c) {
102
106
  let t = n.querySelector(`vf-menu-item[value="${CSS.escape(e)}"]`);
103
107
  if (t) return t;
104
108
  }
105
- throw Error(`${i}: missing menu item ${e}`);
109
+ throw Error(`${a}: missing menu item ${e}`);
106
110
  },
107
111
  onMenu(e) {
108
- for (let n of t) o.on(n, "vf-menu-select", (t) => {
109
- if (E()) return;
112
+ for (let n of t) s.on(n, "vf-menu-select", (t) => {
113
+ if (D()) return;
110
114
  let { value: n, item: r } = t.detail;
111
115
  e(n, r);
112
116
  });
113
117
  },
114
118
  systemItem(e, t, n) {
115
- let i = document.createElement("vf-menu-item");
116
- return i.setAttribute("value", e), i.textContent = t, v.append(i), o.on(v, "vf-menu-select", (t) => {
117
- !E() && t.detail.value === e && n();
118
- }), r.push(() => i.remove()), i;
119
+ let r = document.createElement("vf-menu-item");
120
+ return r.setAttribute("value", e), r.textContent = t, y.append(r), s.on(y, "vf-menu-select", (t) => {
121
+ !D() && t.detail.value === e && n();
122
+ }), i.push(() => r.remove()), r;
119
123
  },
120
124
  dialog(e) {
121
125
  let t = n.find((t) => t.dataset.dialog === e);
122
- if (!t) throw Error(`${i}: missing dialog ${e}`);
126
+ if (!t) throw Error(`${a}: missing dialog ${e}`);
123
127
  return t;
124
128
  },
129
+ window(e) {
130
+ let t = [...r.children].find((t) => t.dataset.window === e);
131
+ if (!t) throw Error(`${a}: missing window ${e}`);
132
+ let n = document.importNode(t, !0);
133
+ return customElements.upgrade(n), n;
134
+ },
125
135
  hold(t) {
126
- return x.holdDialog(t, { app: e.id }), a.add(t), t;
136
+ return S.holdDialog(t, { app: e.id }), o.add(t), t;
127
137
  },
128
138
  release(e) {
129
- a.delete(e) && (x.releaseDialog(e), e.remove());
139
+ o.delete(e) && (S.releaseDialog(e), e.remove());
130
140
  },
131
141
  ask(t) {
132
- if (x.appOf(t) !== e.id) throw Error(`${i}: ask() takes a dialog the application holds`);
142
+ if (S.appOf(t) !== e.id) throw Error(`${a}: ask() takes a dialog the application holds`);
133
143
  return new Promise((e) => {
134
144
  let n = (r) => {
135
145
  r.target === t && (t.removeEventListener("vf-close", n), e(r.detail.returnValue));
@@ -138,97 +148,102 @@ function u(s, c) {
138
148
  });
139
149
  },
140
150
  gate(e, t) {
141
- O.set(e, t), r.push(() => O.delete(e));
151
+ k.set(e, t), i.push(() => k.delete(e));
142
152
  let n = !t();
143
153
  e.disabled !== n && (e.disabled = n);
144
154
  },
145
- typing: D,
146
- modalOpen: E,
155
+ typing: O,
156
+ modalOpen: D,
147
157
  onFront(t) {
148
- t(x.front === e.id), r.push(x.onFront((n) => t(n === e.id)));
158
+ t(S.front === e.id), i.push(S.onFront((n) => t(n === e.id)));
149
159
  },
150
- on(e, t, n, i) {
151
- e.addEventListener(t, n, i), r.push(() => e.removeEventListener(t, n, i));
160
+ on(e, t, n, r) {
161
+ e.addEventListener(t, n, r), i.push(() => e.removeEventListener(t, n, r));
152
162
  },
153
163
  onDispose(e) {
154
- r.push(e);
164
+ i.push(e);
155
165
  }
156
- }, c = e.init(o);
157
- c && (C[e.id] = c), M.push(() => {
158
- for (let e of r.splice(0).reverse()) e();
166
+ }, l = e.init(s);
167
+ l && (w[e.id] = l), N.push(() => {
168
+ for (let e of i.splice(0).reverse()) e();
159
169
  });
160
170
  }
161
- let N = null, P = () => {
162
- let e = x.asking ?? x.front, t = j.find((t) => t.def.id === e) ?? j.find((e) => e.def.id === p) ?? null;
163
- if (!(!t || t === N)) {
164
- if (N) for (let e of N.menus) e.remove();
165
- v.after(...t.menus), _.label = t.def.name, N = t, k();
171
+ let P = null, F = () => {
172
+ let e = S.asking ?? S.front, t = M.find((t) => t.def.id === e) ?? M.find((e) => e.def.id === m) ?? null;
173
+ if (!(!t || t === P)) {
174
+ if (P) for (let e of P.menus) e.remove();
175
+ y.after(...t.menus), v.label = t.def.name, P = t, A();
166
176
  }
167
177
  };
168
- if (g.push(x.onFront(P), x.onDialogs(P)), P(), c.clock !== !1) {
169
- let e = _.querySelector(":scope > [slot=\"end\"]"), t = !e;
170
- e || (e = document.createElement("vf-label"), e.slot = "end", _.append(e));
171
- let n = o(e);
172
- g.push(() => {
178
+ if (_.push(S.onFront(F), S.onDialogs(F)), F(), l.clock !== !1) {
179
+ let e = v.querySelector(":scope > [slot=\"end\"]"), t = !e;
180
+ e || (e = document.createElement("vf-label"), e.slot = "end", v.append(e));
181
+ let n = s(e);
182
+ _.push(() => {
173
183
  n(), t && e.remove();
174
184
  });
175
185
  }
176
- let F = /* @__PURE__ */ new WeakSet(), I = () => {
177
- if (T) for (let e of s.stackingOrder) {
186
+ let I = /* @__PURE__ */ new WeakSet(), L = () => {
187
+ if (E) for (let e of c.stackingOrder) {
178
188
  if (!(e instanceof t)) continue;
179
- let n = x.itemOf(e);
189
+ let n = S.itemOf(e);
180
190
  if (n == null) continue;
181
- let r = T.item(n);
182
- r ? (F.add(e), e.heading !== r.name && (e.heading = r.name)) : F.has(e) && T.get().available && x.close(e, { to: null });
191
+ let r = E.item(n);
192
+ r ? (I.add(e), e.heading !== r.name && (e.heading = r.name)) : I.has(e) && E.get().available && S.close(e, { to: null });
183
193
  }
184
194
  };
185
- return T && g.push(T.subscribe(I), x.onWindows(I)), m?.hold(), {
186
- desktop: s,
187
- windows: x,
188
- catalog: T,
189
- apps: C,
195
+ return E && _.push(E.subscribe(L), S.onWindows(L)), h?.hold(), {
196
+ desktop: c,
197
+ windows: S,
198
+ catalog: E,
199
+ apps: w,
190
200
  ready: (async () => {
191
201
  try {
192
- T && (await T.refresh(), f?.catalog?.seed && await T.seed(f.catalog.seed));
193
- for (let { item: e, app: t } of a(m?.saved ?? null)) await C[t]?.open?.({ item: e });
194
- let e = m?.saved?.active;
202
+ await a(c), E && (await E.refresh(), p?.catalog?.seed && await E.seed(p.catalog.seed));
203
+ for (let { item: e, app: t } of o(h?.saved ?? null)) await w[t]?.open?.({ item: e });
204
+ let e = h?.saved?.active;
195
205
  if (e) {
196
- let t = x.windowFor(e);
197
- t && s.bringToFront(t);
206
+ let t = S.windowFor(e);
207
+ t && c.bringToFront(t);
198
208
  }
199
209
  } finally {
200
- if (m) {
201
- let e = m.start(() => ({
202
- windows: x.snapshot(),
203
- active: x.itemOf(s.activeWindow),
204
- pattern: s.pattern ?? null
210
+ if (h) {
211
+ let e = h.start(() => ({
212
+ windows: S.snapshot(),
213
+ active: S.itemOf(c.activeWindow),
214
+ pattern: c.pattern ?? null
205
215
  }), (e) => {
206
216
  let t = [
207
- x.onLayout(e),
208
- x.onWindows(e),
209
- ...T ? [T.subscribe(e)] : []
217
+ S.onLayout(e),
218
+ S.onWindows(e),
219
+ ...E ? [E.subscribe(e)] : []
210
220
  ];
211
- return s.addEventListener("vf-activate", e), () => {
221
+ c.addEventListener("vf-activate", e);
222
+ let n = new MutationObserver(e);
223
+ return n.observe(c, {
224
+ attributes: !0,
225
+ attributeFilter: ["pattern"]
226
+ }), () => {
212
227
  for (let e of t) e();
213
- s.removeEventListener("vf-activate", e);
228
+ c.removeEventListener("vf-activate", e), n.disconnect();
214
229
  };
215
230
  });
216
- g.push(e), m.release();
231
+ _.push(e), h.release();
217
232
  }
218
233
  }
219
234
  })(),
220
235
  dispose() {
221
- for (let e of M.splice(0).reverse()) e();
222
- if (N) for (let e of N.menus) e.remove();
223
- N = null;
224
- for (let e of g.splice(0)) e();
225
- x.dispose(), T?.dispose();
236
+ for (let e of N.splice(0).reverse()) e();
237
+ if (P) for (let e of P.menus) e.remove();
238
+ P = null;
239
+ for (let e of _.splice(0)) e();
240
+ S.dispose(), E?.dispose();
226
241
  }
227
242
  };
228
243
  }
229
- function d(e) {
244
+ function f(e) {
230
245
  let t = e.data;
231
246
  return typeof t?.app == "string" ? t.app : null;
232
247
  }
233
248
  //#endregion
234
- export { c as APP_KIND, d as appIdOf, u as createShell, s as defineApp };
249
+ export { l as APP_KIND, f as appIdOf, d as createShell, c as defineApp };
@@ -29,12 +29,15 @@
29
29
  * resizable window's pin when its size changed, floors it at the window's
30
30
  * own size limits and caps it to the window area, so growing back restores
31
31
  * every place.
32
+ * - The zoom box, for a window adopted with `zoom`: the window goes to its
33
+ * zoomed box and back to the box it had, kept as a pin; a zoomed window
34
+ * stays zoomed across a resize.
32
35
  * - Dialogs: `holdDialog()` holds an application's dialog under it, in the
33
36
  * desktop. The manager follows each held dialog's `open` attribute, and
34
37
  * `asking` is the application of the one opened last and still open.
35
38
  */
36
39
  import { VfWindow } from '../index.js';
37
- import type { VfDesktop, VfViewportBox } from '../index.js';
40
+ import type { VfCloseKeys, VfDesktop, VfViewportBox } from '../index.js';
38
41
  import type { Bands, Box, Pin, Policy, Size } from './geometry.js';
39
42
  /** A box to write, any edge optional. */
40
43
  export interface BoxInput {
@@ -68,6 +71,13 @@ export interface AdoptOptions {
68
71
  policy?: ((cur: Box) => Policy) | null;
69
72
  /** A box held across a raster resize while the window is near it. */
70
73
  keep?: ((area: Box) => Box) | null;
74
+ /**
75
+ * Its zoom box: the window's zoomed box on the window area, given its
76
+ * current box (to keep its top-left, say). Set, the manager runs the zoom
77
+ * box: it goes to this box and back to the box the user had, and a window
78
+ * at it stays zoomed across a raster resize.
79
+ */
80
+ zoom?: ((area: Box, box: Box) => BoxInput) | null;
71
81
  /**
72
82
  * A palette: shown while its application is front — and while the
73
83
  * predicate holds, for one the application hides for its own reasons —
@@ -77,10 +87,14 @@ export interface AdoptOptions {
77
87
  /**
78
88
  * What the close box means. Unset, it closes the window. Set, the close box
79
89
  * asks the application, which calls `close()` once it decides — after a
80
- * Save prompt, say — or never.
90
+ * Save prompt, say — or never. It gets the modifier keys held on the
91
+ * close box's click; a close from a menu or code holds none. A handler
92
+ * that decides later returns its promise, so `closeAll()` waits for it.
81
93
  */
82
- close?: ((win: VfWindow) => void) | null;
94
+ close?: CloseHandler | null;
83
95
  }
96
+ /** A window's close handler: decides whether it closes, and calls `close()` if so. */
97
+ export type CloseHandler = (win: VfWindow, keys: Partial<VfCloseKeys>) => void | Promise<void>;
84
98
  /** What opening a window for an item declares. */
85
99
  export interface OpenOptions extends Omit<AdoptOptions, 'pin' | 'palette'> {
86
100
  /** Makes the window, not yet appended. */
@@ -108,13 +122,19 @@ export interface WindowManagerOptions {
108
122
  /** A saved pin by item — the session the boot read. */
109
123
  savedPin?: (item: string) => Pin | null;
110
124
  }
125
+ /** Settles once the desktop and its menu bar have rendered, so `workArea` measures the bar. */
126
+ export declare function desktopRendered(desktop: VfDesktop): Promise<void>;
111
127
  /** The innermost focused element, through open shadow roots. */
112
128
  export declare function deepActiveElement(): Element | null;
113
129
  /**
114
- * Move keyboard focus to the first thing in `win`'s content that takes it,
115
- * once that content has rendered — and then only if `when`, read at that
116
- * point, still holds. Resolves whether it did: a window with nothing
117
- * focusable in its content keeps the focus where it was.
130
+ * Move keyboard focus into `win`'s content once that content has rendered —
131
+ * and then only if `when`, read at that point, still holds: to a descendant
132
+ * marked `autofocus`, else to the first thing that takes the focus. An
133
+ * element whose `tabindex` is negative is passed over: a roving cell parked
134
+ * off the tab order, a disabled control. A kit host with no `tabindex` goes
135
+ * through, since it hands the focus on inside itself. Resolves whether the
136
+ * focus moved: a window with nothing focusable in its content keeps the
137
+ * focus where it was.
118
138
  */
119
139
  export declare function focusInto(win: Element, { when }?: {
120
140
  when?: () => boolean;
@@ -146,8 +166,18 @@ export declare function createWindowManager(desktop: VfDesktop, options: WindowM
146
166
  close(win: VfWindow, { to }?: {
147
167
  to?: VfViewportBox | null;
148
168
  }): Promise<boolean>;
149
- /** The close box's route: the window's own close, or a plain close. */
150
- requestClose(win: VfWindow): void;
169
+ /**
170
+ * The close box's route: the window's own close, given the modifier keys
171
+ * held, or a plain close. Resolves whether the window closed, once its
172
+ * close handler has settled.
173
+ */
174
+ requestClose(win: VfWindow, keys?: Partial<VfCloseKeys>): Promise<boolean>;
175
+ /**
176
+ * Ask each of an application's windows to close, front to back, palettes
177
+ * aside: a Quit. Resolves false at the first window still open once its
178
+ * handler has settled — a Cancel — and true when all have closed.
179
+ */
180
+ closeAll(app: string, keys?: Partial<VfCloseKeys>): Promise<boolean>;
151
181
  /** The window showing `item`, or null. */
152
182
  windowFor(item: string): VfWindow | null;
153
183
  /** Every window of an application, back to front. */
@@ -180,8 +210,16 @@ export declare function createWindowManager(desktop: VfDesktop, options: WindowM
180
210
  hasWindows(app: string): boolean;
181
211
  /** Re-read every palette's predicate: an application hid or showed one. */
182
212
  palettesChanged(): void;
183
- /** Write a box for a gesture the owner runs itself — a zoom, its group's arrange. */
213
+ /** Write a box for a gesture the owner runs itself — its group's arrange, a zoom of its own. */
184
214
  write(win: VfWindow, box: Placed): void;
215
+ /**
216
+ * The zoom box, for a window adopted with a `zoom`: away from its zoomed
217
+ * box, the window goes there, its box remembered; at it, the window goes
218
+ * back to that box, carried by its pin through any raster resize since,
219
+ * or to its `place` with none remembered. The zoom box's press runs it;
220
+ * so can a menu command.
221
+ */
222
+ zoom(win: VfWindow): void;
185
223
  /** The box a placement would write, not written. */
186
224
  clamped(win: VfWindow, box: BoxInput): Placed;
187
225
  /** A window's `place` on the live area, clamped, or null. */