vintage-frames 0.15.0 → 0.16.1

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 +907 -99
  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 +14 -8
  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 +27 -5
  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 +57 -13
  39. package/dist/shell/windows.js +214 -154
  40. package/dist/text-control.d.ts +46 -3
  41. package/dist/text-control.js +33 -5
  42. package/dist/top-layer.d.ts +13 -0
  43. package/dist/top-layer.js +16 -0
  44. package/docs/SPEC.md +25 -24
  45. package/editor/vscode.html-custom-data.json +36 -11
  46. package/editor/web-types.json +81 -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 };
@@ -17,7 +17,8 @@
17
17
  * none is active. `beforeFront` runs before a change, `onFront` reports it.
18
18
  * - Palettes are an application's utility windows: shown while it is front
19
19
  * and hidden otherwise, never closed or removed.
20
- * - Opening a window moves keyboard focus into it; closing the active one
20
+ * - Opening a window moves keyboard focus into it, and a new window's field
21
+ * marked `autoselect` opens with its text selected; closing the active one
21
22
  * moves focus to the window that becomes active, else to the closed
22
23
  * window's icon, else to the desktop's icons. Focus left behind in a
23
24
  * window that is no longer active — a title-bar press moves none — goes
@@ -29,12 +30,15 @@
29
30
  * resizable window's pin when its size changed, floors it at the window's
30
31
  * own size limits and caps it to the window area, so growing back restores
31
32
  * every place.
33
+ * - The zoom box, for a window adopted with `zoom`: the window goes to its
34
+ * zoomed box and back to the box it had, kept as a pin; a zoomed window
35
+ * stays zoomed across a resize.
32
36
  * - Dialogs: `holdDialog()` holds an application's dialog under it, in the
33
37
  * desktop. The manager follows each held dialog's `open` attribute, and
34
38
  * `asking` is the application of the one opened last and still open.
35
39
  */
36
40
  import { VfWindow } from '../index.js';
37
- import type { VfDesktop, VfViewportBox } from '../index.js';
41
+ import type { VfCloseKeys, VfDesktop, VfViewportBox } from '../index.js';
38
42
  import type { Bands, Box, Pin, Policy, Size } from './geometry.js';
39
43
  /** A box to write, any edge optional. */
40
44
  export interface BoxInput {
@@ -68,6 +72,13 @@ export interface AdoptOptions {
68
72
  policy?: ((cur: Box) => Policy) | null;
69
73
  /** A box held across a raster resize while the window is near it. */
70
74
  keep?: ((area: Box) => Box) | null;
75
+ /**
76
+ * Its zoom box: the window's zoomed box on the window area, given its
77
+ * current box (to keep its top-left, say). Set, the manager runs the zoom
78
+ * box: it goes to this box and back to the box the user had, and a window
79
+ * at it stays zoomed across a raster resize.
80
+ */
81
+ zoom?: ((area: Box, box: Box) => BoxInput) | null;
71
82
  /**
72
83
  * A palette: shown while its application is front — and while the
73
84
  * predicate holds, for one the application hides for its own reasons —
@@ -77,10 +88,14 @@ export interface AdoptOptions {
77
88
  /**
78
89
  * What the close box means. Unset, it closes the window. Set, the close box
79
90
  * asks the application, which calls `close()` once it decides — after a
80
- * Save prompt, say — or never.
91
+ * Save prompt, say — or never. It gets the modifier keys held on the
92
+ * close box's click; a close from a menu or code holds none. A handler
93
+ * that decides later returns its promise, so `closeAll()` waits for it.
81
94
  */
82
- close?: ((win: VfWindow) => void) | null;
95
+ close?: CloseHandler | null;
83
96
  }
97
+ /** A window's close handler: decides whether it closes, and calls `close()` if so. */
98
+ export type CloseHandler = (win: VfWindow, keys: Partial<VfCloseKeys>) => void | Promise<void>;
84
99
  /** What opening a window for an item declares. */
85
100
  export interface OpenOptions extends Omit<AdoptOptions, 'pin' | 'palette'> {
86
101
  /** Makes the window, not yet appended. */
@@ -108,16 +123,26 @@ export interface WindowManagerOptions {
108
123
  /** A saved pin by item — the session the boot read. */
109
124
  savedPin?: (item: string) => Pin | null;
110
125
  }
126
+ /** Settles once the desktop and its menu bar have rendered, so `workArea` measures the bar. */
127
+ export declare function desktopRendered(desktop: VfDesktop): Promise<void>;
111
128
  /** The innermost focused element, through open shadow roots. */
112
129
  export declare function deepActiveElement(): Element | null;
113
130
  /**
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.
131
+ * Move keyboard focus into `win`'s content once that content has rendered —
132
+ * and then only if `when`, read at that point, still holds: to the first
133
+ * descendant marked `autofocus` or `autoselect`, else to the first thing that
134
+ * takes the focus. With `select`, a control marked `autoselect` has its text
135
+ * selected too; the window manager passes it on a window's first open only,
136
+ * so a caret placed in the window survives a return to it. An element whose
137
+ * `tabindex` is negative is passed over: a roving cell parked off the tab
138
+ * order, a disabled control. A kit host with no `tabindex` goes through,
139
+ * since it hands the focus on inside itself. Resolves whether the focus
140
+ * moved: a window with nothing focusable in its content keeps the focus where
141
+ * it was.
118
142
  */
119
- export declare function focusInto(win: Element, { when }?: {
143
+ export declare function focusInto(win: Element, { when, select }?: {
120
144
  when?: () => boolean;
145
+ select?: boolean;
121
146
  }): Promise<boolean>;
122
147
  /** The window manager over a desktop. */
123
148
  export declare function createWindowManager(desktop: VfDesktop, options: WindowManagerOptions): {
@@ -135,7 +160,8 @@ export declare function createWindowManager(desktop: VfDesktop, options: WindowM
135
160
  * Open the window for `item`: raise it if one is open, else make it,
136
161
  * append it, adopt it and place it — this session's pin, then the saved
137
162
  * one, then `place`, by default the first free cascade slot — and show it
138
- * out of `from`. Keyboard focus moves into it. Returns the window.
163
+ * out of `from`. Keyboard focus moves into it, and a new window's field
164
+ * marked `autoselect` has its text selected. Returns the window.
139
165
  */
140
166
  open(opts: OpenOptions): VfWindow;
141
167
  /**
@@ -146,8 +172,18 @@ export declare function createWindowManager(desktop: VfDesktop, options: WindowM
146
172
  close(win: VfWindow, { to }?: {
147
173
  to?: VfViewportBox | null;
148
174
  }): Promise<boolean>;
149
- /** The close box's route: the window's own close, or a plain close. */
150
- requestClose(win: VfWindow): void;
175
+ /**
176
+ * The close box's route: the window's own close, given the modifier keys
177
+ * held, or a plain close. Resolves whether the window closed, once its
178
+ * close handler has settled.
179
+ */
180
+ requestClose(win: VfWindow, keys?: Partial<VfCloseKeys>): Promise<boolean>;
181
+ /**
182
+ * Ask each of an application's windows to close, front to back, palettes
183
+ * aside: a Quit. Resolves false at the first window still open once its
184
+ * handler has settled — a Cancel — and true when all have closed.
185
+ */
186
+ closeAll(app: string, keys?: Partial<VfCloseKeys>): Promise<boolean>;
151
187
  /** The window showing `item`, or null. */
152
188
  windowFor(item: string): VfWindow | null;
153
189
  /** Every window of an application, back to front. */
@@ -180,8 +216,16 @@ export declare function createWindowManager(desktop: VfDesktop, options: WindowM
180
216
  hasWindows(app: string): boolean;
181
217
  /** Re-read every palette's predicate: an application hid or showed one. */
182
218
  palettesChanged(): void;
183
- /** Write a box for a gesture the owner runs itself — a zoom, its group's arrange. */
219
+ /** Write a box for a gesture the owner runs itself — its group's arrange, a zoom of its own. */
184
220
  write(win: VfWindow, box: Placed): void;
221
+ /**
222
+ * The zoom box, for a window adopted with a `zoom`: away from its zoomed
223
+ * box, the window goes there, its box remembered; at it, the window goes
224
+ * back to that box, carried by its pin through any raster resize since,
225
+ * or to its `place` with none remembered. The zoom box's press runs it;
226
+ * so can a menu command.
227
+ */
228
+ zoom(win: VfWindow): void;
185
229
  /** The box a placement would write, not written. */
186
230
  clamped(win: VfWindow, box: BoxInput): Placed;
187
231
  /** A window's `place` on the live area, clamped, or null. */