vintage-frames 0.18.0 → 0.19.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/README.md +1 -1
  2. package/custom-elements.json +278 -51
  3. package/dist/build/app-file.d.ts +2 -2
  4. package/dist/build/app-file.js +1 -0
  5. package/dist/build/plugins.d.ts +2 -2
  6. package/dist/build/plugins.js +62 -59
  7. package/dist/components/vf-icon.d.ts +7 -0
  8. package/dist/components/vf-icon.js +101 -81
  9. package/dist/components/vf-menu-bar.d.ts +6 -4
  10. package/dist/components/vf-menu-bar.js +26 -56
  11. package/dist/components/vf-menu-item.d.ts +33 -5
  12. package/dist/components/vf-menu-item.js +128 -61
  13. package/dist/components/vf-menu.d.ts +35 -0
  14. package/dist/components/vf-menu.js +171 -111
  15. package/dist/glyphs.d.ts +6 -0
  16. package/dist/glyphs.js +18 -14
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +15 -14
  19. package/dist/menu-navigation.d.ts +34 -0
  20. package/dist/menu-navigation.js +53 -0
  21. package/dist/menu-press.d.ts +25 -7
  22. package/dist/menu-press.js +78 -44
  23. package/dist/motion.d.ts +10 -0
  24. package/dist/shell/app-file.d.ts +20 -4
  25. package/dist/shell/app-file.js +65 -53
  26. package/dist/shell/catalog.d.ts +79 -1
  27. package/dist/shell/catalog.js +206 -153
  28. package/dist/shell/finder.d.ts +28 -9
  29. package/dist/shell/finder.js +339 -267
  30. package/dist/shell/geometry.d.ts +7 -0
  31. package/dist/shell/geometry.js +19 -3
  32. package/dist/shell/index.d.ts +4 -4
  33. package/dist/shell/index.js +12 -12
  34. package/dist/shell/pure.d.ts +1 -1
  35. package/dist/shell/pure.js +6 -6
  36. package/dist/shell/shell.d.ts +14 -10
  37. package/dist/shell/shell.js +148 -116
  38. package/dist/shell/version.d.ts +1 -1
  39. package/dist/shell/version.js +1 -1
  40. package/dist/styles/body-italic-font.d.ts +2 -0
  41. package/dist/styles/body-italic-font.js +10 -0
  42. package/dist/styles/register-embedded-font.d.ts +5 -4
  43. package/dist/styles/register-embedded-font.js +11 -10
  44. package/docs/SPEC.md +11 -7
  45. package/editor/vscode.html-custom-data.json +15 -5
  46. package/editor/web-types.json +55 -9
  47. package/package.json +7 -3
@@ -21,8 +21,7 @@ export interface MenuPressTarget {
21
21
  * driven: **press a title, keep the button down, slide onto a command, release
22
22
  * over it to run it.** Sliding sideways onto another title switches menus
23
23
  * mid-press; releasing over a disabled row, a separator, the title, or off the
24
- * menu entirely closes with nothing chosen (the classic "release outside"
25
- * cancel).
24
+ * menu entirely closes with nothing chosen (the classic "release outside").
26
25
  *
27
26
  * The modern click-to-open style coexists, disambiguated by the gesture itself
28
27
  * and resolved at the first release: a quick in-place tap on a title (under
@@ -31,15 +30,28 @@ export interface MenuPressTarget {
31
30
  * in-place release — any press that travels to another title or row is a
32
31
  * drag-pick however long it took.
33
32
  *
33
+ * **One pointer model for both.** While a menu is open the pointer is tracked
34
+ * with or without a button down; the button only changes what a release
35
+ * means. Each open menu highlights the row under the pointer, or, with the
36
+ * pointer elsewhere, the item whose submenu is open. The pointer resting on
37
+ * an item for {@link SUBMENU_OPEN_MS} opens its submenu, and an open submenu
38
+ * stays {@link SUBMENU_CLOSE_MS} after the pointer moves onto another row of
39
+ * its menu, so a diagonal slide into it can cross the rows between. A release
40
+ * on an item that opens a submenu chooses nothing: the submenu opens now, and
41
+ * every menu stays dropped for clicking on.
42
+ *
34
43
  * Everything is hit-tested by **coordinates**, not by event target, for the
35
44
  * same reason `vf-select` is: under touch, the pointer is implicitly captured
36
45
  * by the element the gesture started on, so every move and the release itself
37
46
  * are delivered to the pressed title no matter what they are actually over.
47
+ * The open menus are tried deepest first, since a submenu is painted over its
48
+ * parent.
38
49
  *
39
50
  * The host binds {@link onPointerDown} to a `pointerdown` on itself — presses
40
- * on titles and rows both bubble up composed — and supplies a
41
- * {@link MenuPressTarget}. Everything else (tracking to the release anywhere on
42
- * the page, the row highlight, classification) lives here.
51
+ * on titles and rows both bubble up composed — calls {@link track} as its
52
+ * menus open and close, and supplies a {@link MenuPressTarget}. Everything
53
+ * else (tracking to the release anywhere on the page, the highlight, the
54
+ * submenus, classification) lives here.
43
55
  */
44
56
  export declare class MenuPressController implements ReactiveController {
45
57
  #private;
@@ -47,10 +59,16 @@ export declare class MenuPressController implements ReactiveController {
47
59
  constructor(host: ReactiveControllerHost, target: MenuPressTarget);
48
60
  /**
49
61
  * Host torn down mid-gesture. The listener controller detaches itself; drop
50
- * the press state and any highlight it stamped on a row, which the normal
51
- * close path is no longer going to run for.
62
+ * the press and tracking state, which the normal close path is no longer
63
+ * going to run for.
52
64
  */
53
65
  hostDisconnected(): void;
66
+ /**
67
+ * The host opened (`true`) or closed (`false`) its menus. While one is open
68
+ * the pointer is tracked with or without a button down, so a resting mouse
69
+ * highlights rows and opens submenus in the click-to-open style too.
70
+ */
71
+ track(open: boolean): void;
54
72
  /**
55
73
  * Starts a press: drops the menu under the pointer if it isn't already, then
56
74
  * tracks the press to its release. Both interaction styles begin here; the
@@ -1,7 +1,8 @@
1
1
  import { DocumentListenersController as e } from "./document-listeners.js";
2
2
  import "./motion.js";
3
+ import { openPath as t } from "./menu-navigation.js";
3
4
  //#region src/menu-press.ts
4
- var t = class {
5
+ var n = class {
5
6
  #e;
6
7
  #t;
7
8
  #n;
@@ -9,89 +10,122 @@ var t = class {
9
10
  #i;
10
11
  #a;
11
12
  #o;
13
+ #s;
14
+ #c;
15
+ #l;
12
16
  constructor(t, n) {
13
- this.target = n, this.#e = !1, this.#t = !1, this.#n = !1, this.#r = null, this.#i = 0, this.#a = null, this.onPointerDown = (e) => {
17
+ this.target = n, this.#e = !1, this.#t = !1, this.#n = !1, this.#r = null, this.#i = 0, this.#a = !1, this.#o = !1, this.#s = null, this.onPointerDown = (e) => {
14
18
  if (e.button > 0 || !e.isPrimary) return;
15
- let t = this.#p(e.clientX, e.clientY);
16
- if (t) {
17
- if (e.preventDefault(), this.#e = !0, this.#n = !1, this.#r = t.item ?? t.menu, this.#i = e.timeStamp, this.#t = !t.menu.open, this.#o.attach(), t.item) {
18
- this.#f(t.item);
19
+ let t = this.#_(e.clientX, e.clientY);
20
+ if (!(!t || !t.title && !t.item)) {
21
+ if (e.preventDefault(), this.#e = !0, this.#n = !1, this.#r = t.item ?? t.menu, this.#i = e.timeStamp, this.#t = !t.menu.open, this.#l.attach(), t.item) {
22
+ this.#h(t);
19
23
  return;
20
24
  }
21
25
  t.menu.focus(), this.#t && this.target.open(t.menu);
22
26
  }
23
- }, this.#s = (e) => {
24
- if (!this.#e) return;
25
- e.preventDefault();
26
- let t = this.#p(e.clientX, e.clientY), n = t ? t.item ?? t.menu : null;
27
- !this.#n && n !== this.#r && (this.#n = !0), t && !t.item && !t.menu.open && this.target.open(t.menu), this.#f(t?.item ?? null);
28
- }, this.#c = (e) => {
27
+ }, this.#u = (e) => {
28
+ if (this.#o) return;
29
+ let t = this.#_(e.clientX, e.clientY);
30
+ if (this.#e) {
31
+ e.preventDefault();
32
+ let n = t ? t.item ?? (t.title ? t.menu : null) : null;
33
+ !this.#n && n !== this.#r && (this.#n = !0), t?.title && !t.menu.open && this.target.open(t.menu);
34
+ }
35
+ this.#h(t);
36
+ }, this.#d = (e) => {
29
37
  if (!this.#e) return;
30
- let t = this.#p(e.clientX, e.clientY), n = this.#t, r = !this.#n, i = e.timeStamp - this.#i < 200;
31
- this.#u(), !(n && r && i) && this.#d(t?.item ?? null);
32
- }, this.#l = () => {
33
- this.#u(), this.#f(null);
34
- }, t.addController(this), this.#o = new e(t, () => [
38
+ let t = this.#_(e.clientX, e.clientY), n = this.#t, r = !this.#n, i = e.timeStamp - this.#i < 200;
39
+ this.#p(), !(n && r && i) && this.#m(t && !t.title ? t : null);
40
+ }, this.#f = () => {
41
+ this.#p(), this.#h(null);
42
+ }, t.addController(this), this.#l = new e(t, () => [
35
43
  [
36
44
  document,
37
45
  "pointermove",
38
- this.#s,
46
+ this.#u,
39
47
  !0
40
48
  ],
41
49
  [
42
50
  document,
43
51
  "pointerup",
44
- this.#c,
52
+ this.#d,
45
53
  !0
46
54
  ],
47
55
  [
48
56
  document,
49
57
  "pointercancel",
50
- this.#l,
58
+ this.#f,
51
59
  !0
52
60
  ]
53
61
  ]);
54
62
  }
55
63
  hostDisconnected() {
56
- this.#u(), this.#f(null);
64
+ this.#p(), this.track(!1);
57
65
  }
58
- #s;
59
- #c;
60
- #l;
61
- #u() {
62
- this.#e && (this.#e = !1, this.#r = null, this.#o.detach());
66
+ track(e) {
67
+ if (this.#a = e, e) {
68
+ this.#l.attach();
69
+ return;
70
+ }
71
+ this.#o = !1, this.#s = null, this.#g(), this.#e || this.#l.detach();
63
72
  }
64
- #d(e) {
65
- if (!e || e.disabled) {
66
- this.#f(null), this.target.close();
73
+ #u;
74
+ #d;
75
+ #f;
76
+ #p() {
77
+ this.#e && (this.#e = !1, this.#r = null, this.#a || this.#l.detach());
78
+ }
79
+ #m(e) {
80
+ let t = e?.item;
81
+ if (!e || !t || t.disabled) {
82
+ this.target.close();
67
83
  return;
68
84
  }
69
- this.#f(e), this.#a = null, e.activate();
85
+ if (this.#g(), t.submenu) {
86
+ e.menu.expand(t);
87
+ return;
88
+ }
89
+ this.#h(e), this.#o = !0, t.activate();
90
+ }
91
+ #h(e) {
92
+ let n = e && !e.title ? {
93
+ menu: e.menu,
94
+ row: e.item && !e.item.disabled ? e.item : null
95
+ } : null, r = this.#s;
96
+ if (r?.menu === n?.menu && r?.row === n?.row) return;
97
+ this.#s = n;
98
+ for (let e of this.target.menus()) if (e.open) for (let r of t(e)) r.pointerOver(r === n?.menu ? n.row : void 0);
99
+ if (this.#g(), !n) return;
100
+ let { menu: i, row: a } = n, o = i.expandedItem;
101
+ (a === null || a !== o) && (o ? this.#c = window.setTimeout(() => i.expand(a?.submenu ? a : null), 300) : a?.submenu && (this.#c = window.setTimeout(() => i.expand(a), 150)));
70
102
  }
71
- #f(e) {
72
- let t = e && !e.disabled ? e : null;
73
- t !== this.#a && (this.#a && (this.#a.active = !1), this.#a = t, t && (t.active = !0));
103
+ #g() {
104
+ this.#c !== void 0 && window.clearTimeout(this.#c), this.#c = void 0;
74
105
  }
75
- #p(e, t) {
76
- let r = this.target.menus(), i = r.find((e) => e.open);
77
- if (i) {
78
- for (let r of i.allItems) if (n(r.getBoundingClientRect(), e, t)) return {
106
+ #_(e, n) {
107
+ let i = this.target.menus(), a = i.find((e) => e.open);
108
+ if (a) for (let i of t(a).reverse()) {
109
+ let t = i.panelRect;
110
+ if (!(!t || !r(t, e, n))) return {
79
111
  menu: i,
80
- item: r
112
+ title: !1,
113
+ item: i.allItems.find((t) => r(t.getBoundingClientRect(), e, n)) ?? null
81
114
  };
82
115
  }
83
- for (let i of r) {
84
- let r = i.labelRect;
85
- if (r && n(r, e, t)) return {
86
- menu: i,
116
+ for (let t of i) {
117
+ let i = t.labelRect;
118
+ if (i && r(i, e, n)) return {
119
+ menu: t,
120
+ title: !0,
87
121
  item: null
88
122
  };
89
123
  }
90
124
  return null;
91
125
  }
92
126
  };
93
- function n(e, t, n) {
127
+ function r(e, t, n) {
94
128
  return t >= e.left && t < e.right && n >= e.top && n < e.bottom;
95
129
  }
96
130
  //#endregion
97
- export { t as MenuPressController };
131
+ export { n as MenuPressController };
package/dist/motion.d.ts CHANGED
@@ -19,6 +19,16 @@ export declare function prefersReducedMotion(): boolean;
19
19
  * drag-pick regardless of duration.
20
20
  */
21
21
  export declare const PRESS_HOLD_MS = 200;
22
+ /**
23
+ * How long (ms) the pointer rests on a menu item before its submenu opens,
24
+ * and how long it may stray onto another row of the same menu before an open
25
+ * submenu closes. The second is the longer, so a diagonal slide from an item
26
+ * into its submenu can cross the rows between. Not gated on
27
+ * `prefers-reduced-motion`: they are the interaction, not decoration, the
28
+ * reasoning {@link MENU_SCROLL_INTERVAL_MS} gives. Provisional, tuned by hand.
29
+ */
30
+ export declare const SUBMENU_OPEN_MS = 150;
31
+ export declare const SUBMENU_CLOSE_MS = 300;
22
32
  /**
23
33
  * How long (ms) a press on a selected icon's name waits before it opens the
24
34
  * rename box — long enough that a double-click has already declared itself.
@@ -5,13 +5,21 @@
5
5
  * the chunks and any decoder skips them.
6
6
  *
7
7
  * This is the reading half: the reader, checking a version against a range,
8
- * putting versions in order, and the scale every box is drawn at. It is
9
- * pure and runs the same under Node and in a page, through the platform's
10
- * compression streams. Writing an app file, and the box, are
11
- * `vintage-frames/build`'s.
8
+ * putting versions in order, the app API levels and whether this kit runs an
9
+ * application, and the scale every box is drawn at. It is pure and runs the
10
+ * same under Node and in a page, through the platform's compression streams.
11
+ * Writing an app file, and the box, are `vintage-frames/build`'s.
12
12
  */
13
13
  /** The app file format this kit reads and writes. */
14
14
  export declare const APP_FILE_FORMAT = 1;
15
+ /**
16
+ * The app API this kit provides: the level of what a built application can
17
+ * use of it. It goes up when something in it changes or goes away, and never
18
+ * for an addition. app-api.json lists what it covers.
19
+ */
20
+ export declare const APP_API = 2;
21
+ /** The oldest app API this kit still runs. */
22
+ export declare const APP_API_OLDEST = 2;
15
23
  /**
16
24
  * One system px of an app file's box, in its picture's px: every box of
17
25
  * this format is drawn at 3×, each system px a 3 × 3 block.
@@ -29,6 +37,8 @@ export interface AppManifest {
29
37
  version: string;
30
38
  /** The kit range the code was built and checked against: `^0.17.0`. */
31
39
  requires: string;
40
+ /** The app API the code was built against, {@link APP_API}. A file from before levels has none. */
41
+ api?: number;
32
42
  author: string;
33
43
  /** What the application is, in a sentence or two of plain text. */
34
44
  description?: string;
@@ -97,3 +107,9 @@ export declare function compareVersions(a: string, b: string): number;
97
107
  * false.
98
108
  */
99
109
  export declare function satisfies(version: string, range: string): boolean;
110
+ /**
111
+ * Whether this kit runs the application a manifest describes: one built at
112
+ * an app API from {@link APP_API_OLDEST} to {@link APP_API}, or, for a file
113
+ * from before levels, one whose `requires` this kit's version meets.
114
+ */
115
+ export declare function appRuns(manifest: Pick<AppManifest, 'requires' | 'api'>): boolean;
@@ -1,28 +1,30 @@
1
+ import { VERSION as e } from "./version.js";
1
2
  //#region src/shell/app-file.ts
2
- var e = 1, t = 3, n = "vintage-frames.app", r = "vintage-frames.code", i = [
3
+ var t = 1, n = 2, r = 2, i = 3, a = "vintage-frames.app", o = "vintage-frames.code", s = [
3
4
  "id",
4
5
  "name",
5
6
  "version",
6
7
  "requires",
8
+ "api",
7
9
  "author",
8
10
  "description",
9
11
  "icon"
10
- ], a = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10), o = Uint32Array.from({ length: 256 }, (e, t) => {
12
+ ], c = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10), l = Uint32Array.from({ length: 256 }, (e, t) => {
11
13
  let n = t;
12
14
  for (let e = 0; e < 8; e++) n = n & 1 ? 3988292384 ^ n >>> 1 : n >>> 1;
13
15
  return n >>> 0;
14
16
  });
15
- function s(e) {
17
+ function u(e) {
16
18
  let t = 4294967295;
17
- for (let n of e) t = o[(t ^ n) & 255] ^ t >>> 8;
19
+ for (let n of e) t = l[(t ^ n) & 255] ^ t >>> 8;
18
20
  return (t ^ 4294967295) >>> 0;
19
21
  }
20
- function c(e) {
21
- if (e.length < 8 || a.some((t, n) => e[n] !== t)) return null;
22
+ function d(e) {
23
+ if (e.length < 8 || c.some((t, n) => e[n] !== t)) return null;
22
24
  let t = new DataView(e.buffer, e.byteOffset, e.byteLength), n = [], r = 8;
23
25
  for (; r + 12 <= e.length;) {
24
26
  let i = r + 12 + t.getUint32(r);
25
- if (i > e.length || s(e.subarray(r + 4, i - 4)) !== t.getUint32(i - 4)) return null;
27
+ if (i > e.length || u(e.subarray(r + 4, i - 4)) !== t.getUint32(i - 4)) return null;
26
28
  let a = String.fromCharCode(...e.subarray(r + 4, r + 8));
27
29
  if (n.push({
28
30
  type: a,
@@ -34,85 +36,91 @@ function c(e) {
34
36
  }
35
37
  return null;
36
38
  }
37
- function l(e) {
39
+ function f(e) {
38
40
  let t = e.indexOf(0);
39
41
  return t > 0 ? String.fromCharCode(...e.subarray(0, t)) : "";
40
42
  }
41
- var u = new TextDecoder("utf-8", { fatal: !0 });
42
- async function d(e) {
43
+ var p = new TextDecoder("utf-8", { fatal: !0 });
44
+ async function m(e) {
43
45
  let t = e.indexOf(0), n = e[t + 1], r = e[t + 2], i = e.indexOf(0, t + 3), a = i < 0 ? -1 : e.indexOf(0, i + 1);
44
46
  if (t < 1 || a < 0 || n > 1 || r !== 0) throw Error("a broken iTXt chunk");
45
47
  let o = e.subarray(a + 1);
46
- return u.decode(n ? await f(o) : o);
48
+ return p.decode(n ? await h(o) : o);
47
49
  }
48
- async function f(e) {
50
+ async function h(e) {
49
51
  let t = new Blob([e.slice()]).stream().pipeThrough(new DecompressionStream("deflate"));
50
52
  return new Uint8Array(await new Response(t).arrayBuffer());
51
53
  }
52
- async function p(e) {
54
+ async function g(e) {
53
55
  let t = new Blob([e.slice()]).stream().pipeThrough(new CompressionStream("deflate"));
54
56
  return new Uint8Array(await new Response(t).arrayBuffer());
55
57
  }
56
- function m(e) {
58
+ function _(e) {
57
59
  if (!e || typeof e != "object") return "its manifest is not an object";
58
60
  let t = e;
59
61
  if (t.format !== 1) return typeof t.format == "number" ? `it is format ${t.format}, and this kit reads format 1` : "its manifest has no format";
60
62
  let n = { format: 1 };
61
- for (let e of i) {
63
+ for (let e of s) {
62
64
  let r = t[e];
65
+ if (e === "api") {
66
+ if (r === void 0) continue;
67
+ if (!Number.isInteger(r) || r < 1) return `its api, ${JSON.stringify(r)}, is not a level`;
68
+ n.api = r;
69
+ continue;
70
+ }
63
71
  if (e === "description") {
64
72
  if (r !== void 0 && typeof r != "string") return "its description is not text";
65
73
  typeof r == "string" && r.trim() && (n.description = r);
66
74
  continue;
67
75
  }
68
76
  if (typeof r != "string" || !r) return `its manifest has no ${e}`;
69
- if (e === "version" && !y(r)) return `its version, ${r}, is not a version`;
70
- if (e === "requires" && !b(r)) return `its requires, ${r}, is not a range`;
77
+ if (e === "version" && !S(r)) return `its version, ${r}, is not a version`;
78
+ if (e === "requires" && !C(r)) return `its requires, ${r}, is not a range`;
71
79
  n[e] = r;
72
80
  }
73
81
  return n;
74
82
  }
75
- async function h(e) {
76
- let t = c(e);
83
+ async function v(e) {
84
+ let t = d(e);
77
85
  if (!t) return "it is not a whole PNG";
78
- let i = /* @__PURE__ */ new Map();
79
- for (let { type: e, data: n } of t) {
86
+ let n = /* @__PURE__ */ new Map();
87
+ for (let { type: e, data: r } of t) {
80
88
  if (e !== "iTXt") continue;
81
- let t = l(n);
89
+ let t = f(r);
82
90
  if (t === "vintage-frames.app" || t === "vintage-frames.code") {
83
- if (i.has(t)) return `it has two ${t} chunks`;
84
- i.set(t, n);
91
+ if (n.has(t)) return `it has two ${t} chunks`;
92
+ n.set(t, r);
85
93
  }
86
94
  }
87
- let a = i.get(n), o = i.get(r);
88
- if (!a && !o) return "it carries no application";
89
- if (!a) return "its manifest is missing";
90
- if (!o) return "its code is missing";
95
+ let r = n.get(a), i = n.get(o);
96
+ if (!r && !i) return "it carries no application";
97
+ if (!r) return "its manifest is missing";
98
+ if (!i) return "its code is missing";
91
99
  let s;
92
100
  try {
93
- s = JSON.parse(await d(a));
101
+ s = JSON.parse(await m(r));
94
102
  } catch {
95
103
  return "its manifest does not read";
96
104
  }
97
- let u = m(s);
98
- if (typeof u == "string") return u;
105
+ let c = _(s);
106
+ if (typeof c == "string") return c;
99
107
  try {
100
108
  return {
101
- manifest: u,
102
- code: await d(o)
109
+ manifest: c,
110
+ code: await m(i)
103
111
  };
104
112
  } catch {
105
113
  return "its code does not inflate";
106
114
  }
107
115
  }
108
- async function g(e) {
109
- let t = await h(e);
116
+ async function y(e) {
117
+ let t = await v(e);
110
118
  return typeof t == "string" ? null : t;
111
119
  }
112
- var _ = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
113
- function v(e) {
120
+ var b = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
121
+ function x(e) {
114
122
  if (typeof e != "string") return null;
115
- let t = _.exec(e.trim().replace(/^v/, ""));
123
+ let t = b.exec(e.trim().replace(/^v/, ""));
116
124
  return t ? {
117
125
  core: [
118
126
  Number(t[1]),
@@ -122,18 +130,18 @@ function v(e) {
122
130
  pre: t[4] ? t[4].split(".") : []
123
131
  } : null;
124
132
  }
125
- function y(e) {
126
- return _.test(e);
133
+ function S(e) {
134
+ return b.test(e);
127
135
  }
128
- function b(e) {
129
- return y(e.startsWith("^") ? e.slice(1) : e);
136
+ function C(e) {
137
+ return S(e.startsWith("^") ? e.slice(1) : e);
130
138
  }
131
- function x(e) {
139
+ function w(e) {
132
140
  if (typeof e != "string") return null;
133
141
  let t = e.trim();
134
- return v(t.startsWith("^") ? t.slice(1) : t);
142
+ return x(t.startsWith("^") ? t.slice(1) : t);
135
143
  }
136
- function S(e, t) {
144
+ function T(e, t) {
137
145
  for (let n = 0; n < 3; n++) if (e.core[n] !== t.core[n]) return e.core[n] - t.core[n];
138
146
  if (!e.pre.length || !t.pre.length) return t.pre.length - e.pre.length;
139
147
  for (let n = 0; n < Math.max(e.pre.length, t.pre.length); n++) {
@@ -146,14 +154,14 @@ function S(e, t) {
146
154
  }
147
155
  return 0;
148
156
  }
149
- function C(e, t) {
150
- let n = v(e), r = v(t);
151
- return n && r ? S(n, r) : !!n - +!!r;
157
+ function E(e, t) {
158
+ let n = x(e), r = x(t);
159
+ return n && r ? T(n, r) : !!n - +!!r;
152
160
  }
153
- function w(e, t) {
154
- let n = v(e), r = x(t);
161
+ function D(e, t) {
162
+ let n = x(e), r = w(t);
155
163
  if (!n || !r) return !1;
156
- if (!t.trim().startsWith("^")) return S(n, r) === 0;
164
+ if (!t.trim().startsWith("^")) return T(n, r) === 0;
157
165
  if (n.pre.length && !(r.pre.length && n.core.every((e, t) => e === r.core[t]))) return !1;
158
166
  let [i, a, o] = r.core, s = {
159
167
  core: i ? [
@@ -171,7 +179,11 @@ function w(e, t) {
171
179
  ],
172
180
  pre: []
173
181
  };
174
- return S(n, r) >= 0 && S(n, s) < 0;
182
+ return T(n, r) >= 0 && T(n, s) < 0;
183
+ }
184
+ function O(t) {
185
+ let { api: n } = t;
186
+ return n === void 0 ? D(e, t.requires) : Number.isInteger(n) && n >= 2 && n <= 2;
175
187
  }
176
188
  //#endregion
177
- export { e as APP_FILE_FORMAT, t as BOX_SCALE, r as CODE_KEYWORD, n as MANIFEST_KEYWORD, a as PNG_SIGNATURE, C as compareVersions, s as crc32, p as deflate, f as inflate, h as inspectAppFile, y as isVersion, l as itxtKeyword, m as manifestOf, c as pngChunks, g as readAppFile, w as satisfies };
189
+ export { n as APP_API, r as APP_API_OLDEST, t as APP_FILE_FORMAT, i as BOX_SCALE, o as CODE_KEYWORD, a as MANIFEST_KEYWORD, c as PNG_SIGNATURE, O as appRuns, E as compareVersions, u as crc32, g as deflate, h as inflate, v as inspectAppFile, S as isVersion, f as itxtKeyword, _ as manifestOf, d as pngChunks, y as readAppFile, D as satisfies };
@@ -13,6 +13,14 @@
13
13
  * never renamed, moved, copied or removed; nothing is made in the Trash.
14
14
  * Deleting is a move into the Trash, and only Empty Trash removes. A
15
15
  * volume's position is stored on a record of its own id.
16
+ * - The System Folder is kept the same way when the site names it: a folder
17
+ * on the startup disk (the desktop without one) holding Apple Menu Items
18
+ * and Startup Items, listed after the volumes. What they hold is anyone's,
19
+ * and the shell reads it: the Apple menu, and what opens at startup.
20
+ * - An alias stands for another item: its data names the original's id and
21
+ * kind, and `resolve` finds the original wherever it moved, until it is
22
+ * removed. An alias of a container takes a drop for its original, and
23
+ * keeps taking one, to say so, once the original is gone.
16
24
  * - A folder never goes into itself or a folder inside it. A parent with no
17
25
  * record reads as the desktop.
18
26
  * - An item whose kind no application registers is left out of the listing
@@ -30,17 +38,25 @@ import type { Point } from './geometry.js';
30
38
  export declare const TRASH = "trash";
31
39
  /** The startup disk's id. */
32
40
  export declare const DISK = "disk";
41
+ /** The System Folder's id: kept on the startup disk when the site names it. */
42
+ export declare const SYSTEM_FOLDER = "system-folder";
43
+ /** Apple Menu Items' id, in the System Folder: what it holds is the Apple menu. */
44
+ export declare const APPLE_MENU_ITEMS = "apple-menu-items";
45
+ /** Startup Items' id, in the System Folder: what it holds opens at startup. */
46
+ export declare const STARTUP_ITEMS = "startup-items";
33
47
  /** A folder's kind. */
34
48
  export declare const FOLDER = "folder";
35
49
  /** An application's icon: the kind the shell registers, whose data names the application it opens. */
36
50
  export declare const APP_KIND = "app";
51
+ /** An alias: the kind the shell registers, whose data names the item it stands for. */
52
+ export declare const ALIAS_KIND = "alias";
37
53
  /** The name a new folder takes, counted up where it is taken. */
38
54
  export declare const UNTITLED_FOLDER = "untitled folder";
39
55
  /** One item of the catalog. */
40
56
  export interface Item {
41
57
  id: string;
42
58
  name: string;
43
- /** `folder`, a volume's (`trash`, `disk`), the shell's `app`, or a kind an application registers. */
59
+ /** `folder`, a volume's (`trash`, `disk`), the shell's `app` or `alias`, or a kind an application registers. */
44
60
  kind: string;
45
61
  /** A folder's id, a volume's, or null for the desktop. */
46
62
  parent: string | null;
@@ -78,9 +94,17 @@ export interface Volumes {
78
94
  trash?: string | false;
79
95
  disk?: string | false;
80
96
  }
97
+ /** The System Folder and its two folders, by name. A site that names them has them. */
98
+ export interface SystemFolders {
99
+ folder: string;
100
+ appleMenu: string;
101
+ startup: string;
102
+ }
81
103
  export interface CatalogOptions {
82
104
  storage: CatalogStorage | null;
83
105
  volumes?: Volumes;
106
+ /** The System Folder, on the startup disk. Unset, none. */
107
+ system?: SystemFolders;
84
108
  /** Whether an application registers `kind`. Folders are always listed. Default: every kind. */
85
109
  listed?(kind: string): boolean;
86
110
  /** A kind's payload hooks. */
@@ -109,10 +133,22 @@ export interface CatalogArchive {
109
133
  export declare const isContainerKind: (kind: string) => boolean;
110
134
  /** Whether `id` names a volume. */
111
135
  export declare const isVolume: (id: string | null | undefined) => boolean;
136
+ /**
137
+ * Whether `id` names a container the catalog keeps, never renamed, moved,
138
+ * copied or removed: a volume, the System Folder or a folder in it.
139
+ */
140
+ export declare const isKept: (id: string | null | undefined) => boolean;
112
141
  /** The application an `app` item stands for: its `data.app`, or null. */
113
142
  export declare function appIdOf(item: Item): string | null;
143
+ /** The id of the item an alias stands for: its `data.original`, or null for anything but an alias. */
144
+ export declare function originalOf(item: Item): string | null;
114
145
  /** The item with `id`, or null. */
115
146
  export declare function itemOf(state: CatalogState, id: string | null | undefined): Item | null;
147
+ /**
148
+ * The item `id` names, or the original an alias stands for, through an alias
149
+ * of an alias too. Null once the original is gone, and for a chain that loops.
150
+ */
151
+ export declare function resolve(state: CatalogState, id: string | null | undefined): Item | null;
116
152
  /** The container `parent` resolves to: that container while it is listed, else the desktop (null). */
117
153
  export declare function containerOf(state: CatalogState, parent: string | null | undefined): string | null;
118
154
  /** The items directly in a container (null: the desktop), in listing order. */
@@ -132,6 +168,26 @@ export declare function isInside(state: CatalogState, id: string, ancestor: stri
132
168
  export declare function enclosingFolders(state: CatalogState, id: string | null | undefined): string[];
133
169
  /** Whether `id` is the Trash or anything inside it. */
134
170
  export declare function isTrashed(state: CatalogState, id: string | null | undefined): boolean;
171
+ /**
172
+ * Whether Make Alias takes item `id`: an application, a document, a folder
173
+ * or the startup disk, or an alias of one, outside the Trash. Never the
174
+ * Trash itself, and never an alias whose original is gone.
175
+ */
176
+ export declare function canAlias(state: CatalogState, id: string | null | undefined): boolean;
177
+ /** What a drop onto an item files into: a container's id, or the alias of a container whose original is gone. */
178
+ export type DropTarget = {
179
+ folder: string;
180
+ } | {
181
+ missing: Item;
182
+ };
183
+ /**
184
+ * What a drop onto item `id` files into. A container takes it itself, and
185
+ * an alias of one passes it to the original, so filing judges the original:
186
+ * a folder never goes into itself through an alias either. An alias of a
187
+ * container whose original is gone still takes the drop and files nothing:
188
+ * `missing`, the alias. Anything else takes none: null.
189
+ */
190
+ export declare function dropTargetOf(state: CatalogState, id: string | null | undefined): DropTarget | null;
135
191
  /** Everything under a container, each container before what it holds; a loop is walked once. */
136
192
  export declare function descendantsOf(state: CatalogState, parent: string | null): Item[];
137
193
  /** The first free folder name in a container: "untitled folder", "untitled folder 2", … */
@@ -142,6 +198,28 @@ export declare function nextFolderName(state: CatalogState, parent: string | nul
142
198
  * then " copy 2", " copy 3", …
143
199
  */
144
200
  export declare function copyName(state: CatalogState, parent: string | null, name: string, kind: string): string;
201
+ /** The name for an alias of `name` in a container: "name alias", then "name alias 2", … while an alias there has it. */
202
+ export declare function aliasName(state: CatalogState, parent: string | null, name: string): string;
203
+ /** What Apple Menu Items holds, by name: the Apple menu's entries. None without the System Folder. */
204
+ export declare const appleMenuItemsOf: (state: CatalogState) => Item[];
205
+ /** What Startup Items holds, by name: what opens at startup. None without the System Folder. */
206
+ export declare const startupItemsOf: (state: CatalogState) => Item[];
207
+ /** One entry of the Apple menu, and what it holds when it is a folder's. */
208
+ export interface AppleMenuEntry {
209
+ /** The entry's own item: an alias stays the alias. */
210
+ item: Item;
211
+ /** What the folder holds, or the folder an alias stands for, by name. */
212
+ entries?: AppleMenuEntry[];
213
+ }
214
+ /**
215
+ * The Apple menu as a tree: what Apple Menu Items holds, by name, and for a
216
+ * folder in it, or an alias of one, what that folder holds, the same way,
217
+ * down to five levels below the menu. A folder already on the way down
218
+ * (Apple Menu Items starts it), an empty folder and a folder past the fifth
219
+ * level have no entries, so the tree never loops. None without the System
220
+ * Folder.
221
+ */
222
+ export declare function appleMenuOf(state: CatalogState): AppleMenuEntry[];
145
223
  /** The catalog's operations, over its listing. */
146
224
  export interface Catalog {
147
225
  get(): CatalogState;