vintage-frames 0.14.1 → 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 (55) hide show
  1. package/custom-elements.json +770 -74
  2. package/dist/chrome.d.ts +10 -1
  3. package/dist/chrome.js +8 -3
  4. package/dist/components/vf-button.d.ts +5 -1
  5. package/dist/components/vf-button.js +1 -1
  6. package/dist/components/vf-desktop.d.ts +5 -2
  7. package/dist/components/vf-desktop.js +5 -5
  8. package/dist/components/vf-dialog.d.ts +13 -2
  9. package/dist/components/vf-dialog.js +27 -27
  10. package/dist/components/vf-menu-bar.js +9 -10
  11. package/dist/components/vf-menu-item.js +18 -13
  12. package/dist/components/vf-menu.d.ts +8 -3
  13. package/dist/components/vf-menu.js +85 -61
  14. package/dist/components/vf-number-field.d.ts +4 -0
  15. package/dist/components/vf-number-field.js +28 -25
  16. package/dist/components/vf-paragraph.d.ts +3 -1
  17. package/dist/components/vf-paragraph.js +7 -0
  18. package/dist/components/vf-scroll-area.d.ts +16 -1
  19. package/dist/components/vf-scroll-area.js +39 -29
  20. package/dist/components/vf-select.d.ts +5 -4
  21. package/dist/components/vf-select.js +26 -17
  22. package/dist/components/vf-text-field.d.ts +5 -5
  23. package/dist/components/vf-text-field.js +1 -1
  24. package/dist/components/vf-window.d.ts +22 -5
  25. package/dist/components/vf-window.js +113 -98
  26. package/dist/cursor.d.ts +8 -0
  27. package/dist/cursor.js +70 -73
  28. package/dist/events.d.ts +3 -2
  29. package/dist/events.js +3 -3
  30. package/dist/index.d.ts +2 -1
  31. package/dist/modal-dialog.d.ts +49 -4
  32. package/dist/modal-dialog.js +115 -87
  33. package/dist/position.js +25 -20
  34. package/dist/shell/alert.d.ts +17 -9
  35. package/dist/shell/alert.js +25 -28
  36. package/dist/shell/catalog.d.ts +1 -1
  37. package/dist/shell/catalog.js +32 -18
  38. package/dist/shell/finder.d.ts +46 -1
  39. package/dist/shell/finder.js +166 -126
  40. package/dist/shell/index.d.ts +2 -4
  41. package/dist/shell/index.js +5 -6
  42. package/dist/shell/pure.d.ts +10 -0
  43. package/dist/shell/pure.js +4 -0
  44. package/dist/shell/shell.d.ts +40 -13
  45. package/dist/shell/shell.js +87 -51
  46. package/dist/shell/windows.d.ts +69 -12
  47. package/dist/shell/windows.js +245 -144
  48. package/dist/text-control.d.ts +30 -1
  49. package/dist/text-control.js +27 -6
  50. package/dist/top-layer.d.ts +13 -0
  51. package/dist/top-layer.js +16 -0
  52. package/docs/SPEC.md +25 -24
  53. package/editor/vscode.html-custom-data.json +22 -12
  54. package/editor/web-types.json +58 -25
  55. package/package.json +5 -1
@@ -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 };
@@ -7,21 +7,25 @@
7
7
  * nodes come back each time it is front, so items keep their state. A menu
8
8
  * off the bar has no key equivalents, so only the front application's fire.
9
9
  * The bar's label is the front application's name.
10
+ * - While an application's held dialog is open, the bar shows that
11
+ * application's name and menus instead; nothing else changes.
10
12
  * - Each application's `init(ctx)` gets the desktop, the window manager, its
11
- * own menus, the system menu, the other applications' actions (read at the
12
- * call), the catalog when a Finder runs, the site's services, `alert()`,
13
- * `gate()`, `onFront()` and `onDispose()`. Everything it sets up comes down
14
- * through `onDispose()`, so a hot reload starts clean.
13
+ * own menus, dialogs and window markup, the system menu, the other applications' actions
14
+ * (read at the call), the catalog when a Finder runs, the site's services,
15
+ * `ask()`, `gate()`, `onFront()` and `onDispose()`. Everything it sets up
16
+ * comes down through `onDispose()`, and its dialogs with it, so a hot
17
+ * reload starts clean. The shell composes no UI of its own.
15
18
  * - A window showing a catalog item follows it: its title follows a rename,
16
19
  * and it closes when the item is removed.
17
- * - The boot reads the catalog, seeds it once, reopens the last session's
18
- * 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.
19
23
  */
20
- import type { VfDesktop, VfMenu, VfMenuItem, VfViewportBox } from '../index.js';
24
+ import { VfWindow } from '../index.js';
25
+ import type { VfDesktop, VfDialog, VfMenu, VfMenuItem, VfModalDialog, VfViewportBox } from '../index.js';
21
26
  import type { Catalog, CatalogStorage, Item, Volumes } from './catalog.js';
22
27
  import type { WindowManager } from './windows.js';
23
28
  import type { ShellState } from './state.js';
24
- import type { AlertOptions } from './alert.js';
25
29
  /** A kind of catalog item an application registers: how it looks and opens. */
26
30
  export interface KindDefinition {
27
31
  /** Its 32×32 art: one URL, or one per item. */
@@ -57,6 +61,16 @@ export interface AppDefinition<Actions = unknown> {
57
61
  icon?: string;
58
62
  /** A fragment of `vf-menu` elements, each with a `data-menu` name. */
59
63
  menus?: string;
64
+ /**
65
+ * A fragment of dialogs, each with a `data-dialog` name: a `vf-dialog`, or
66
+ * an element that renders one inside itself. Held under the application.
67
+ */
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;
60
74
  /** The catalog kinds it opens. */
61
75
  kinds?: Record<string, KindDefinition>;
62
76
  /** The Finder's: the catalog every application shares. */
@@ -98,8 +112,24 @@ export interface AppContext {
98
112
  onMenu(fn: (value: string, item: VfMenuItem) => void): void;
99
113
  /** Put an item in the system menu, which runs `fn` when picked. */
100
114
  systemItem(value: string, label: string, fn: () => void): VfMenuItem;
101
- /** Show an alert with the site's caution art; resolves the button's value. */
102
- alert(message: string, options?: AlertOptions): Promise<string | null>;
115
+ /** One of its dialogs by `data-dialog`; throws when the markup drifts. */
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;
123
+ /** Hold a dialog it made in code: appended to the desktop, held under it, removed with it. Returns it. */
124
+ hold<T extends Element>(dialog: T): T;
125
+ /** Let a dialog it holds go, and remove it. */
126
+ release(dialog: Element): void;
127
+ /**
128
+ * Show a dialog it holds, and resolve its `returnValue`: the value of the
129
+ * button that submitted it, or null when it closed without one. Throws for
130
+ * a dialog it doesn't hold.
131
+ */
132
+ ask(dialog: VfModalDialog): Promise<string | null>;
103
133
  /** Keep `item` disabled while `test` is false: re-run on every press, key, selection change and activation. */
104
134
  gate(item: VfMenuItem, test: () => boolean): void;
105
135
  /** Whether a text field has keyboard focus, read through shadow roots at the call. */
@@ -128,8 +158,6 @@ export interface ShellOptions {
128
158
  services?: Record<string, unknown>;
129
159
  /** The clock in the menu bar's `end` slot. Default true. */
130
160
  clock?: boolean;
131
- /** The 32×32 caution art the alerts carry. */
132
- caution?: string | null;
133
161
  }
134
162
  /** A running shell. */
135
163
  export interface Shell {
@@ -139,7 +167,6 @@ export interface Shell {
139
167
  readonly apps: Record<string, any>;
140
168
  /** Settles once the boot has read the catalog and reopened the session. */
141
169
  readonly ready: Promise<void>;
142
- alert(message: string, options?: AlertOptions): Promise<string | null>;
143
170
  dispose(): void;
144
171
  }
145
172
  /** The kind the shell registers: an application's icon, which opens it. */
@@ -2,10 +2,9 @@ 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";
8
- import { showAlert as s } from "./alert.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";
9
8
  //#region src/shell/shell.ts
10
9
  function c(e) {
11
10
  return e;
@@ -62,22 +61,30 @@ function d(c, l) {
62
61
  }
63
62
  };
64
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));
65
- let j = (e, t = {}) => s(c, e, {
66
- art: l.caution ?? null,
67
- ...t
68
- }), M = (e) => {
64
+ let j = (e) => {
69
65
  if (!e) return [];
70
66
  let t = document.createElement("div");
71
67
  t.innerHTML = e, customElements.upgrade(t);
72
- let n = [...t.children].filter((e) => e.localName === "vf-menu");
68
+ let n = [...t.children].filter((e) => e instanceof HTMLElement);
73
69
  for (let e of n) e.remove();
74
70
  return n;
75
- }, N = d.map((e) => ({
71
+ }, M = d.map((e) => ({
76
72
  def: e,
77
- menus: M(e.menus)
78
- })), P = [];
79
- for (let { def: e, menus: t } of N) {
80
- let n = [], r = `vintage-frames/shell (${e.id})`, i = {
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();
86
+ });
87
+ let s = {
81
88
  desktop: c,
82
89
  windows: S,
83
90
  menus: t,
@@ -91,7 +98,7 @@ function d(c, l) {
91
98
  app: (e) => d.find((t) => t.id === e),
92
99
  menu(e) {
93
100
  let n = t.find((t) => t.dataset.menu === e);
94
- if (!n) throw Error(`${r}: missing menu ${e}`);
101
+ if (!n) throw Error(`${a}: missing menu ${e}`);
95
102
  return n;
96
103
  },
97
104
  item(e) {
@@ -99,76 +106,101 @@ function d(c, l) {
99
106
  let t = n.querySelector(`vf-menu-item[value="${CSS.escape(e)}"]`);
100
107
  if (t) return t;
101
108
  }
102
- throw Error(`${r}: missing menu item ${e}`);
109
+ throw Error(`${a}: missing menu item ${e}`);
103
110
  },
104
111
  onMenu(e) {
105
- for (let n of t) i.on(n, "vf-menu-select", (t) => {
112
+ for (let n of t) s.on(n, "vf-menu-select", (t) => {
106
113
  if (D()) return;
107
114
  let { value: n, item: r } = t.detail;
108
115
  e(n, r);
109
116
  });
110
117
  },
111
- systemItem(e, t, r) {
112
- let a = document.createElement("vf-menu-item");
113
- return a.setAttribute("value", e), a.textContent = t, y.append(a), i.on(y, "vf-menu-select", (t) => {
114
- !D() && t.detail.value === e && r();
115
- }), n.push(() => a.remove()), a;
118
+ systemItem(e, t, n) {
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;
123
+ },
124
+ dialog(e) {
125
+ let t = n.find((t) => t.dataset.dialog === e);
126
+ if (!t) throw Error(`${a}: missing dialog ${e}`);
127
+ return t;
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
+ },
135
+ hold(t) {
136
+ return S.holdDialog(t, { app: e.id }), o.add(t), t;
137
+ },
138
+ release(e) {
139
+ o.delete(e) && (S.releaseDialog(e), e.remove());
140
+ },
141
+ ask(t) {
142
+ if (S.appOf(t) !== e.id) throw Error(`${a}: ask() takes a dialog the application holds`);
143
+ return new Promise((e) => {
144
+ let n = (r) => {
145
+ r.target === t && (t.removeEventListener("vf-close", n), e(r.detail.returnValue));
146
+ };
147
+ t.addEventListener("vf-close", n), t.show();
148
+ });
116
149
  },
117
- alert: j,
118
150
  gate(e, t) {
119
- k.set(e, t), n.push(() => k.delete(e));
120
- let r = !t();
121
- e.disabled !== r && (e.disabled = r);
151
+ k.set(e, t), i.push(() => k.delete(e));
152
+ let n = !t();
153
+ e.disabled !== n && (e.disabled = n);
122
154
  },
123
155
  typing: O,
124
156
  modalOpen: D,
125
157
  onFront(t) {
126
- t(S.front === e.id), n.push(S.onFront((n) => t(n === e.id)));
158
+ t(S.front === e.id), i.push(S.onFront((n) => t(n === e.id)));
127
159
  },
128
- on(e, t, r, i) {
129
- e.addEventListener(t, r, i), n.push(() => e.removeEventListener(t, r, i));
160
+ on(e, t, n, r) {
161
+ e.addEventListener(t, n, r), i.push(() => e.removeEventListener(t, n, r));
130
162
  },
131
163
  onDispose(e) {
132
- n.push(e);
164
+ i.push(e);
133
165
  }
134
- }, a = e.init(i);
135
- a && (w[e.id] = a), P.push(() => {
136
- for (let e of n.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();
137
169
  });
138
170
  }
139
- let F = null, I = () => {
140
- let e = N.find((e) => e.def.id === S.front) ?? N.find((e) => e.def.id === m) ?? null;
141
- if (!(!e || e === F)) {
142
- if (F) for (let e of F.menus) e.remove();
143
- y.after(...e.menus), v.label = e.def.name, F = e, A();
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();
144
176
  }
145
177
  };
146
- if (_.push(S.onFront(I)), I(), l.clock !== !1) {
178
+ if (_.push(S.onFront(F), S.onDialogs(F)), F(), l.clock !== !1) {
147
179
  let e = v.querySelector(":scope > [slot=\"end\"]"), t = !e;
148
180
  e || (e = document.createElement("vf-label"), e.slot = "end", v.append(e));
149
- let n = o(e);
181
+ let n = s(e);
150
182
  _.push(() => {
151
183
  n(), t && e.remove();
152
184
  });
153
185
  }
154
- let L = /* @__PURE__ */ new WeakSet(), R = () => {
186
+ let I = /* @__PURE__ */ new WeakSet(), L = () => {
155
187
  if (E) for (let e of c.stackingOrder) {
156
188
  if (!(e instanceof t)) continue;
157
189
  let n = S.itemOf(e);
158
190
  if (n == null) continue;
159
191
  let r = E.item(n);
160
- r ? (L.add(e), e.heading !== r.name && (e.heading = r.name)) : L.has(e) && E.get().available && S.close(e, { to: null });
192
+ r ? (I.add(e), e.heading !== r.name && (e.heading = r.name)) : I.has(e) && E.get().available && S.close(e, { to: null });
161
193
  }
162
194
  };
163
- return E && _.push(E.subscribe(R), S.onWindows(R)), h?.hold(), {
195
+ return E && _.push(E.subscribe(L), S.onWindows(L)), h?.hold(), {
164
196
  desktop: c,
165
197
  windows: S,
166
198
  catalog: E,
167
199
  apps: w,
168
200
  ready: (async () => {
169
201
  try {
170
- E && (await E.refresh(), p?.catalog?.seed && await E.seed(p.catalog.seed));
171
- for (let { item: e, app: t } of a(h?.saved ?? null)) await w[t]?.open?.({ item: e });
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 });
172
204
  let e = h?.saved?.active;
173
205
  if (e) {
174
206
  let t = S.windowFor(e);
@@ -186,20 +218,24 @@ function d(c, l) {
186
218
  S.onWindows(e),
187
219
  ...E ? [E.subscribe(e)] : []
188
220
  ];
189
- return c.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
+ }), () => {
190
227
  for (let e of t) e();
191
- c.removeEventListener("vf-activate", e);
228
+ c.removeEventListener("vf-activate", e), n.disconnect();
192
229
  };
193
230
  });
194
231
  _.push(e), h.release();
195
232
  }
196
233
  }
197
234
  })(),
198
- alert: j,
199
235
  dispose() {
200
- for (let e of P.splice(0).reverse()) e();
201
- if (F) for (let e of F.menus) e.remove();
202
- F = null;
236
+ for (let e of N.splice(0).reverse()) e();
237
+ if (P) for (let e of P.menus) e.remove();
238
+ P = null;
203
239
  for (let e of _.splice(0)) e();
204
240
  S.dispose(), E?.dispose();
205
241
  }
@@ -29,9 +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.
35
+ * - Dialogs: `holdDialog()` holds an application's dialog under it, in the
36
+ * desktop. The manager follows each held dialog's `open` attribute, and
37
+ * `asking` is the application of the one opened last and still open.
32
38
  */
33
39
  import { VfWindow } from '../index.js';
34
- import type { VfDesktop, VfViewportBox } from '../index.js';
40
+ import type { VfCloseKeys, VfDesktop, VfViewportBox } from '../index.js';
35
41
  import type { Bands, Box, Pin, Policy, Size } from './geometry.js';
36
42
  /** A box to write, any edge optional. */
37
43
  export interface BoxInput {
@@ -65,6 +71,13 @@ export interface AdoptOptions {
65
71
  policy?: ((cur: Box) => Policy) | null;
66
72
  /** A box held across a raster resize while the window is near it. */
67
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;
68
81
  /**
69
82
  * A palette: shown while its application is front — and while the
70
83
  * predicate holds, for one the application hides for its own reasons —
@@ -74,10 +87,14 @@ export interface AdoptOptions {
74
87
  /**
75
88
  * What the close box means. Unset, it closes the window. Set, the close box
76
89
  * asks the application, which calls `close()` once it decides — after a
77
- * 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.
78
93
  */
79
- close?: ((win: VfWindow) => void) | null;
94
+ close?: CloseHandler | null;
80
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>;
81
98
  /** What opening a window for an item declares. */
82
99
  export interface OpenOptions extends Omit<AdoptOptions, 'pin' | 'palette'> {
83
100
  /** Makes the window, not yet appended. */
@@ -105,13 +122,19 @@ export interface WindowManagerOptions {
105
122
  /** A saved pin by item — the session the boot read. */
106
123
  savedPin?: (item: string) => Pin | null;
107
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>;
108
127
  /** The innermost focused element, through open shadow roots. */
109
128
  export declare function deepActiveElement(): Element | null;
110
129
  /**
111
- * Move keyboard focus to the first thing in `win`'s content that takes it,
112
- * once that content has rendered — and then only if `when`, read at that
113
- * point, still holds. Resolves whether it did: a window with nothing
114
- * 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.
115
138
  */
116
139
  export declare function focusInto(win: Element, { when }?: {
117
140
  when?: () => boolean;
@@ -143,16 +166,40 @@ export declare function createWindowManager(desktop: VfDesktop, options: WindowM
143
166
  close(win: VfWindow, { to }?: {
144
167
  to?: VfViewportBox | null;
145
168
  }): Promise<boolean>;
146
- /** The close box's route: the window's own close, or a plain close. */
147
- 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>;
148
181
  /** The window showing `item`, or null. */
149
182
  windowFor(item: string): VfWindow | null;
150
183
  /** Every window of an application, back to front. */
151
184
  windowsOf(app: string): VfWindow[];
152
185
  /** A window's item, or null. */
153
186
  itemOf(win: Element | null): string | null;
154
- /** A window's application, or null when the manager doesn't hold it. */
155
- appOf(win: Element | null): string | null;
187
+ /** A window's or a held dialog's application, or null when the manager doesn't hold it. */
188
+ appOf(el: Element | null): string | null;
189
+ /**
190
+ * Hold an application's dialog: a `vf-dialog`, or an element that renders
191
+ * one inside itself. It is appended to the desktop unless it is already
192
+ * in it, and its `open` is followed from then on.
193
+ */
194
+ holdDialog(dialog: Element, { app }: {
195
+ app: string;
196
+ }): void;
197
+ /** Let a held dialog go. It stays where it is; the caller removes it. */
198
+ releaseDialog(dialog: Element): void;
199
+ /** An application's held dialogs. */
200
+ dialogsOf(app: string): Element[];
201
+ /** The application of the held dialog opened last and still open, or null. */
202
+ readonly asking: string | null;
156
203
  /** Whether `win` is a palette. */
157
204
  isPalette(win: Element | null): boolean;
158
205
  /** Change the item a window shows: an untitled document's first save, say. */
@@ -163,8 +210,16 @@ export declare function createWindowManager(desktop: VfDesktop, options: WindowM
163
210
  hasWindows(app: string): boolean;
164
211
  /** Re-read every palette's predicate: an application hid or showed one. */
165
212
  palettesChanged(): void;
166
- /** 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. */
167
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;
168
223
  /** The box a placement would write, not written. */
169
224
  clamped(win: VfWindow, box: BoxInput): Placed;
170
225
  /** A window's `place` on the live area, clamped, or null. */
@@ -209,6 +264,8 @@ export declare function createWindowManager(desktop: VfDesktop, options: WindowM
209
264
  beforeFront(fn: (win: HTMLElement | null) => void): () => void;
210
265
  /** The front application changed. */
211
266
  onFront(fn: (app: string) => void): () => void;
267
+ /** A held dialog opened or closed, so `asking` may have changed. */
268
+ onDialogs(fn: () => void): () => void;
212
269
  dispose(): void;
213
270
  };
214
271
  export type WindowManager = ReturnType<typeof createWindowManager>;