vintage-frames 0.17.0 → 0.19.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 (39) hide show
  1. package/README.md +1 -1
  2. package/custom-elements.json +96 -7
  3. package/dist/build/app-file.d.ts +2 -2
  4. package/dist/build/app-file.js +1 -0
  5. package/dist/build/box.d.ts +3 -1
  6. package/dist/build/box.js +6 -3
  7. package/dist/build/frame.js +19 -17
  8. package/dist/build/plugins.d.ts +5 -4
  9. package/dist/build/plugins.js +69 -64
  10. package/dist/components/vf-icon.d.ts +7 -0
  11. package/dist/components/vf-icon.js +101 -81
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +15 -14
  14. package/dist/modal-dialog.d.ts +7 -6
  15. package/dist/modal-dialog.js +30 -22
  16. package/dist/shell/app-file.d.ts +43 -5
  17. package/dist/shell/app-file.js +90 -62
  18. package/dist/shell/catalog.d.ts +41 -1
  19. package/dist/shell/catalog.js +174 -153
  20. package/dist/shell/finder.d.ts +27 -9
  21. package/dist/shell/finder.js +335 -277
  22. package/dist/shell/geometry.d.ts +7 -0
  23. package/dist/shell/geometry.js +19 -3
  24. package/dist/shell/index.d.ts +4 -4
  25. package/dist/shell/index.js +12 -12
  26. package/dist/shell/pure.d.ts +1 -1
  27. package/dist/shell/pure.js +6 -6
  28. package/dist/shell/shell.d.ts +14 -10
  29. package/dist/shell/shell.js +144 -117
  30. package/dist/shell/version.d.ts +1 -1
  31. package/dist/shell/version.js +1 -1
  32. package/dist/styles/body-italic-font.d.ts +2 -0
  33. package/dist/styles/body-italic-font.js +10 -0
  34. package/dist/styles/register-embedded-font.d.ts +5 -4
  35. package/dist/styles/register-embedded-font.js +11 -10
  36. package/docs/SPEC.md +2 -2
  37. package/editor/vscode.html-custom-data.json +5 -0
  38. package/editor/web-types.json +11 -1
  39. package/package.json +7 -3
package/README.md CHANGED
@@ -25,7 +25,7 @@ There is no need to import a stylesheet. The components carry their styles with
25
25
 
26
26
  ## The shell
27
27
 
28
- `vintage-frames/shell` is an experimental second entry point that runs applications over one desktop: a window manager, a menu bar that shows the front application's menus, and a Finder with folders, filing, Clean Up and the Trash.
28
+ `vintage-frames/shell` is an experimental second entry point that runs applications over one desktop: a window manager, a menu bar that shows the front application's menus, and a Finder with folders, aliases, filing, Clean Up and the Trash. A System Folder's contents set the Apple menu and what opens at startup.
29
29
 
30
30
  ```ts
31
31
  import 'vintage-frames'
@@ -3908,6 +3908,20 @@
3908
3908
  "module": "src/modal-dialog.ts"
3909
3909
  }
3910
3910
  },
3911
+ {
3912
+ "kind": "field",
3913
+ "name": "#opened",
3914
+ "privacy": "private",
3915
+ "type": {
3916
+ "text": "boolean"
3917
+ },
3918
+ "default": "false",
3919
+ "description": "Whether the modal has opened since its last close was funneled: true from\nan open until `#funnelClose` runs for the close that ends it, which can be\nafter the native `<dialog>` has closed, while its `close` event is queued.",
3920
+ "inheritedFrom": {
3921
+ "name": "VfModalDialog",
3922
+ "module": "src/modal-dialog.ts"
3923
+ }
3924
+ },
3911
3925
  {
3912
3926
  "kind": "method",
3913
3927
  "name": "show",
@@ -4095,7 +4109,7 @@
4095
4109
  "text": "void"
4096
4110
  }
4097
4111
  },
4098
- "description": "Reconcile the native `<dialog>` with `open`. Opening states the origin —\nthe author's, or the centered one — onto the top-layer box; the UA's own\n`margin: auto` centering lands on a half pixel whenever viewport minus\ndialog is odd, which fringes all the 1-bit chrome inside. Closing just\ncalls `dialog.close()`, routing teardown through the native `close` event\nso _onNativeClose is the one place placement is cleared and\n`vf-close` is fired.",
4112
+ "description": "Reconcile the native `<dialog>` with `open`. Opening states the origin —\nthe author's, or the centered one — onto the top-layer box; the UA's own\n`margin: auto` centering lands on a half pixel whenever viewport minus\ndialog is odd, which fringes all the 1-bit chrome inside. Closing just\ncalls `dialog.close()`, and the native `close` event runs the close funnel,\n`#funnelClose`, the one place placement is cleared and `vf-close` is\nfired.",
4099
4113
  "inheritedFrom": {
4100
4114
  "name": "VfModalDialog",
4101
4115
  "module": "src/modal-dialog.ts"
@@ -4152,7 +4166,7 @@
4152
4166
  }
4153
4167
  }
4154
4168
  ],
4155
- "description": "While open, watch everything that invalidates the placement: the dialog's\nown box changing (content upgrade/growth, and `--vf-scale` moving under a\nzoom or density change, since every metric resizes with it), the viewport\nresizing, and the scale itself — which the box alone would miss on a page\nthat pins `--vf-scale`, where a zoom moves the lattice without resizing\nanything. Undone in _onNativeClose / `disconnectedCallback`.",
4169
+ "description": "While open, watch everything that invalidates the placement: the dialog's\nown box changing (content upgrade/growth, and `--vf-scale` moving under a\nzoom or density change, since every metric resizes with it), the viewport\nresizing, and the scale itself — which the box alone would miss on a page\nthat pins `--vf-scale`, where a zoom moves the lattice without resizing\nanything. Undone by the close funnel and `disconnectedCallback`.",
4156
4170
  "inheritedFrom": {
4157
4171
  "name": "VfModalDialog",
4158
4172
  "module": "src/modal-dialog.ts"
@@ -4196,7 +4210,22 @@
4196
4210
  "text": "void"
4197
4211
  }
4198
4212
  },
4199
- "description": "Native `close` — the single teardown funnel for every close path. Drops\nthe written origin so the next open re-derives it from the box it will\nactually have (a stated `top`/`left` is the author's or the user's and\nsurvives, and is re-applied by that open), syncs `open`, and fires\n`vf-close` with the reason and the value the close carried.",
4213
+ "description": "Native `close`, which every close path ends in: syncs `open` and runs the\nclose funnel. An open that came before the event has run the funnel for\nthis close already, and the event passes.",
4214
+ "inheritedFrom": {
4215
+ "name": "VfModalDialog",
4216
+ "module": "src/modal-dialog.ts"
4217
+ }
4218
+ },
4219
+ {
4220
+ "kind": "method",
4221
+ "name": "#funnelClose",
4222
+ "privacy": "private",
4223
+ "return": {
4224
+ "type": {
4225
+ "text": "void"
4226
+ }
4227
+ },
4228
+ "description": "The close funnel, the single teardown for every close path. Drops the\nwritten origin so the next open re-derives it from the box it will\nactually have (a stated `top`/`left` is the author's or the user's and\nsurvives, and is re-applied by that open), and fires `vf-close` with the\nreason and the value the close carried.",
4200
4229
  "inheritedFrom": {
4201
4230
  "name": "VfModalDialog",
4202
4231
  "module": "src/modal-dialog.ts"
@@ -5652,6 +5681,17 @@
5652
5681
  "attribute": "open",
5653
5682
  "reflects": true
5654
5683
  },
5684
+ {
5685
+ "kind": "field",
5686
+ "name": "alias",
5687
+ "type": {
5688
+ "text": "boolean"
5689
+ },
5690
+ "default": "false",
5691
+ "description": "The icon is an alias, an item standing for another: its name is set in\nitalics, the body face's own, and the plate widens to the slant's last\npixel.",
5692
+ "attribute": "alias",
5693
+ "reflects": true
5694
+ },
5655
5695
  {
5656
5696
  "kind": "field",
5657
5697
  "name": "movable",
@@ -6243,6 +6283,25 @@
6243
6283
  },
6244
6284
  "description": "Size the plate to a whole, EVEN number of system pixels.\n\nThis is the whole alignment argument, so it is worth stating plainly. The\nframe centers two things over one axis — the art cell and the name — and a\ncentered child's offset is `(box − child) / 2`. That is a whole number\nexactly when the box and the child have the SAME PARITY. The cell is 32 or\n16 and a declared `width` is required even, so if the plate is even too,\nevery offset in the component is whole by construction: no correction, no\nsnapping, nothing for the compositor to round.\n\nThe text is measured rather than the plate, with a Range over its own\ncontents — the plate already carries the width this computed last time, so\nreading the element back would just return it. The measurement is in\nsystem px and so is scale-invariant: the same name is the same number of\nart pixels wide at every display density, which is why nothing here\nre-runs when the scale changes.\n\nRounding UP to even (never down) means the plate never clips the run it\nwas measured from; the leftover is at most one system pixel of extra plate\nafter the text, since the embedded faces advance in whole pixels and only\na fallback face measures fractionally."
6245
6285
  },
6286
+ {
6287
+ "kind": "method",
6288
+ "name": "#lean",
6289
+ "privacy": "private",
6290
+ "return": {
6291
+ "type": {
6292
+ "text": "number"
6293
+ }
6294
+ },
6295
+ "parameters": [
6296
+ {
6297
+ "name": "plate",
6298
+ "type": {
6299
+ "text": "HTMLElement"
6300
+ }
6301
+ }
6302
+ ],
6303
+ "description": "How far an alias's italic name runs past its advance, in whole system px:\nthe slant leans the tops of the last letters right, by up to 5. Read off\nthe glyphs' ink, in a canvas set in the plate's own font."
6304
+ },
6246
6305
  {
6247
6306
  "kind": "field",
6248
6307
  "name": "#plateWidthCss",
@@ -6740,6 +6799,15 @@
6740
6799
  "description": "The icon's window is on screen, so the art paints as the Finder's open\nghost — outline held, interior re-filled with the kit's loose dither —\nderived in the client from the slotted art itself (see the class doc).\nSet it when handling `vf-open`, clear it when the window goes away.\nSelection inverts the ghost exactly as it inverts the art.",
6741
6800
  "fieldName": "open"
6742
6801
  },
6802
+ {
6803
+ "name": "alias",
6804
+ "type": {
6805
+ "text": "boolean"
6806
+ },
6807
+ "default": "false",
6808
+ "description": "The icon is an alias, an item standing for another: its name is set in\nitalics, the body face's own, and the plate widens to the slant's last\npixel.",
6809
+ "fieldName": "alias"
6810
+ },
6743
6811
  {
6744
6812
  "name": "movable",
6745
6813
  "type": {
@@ -24316,7 +24384,7 @@
24316
24384
  },
24317
24385
  {
24318
24386
  "kind": "class",
24319
- "description": "Base class for the modal shell `vf-dialog` — and for a consumer's own\nmodal (an alert box, say) authored against the kit.\n\nOwns the native `<dialog>` lifecycle every modal shares: `open` sync, `show()` /\n`close()`, the top/left placement (stated or centered) and the\nsingle `close` funnel that drops the written origin and fires `vf-close` with\nthe reason. Because every close path — Escape, `close()`, the opt-in\nlightDismiss click outside — routes through the native `close`\nevent, an Escape-close no longer leaves a stale origin behind, so the next\nopen re-derives it.\n\nIt also owns the two keyboard rules every classic dialog followed (see\ndefaultButton and initialFocusTarget): Return or Enter\nactivates the default button from anywhere in the dialog, and the box opens\nwith the insertion point in its first text field — or, with none, focus on\nthe default button. A field marked `autoselect` takes the focus with its\ntext selected, on every open.\n\nRemoving an open modal from the DOM is a close path too. HTML's dialog\n*removing steps* take the element out of the top layer **without** running\nthe close algorithm — no `close` event, no focus restoration — which is\nexactly what the standard framework pattern of unmounting a dialog instead\nof calling `close()` does. `disconnectedCallback` routes that path through\nthe same funnel: the removed element still fires `vf-close` (heard by\nlisteners on the element itself — it has left the tree, so nothing\nbubbles), `open` and the pinned margins reconcile so a re-append mounts it\nclosed and re-centered, and focus returns to the element that was focused\nwhen the modal opened.\n\nA `<form method=\"dialog\">` inside the modal closes it with the submitting\nbutton's `value` as returnValue, as a form inside a native\n`<dialog>` does. The platform's own rule looks for a `<dialog>` around the\nform, and this one is in the shadow root, so the modal listens for the\nsubmit itself.\n\nSubclasses supply only the frame chrome: a `render()` returning\n`<dialog",
24387
+ "description": "Base class for the modal shell `vf-dialog` — and for a consumer's own\nmodal (an alert box, say) authored against the kit.\n\nOwns the native `<dialog>` lifecycle every modal shares: `open` sync, `show()` /\n`close()`, the top/left placement (stated or centered) and the\nsingle `close` funnel that drops the written origin and fires `vf-close` with\nthe reason. Because every close path — Escape, `close()`, the opt-in\nlightDismiss click outside — routes through the native `close`\nevent, an Escape-close no longer leaves a stale origin behind, so the next\nopen re-derives it. That event is queued: a modal opened again before it\ncomes (`close()` then `show()` in one turn) runs the funnel at the open,\ninside `show()`, so the close's `vf-close` still comes before the open's\n`vf-show`, and the late event changes nothing.\n\nIt also owns the two keyboard rules every classic dialog followed (see\ndefaultButton and initialFocusTarget): Return or Enter\nactivates the default button from anywhere in the dialog, and the box opens\nwith the insertion point in its first text field — or, with none, focus on\nthe default button. A field marked `autoselect` takes the focus with its\ntext selected, on every open.\n\nRemoving an open modal from the DOM is a close path too. HTML's dialog\n*removing steps* take the element out of the top layer **without** running\nthe close algorithm — no `close` event, no focus restoration — which is\nexactly what the standard framework pattern of unmounting a dialog instead\nof calling `close()` does. `disconnectedCallback` routes that path through\nthe same funnel: the removed element still fires `vf-close` (heard by\nlisteners on the element itself — it has left the tree, so nothing\nbubbles), `open` and the pinned margins reconcile so a re-append mounts it\nclosed and re-centered, and focus returns to the element that was focused\nwhen the modal opened.\n\nA `<form method=\"dialog\">` inside the modal closes it with the submitting\nbutton's `value` as returnValue, as a form inside a native\n`<dialog>` does. The platform's own rule looks for a `<dialog>` around the\nform, and this one is in the shadow root, so the modal listens for the\nsubmit itself.\n\nSubclasses supply only the frame chrome: a `render()` returning\n`<dialog",
24320
24388
  "name": "VfModalDialog",
24321
24389
  "members": [
24322
24390
  {
@@ -24551,6 +24619,16 @@
24551
24619
  "default": "null",
24552
24620
  "description": "The element that was focused when the modal opened. The normal close path\nrestores focus natively, but the removal path (see the class doc) skips\nthe close algorithm, and by the time `disconnectedCallback` runs, focus\nhas already fallen to `<body>` — so we keep our own record."
24553
24621
  },
24622
+ {
24623
+ "kind": "field",
24624
+ "name": "#opened",
24625
+ "privacy": "private",
24626
+ "type": {
24627
+ "text": "boolean"
24628
+ },
24629
+ "default": "false",
24630
+ "description": "Whether the modal has opened since its last close was funneled: true from\nan open until `#funnelClose` runs for the close that ends it, which can be\nafter the native `<dialog>` has closed, while its `close` event is queued."
24631
+ },
24554
24632
  {
24555
24633
  "kind": "method",
24556
24634
  "name": "show",
@@ -24702,7 +24780,7 @@
24702
24780
  "text": "void"
24703
24781
  }
24704
24782
  },
24705
- "description": "Reconcile the native `<dialog>` with `open`. Opening states the origin —\nthe author's, or the centered one — onto the top-layer box; the UA's own\n`margin: auto` centering lands on a half pixel whenever viewport minus\ndialog is odd, which fringes all the 1-bit chrome inside. Closing just\ncalls `dialog.close()`, routing teardown through the native `close` event\nso _onNativeClose is the one place placement is cleared and\n`vf-close` is fired."
24783
+ "description": "Reconcile the native `<dialog>` with `open`. Opening states the origin —\nthe author's, or the centered one — onto the top-layer box; the UA's own\n`margin: auto` centering lands on a half pixel whenever viewport minus\ndialog is odd, which fringes all the 1-bit chrome inside. Closing just\ncalls `dialog.close()`, and the native `close` event runs the close funnel,\n`#funnelClose`, the one place placement is cleared and `vf-close` is\nfired."
24706
24784
  },
24707
24785
  {
24708
24786
  "kind": "field",
@@ -24743,7 +24821,7 @@
24743
24821
  }
24744
24822
  }
24745
24823
  ],
24746
- "description": "While open, watch everything that invalidates the placement: the dialog's\nown box changing (content upgrade/growth, and `--vf-scale` moving under a\nzoom or density change, since every metric resizes with it), the viewport\nresizing, and the scale itself — which the box alone would miss on a page\nthat pins `--vf-scale`, where a zoom moves the lattice without resizing\nanything. Undone in _onNativeClose / `disconnectedCallback`."
24824
+ "description": "While open, watch everything that invalidates the placement: the dialog's\nown box changing (content upgrade/growth, and `--vf-scale` moving under a\nzoom or density change, since every metric resizes with it), the viewport\nresizing, and the scale itself — which the box alone would miss on a page\nthat pins `--vf-scale`, where a zoom moves the lattice without resizing\nanything. Undone by the close funnel and `disconnectedCallback`."
24747
24825
  },
24748
24826
  {
24749
24827
  "kind": "method",
@@ -24775,7 +24853,18 @@
24775
24853
  "text": "void"
24776
24854
  }
24777
24855
  },
24778
- "description": "Native `close` — the single teardown funnel for every close path. Drops\nthe written origin so the next open re-derives it from the box it will\nactually have (a stated `top`/`left` is the author's or the user's and\nsurvives, and is re-applied by that open), syncs `open`, and fires\n`vf-close` with the reason and the value the close carried."
24856
+ "description": "Native `close`, which every close path ends in: syncs `open` and runs the\nclose funnel. An open that came before the event has run the funnel for\nthis close already, and the event passes."
24857
+ },
24858
+ {
24859
+ "kind": "method",
24860
+ "name": "#funnelClose",
24861
+ "privacy": "private",
24862
+ "return": {
24863
+ "type": {
24864
+ "text": "void"
24865
+ }
24866
+ },
24867
+ "description": "The close funnel, the single teardown for every close path. Drops the\nwritten origin so the next open re-derives it from the box it will\nactually have (a stated `top`/`left` is the author's or the user's and\nsurvives, and is re-applied by that open), and fires `vf-close` with the\nreason and the value the close carried."
24779
24868
  }
24780
24869
  ],
24781
24870
  "events": [
@@ -6,8 +6,8 @@
6
6
  import type { AppManifest } from '../shell/app-file.js';
7
7
  /** What {@link packApp} packs. */
8
8
  export interface PackAppInput {
9
- /** The manifest's fields but its format and icon, which the packer fills in. */
10
- manifest: Omit<AppManifest, 'format' | 'icon'>;
9
+ /** The manifest's fields but its format, app API and icon, which the packer fills in. */
10
+ manifest: Omit<AppManifest, 'format' | 'api' | 'icon'>;
11
11
  /** The icon's PNG, 32 × 32. */
12
12
  icon: Uint8Array;
13
13
  /** The application's code: one ES module whose default export is its factory. */
@@ -24,6 +24,7 @@ async function p({ manifest: e, icon: t, code: n, artwork: r }) {
24
24
  let i = {
25
25
  ...e,
26
26
  format: 1,
27
+ api: 2,
27
28
  icon: `data:image/png;base64,${m(t)}`
28
29
  };
29
30
  return f(await l({
@@ -19,6 +19,8 @@ export interface Slot {
19
19
  export interface BoxFrame {
20
20
  /** The picture, a PNG. */
21
21
  picture: Uint8Array;
22
+ /** One system px, in the picture's px. Each of the artwork's px is a whole number of them. */
23
+ scale: number;
22
24
  /** Where the artwork goes. Without artwork the icon goes there, `stamp` times its size and centered. */
23
25
  artwork: Slot & {
24
26
  stamp: number;
@@ -46,7 +48,7 @@ export interface BoxInput {
46
48
  manifest: AppManifest;
47
49
  /** The icon's PNG, 32 × 32. */
48
50
  icon: Uint8Array;
49
- /** The artwork's PNG: the artwork slot's size, or that divided by a whole number. */
51
+ /** The artwork's PNG: the artwork slot's size in system px, or that divided by a whole number. */
50
52
  artwork?: Uint8Array;
51
53
  }
52
54
  /** The box in the kit's frame, as a PNG. */
package/dist/build/box.js CHANGED
@@ -16,9 +16,12 @@ async function a(e, { manifest: n, icon: r, artwork: i }) {
16
16
  if (c.width !== 32 || c.height !== 32) throw Error(`the icon is ${c.width} × ${c.height}, and an icon is 32 × 32`);
17
17
  let u = e.artwork;
18
18
  if (i) {
19
- let e = await t(i, "the artwork"), n = u.width / e.width;
20
- if (!Number.isInteger(n) || e.height * n !== u.height) throw Error(`the artwork is ${e.width} × ${e.height}: draw it at ${u.width} × ${u.height}, or that divided by a whole number (${o(u)})`);
21
- s(a, e, u.left, u.top, n, u);
19
+ let n = await t(i, "the artwork"), r = {
20
+ width: u.width / e.scale,
21
+ height: u.height / e.scale
22
+ }, c = r.width / n.width;
23
+ if (!Number.isInteger(c) || n.height * c !== r.height) throw Error(`the artwork is ${n.width} × ${n.height}: draw it at ${r.width} × ${r.height}, or that divided by a whole number (${o(r)})`);
24
+ s(a, n, u.left, u.top, c * e.scale, u);
22
25
  } else {
23
26
  let e = 32 * u.stamp;
24
27
  s(a, c, u.left + Math.floor((u.width - e) / 2), u.top + Math.floor((u.height - e) / 2), u.stamp, u);
@@ -1,42 +1,44 @@
1
+ import "../shell/app-file.js";
1
2
  import { VF_BODY_STRIKE as e } from "../styles/body-strike.js";
2
3
  import { VF_DISPLAY_STRIKE as t } from "../styles/display-strike.js";
3
4
  import { FRAME_PNG as n } from "./frame-picture.js";
4
5
  //#region src/build/frame.ts
5
- var r = {
6
- left: 48,
7
- width: 384,
8
- scale: 3,
6
+ var r = 3, i = {
7
+ left: 16 * r,
8
+ width: 128 * r,
9
+ scale: r,
9
10
  ink: "#000000",
10
11
  align: "center"
11
- }, i = {
12
+ }, a = {
12
13
  picture: n,
14
+ scale: r,
13
15
  artwork: {
14
- left: 48,
15
- top: 48,
16
- width: 384,
17
- height: 384,
18
- stamp: 8
16
+ left: 16 * r,
17
+ top: 16 * r,
18
+ width: 128 * r,
19
+ height: 128 * r,
20
+ stamp: 3 * r
19
21
  },
20
22
  text: [
21
23
  {
22
- ...r,
23
- top: 456,
24
+ ...i,
25
+ top: 152 * r,
24
26
  strike: t,
25
27
  text: "{name}"
26
28
  },
27
29
  {
28
- ...r,
29
- top: 510,
30
+ ...i,
31
+ top: 170 * r,
30
32
  strike: e,
31
33
  text: "Version {version}"
32
34
  },
33
35
  {
34
- ...r,
35
- top: 546,
36
+ ...i,
37
+ top: 182 * r,
36
38
  strike: e,
37
39
  text: "by {author}"
38
40
  }
39
41
  ]
40
42
  };
41
43
  //#endregion
42
- export { i as FRAME };
44
+ export { a as FRAME };
@@ -4,18 +4,19 @@
4
4
  */
5
5
  import type { Plugin } from 'vite';
6
6
  export interface AppFileOptions {
7
- /** The application's id, name, version and author: its `app.ts`. */
7
+ /** The application's id, name, version and author, and its description if it has one: its `app.ts`. */
8
8
  app: {
9
9
  id: string;
10
10
  name: string;
11
11
  version: string;
12
12
  author: string;
13
+ description?: string;
13
14
  };
14
15
  /** The module whose default export is the application's factory. */
15
16
  entry: string;
16
17
  /** Its 32 × 32 icon, a PNG. */
17
18
  icon: string;
18
- /** Its artwork for the box: a PNG at the artwork slot's size, or that divided by a whole number. */
19
+ /** Its artwork for the box: a 128 × 128 PNG, or that divided by a whole number. */
19
20
  artwork?: string;
20
21
  /** A directory to write the app file to as well, such as a site's `apps/`. */
21
22
  copyTo?: string;
@@ -31,8 +32,8 @@ export interface AppFileOptions {
31
32
  export declare function appFile(options: AppFileOptions): Plugin;
32
33
  /**
33
34
  * Builds app files into a site. An import of `*.png?app` reads the file and
34
- * refuses one that isn't an app file, or whose `requires` this kit's
35
- * version doesn't meet. Its default export is the application's factory,
35
+ * refuses one that isn't an app file, or one this kit doesn't run
36
+ * (`appRuns`). Its default export is the application's factory,
36
37
  * its definition given the manifest's icon, and `manifest` is the manifest.
37
38
  * The code imports the kit by name, so the page keeps one copy of it. A new
38
39
  * file reloads the dev server's page.
@@ -1,40 +1,41 @@
1
- import { inspectAppFile as e, satisfies as t } from "../shell/app-file.js";
2
- import { VERSION as n } from "../shell/version.js";
3
- import { packApp as r } from "./app-file.js";
4
- import { mkdir as i, readFile as a, writeFile as o } from "node:fs/promises";
5
- import { join as s, relative as c, resolve as l } from "node:path";
1
+ import { VERSION as e } from "../shell/version.js";
2
+ import { appRuns as t, inspectAppFile as n, isVersion as r, satisfies as i } from "../shell/app-file.js";
3
+ import { packApp as a } from "./app-file.js";
4
+ import { mkdir as o, readFile as s, writeFile as c } from "node:fs/promises";
5
+ import { join as l, relative as u, resolve as d } from "node:path";
6
6
  //#region src/build/plugins.ts
7
- var u = [
7
+ var f = [
8
8
  "vintage-frames",
9
9
  "vintage-frames/shell",
10
10
  "vintage-frames/shell/pure"
11
- ], d = "app-code";
12
- function f(e) {
11
+ ], p = "app-code";
12
+ function m(e) {
13
13
  let t = e.match(/(?<=[\\/]node_modules[\\/])(?:@[^\\/]+[\\/])?[^\\/]+/g)?.at(-1)?.replace("\\", "/");
14
14
  return t === "vintage-frames" ? "a copy of vintage-frames" : t && /^(?:lit|lit-html|lit-element|@lit\/.+|@lit-labs\/.+)$/.test(t) ? `Lit (${t})` : null;
15
15
  }
16
- async function p(e) {
17
- let t = JSON.parse(await a(s(e, "package.json"), "utf8")), r = t.devDependencies?.["vintage-frames"] ?? t.dependencies?.["vintage-frames"];
18
- if (typeof r != "string") throw Error("package.json has no vintage-frames dev dependency");
19
- if (!/^\^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(r)) throw Error(`package.json's vintage-frames range is "${r}", and an app file requires a caret range: ^${n}`);
20
- return r;
16
+ async function h(t) {
17
+ let n = JSON.parse(await s(l(t, "package.json"), "utf8")), i = n.devDependencies?.["vintage-frames"] ?? n.dependencies?.["vintage-frames"];
18
+ if (typeof i != "string") throw Error("package.json has no vintage-frames dev dependency");
19
+ if (!i.startsWith("^") || !r(i.slice(1))) throw Error(`package.json's vintage-frames range is "${i}", and an app file requires a caret range: ^${e}`);
20
+ return i;
21
21
  }
22
- function m(e) {
23
- let { app: c, entry: d, icon: m, artwork: h, copyTo: g } = e;
22
+ function g(t) {
23
+ let { app: n, entry: u, icon: p, artwork: g, copyTo: _ } = t;
24
24
  for (let e of [
25
25
  "id",
26
26
  "name",
27
27
  "version",
28
28
  "author"
29
- ]) if (typeof c?.[e] != "string" || !c[e]) throw Error(`vintage-frames appFile(): app.${e} is missing`);
30
- if (!d || !m) throw Error(`vintage-frames appFile(): ${d ? "icon" : "entry"} is missing`);
31
- let _ = `${c.name.replace(/[\\/:*?"<>|]/g, "-")}.png`, v = "", y = null;
29
+ ]) if (typeof n?.[e] != "string" || !n[e]) throw Error(`vintage-frames appFile(): app.${e} is missing`);
30
+ if (!r(n.version)) throw Error(`vintage-frames appFile(): app.version, ${n.version}, is not a version`);
31
+ if (!u || !p) throw Error(`vintage-frames appFile(): ${u ? "icon" : "entry"} is missing`);
32
+ let v = `${n.name.replace(/[\\/:*?"<>|]/g, "-")}.png`, y = "", b = null;
32
33
  return {
33
34
  name: "vintage-frames:app-file",
34
35
  apply: "build",
35
36
  config: () => ({ build: {
36
37
  lib: {
37
- entry: d,
38
+ entry: u,
38
39
  formats: ["es"],
39
40
  fileName: "app"
40
41
  },
@@ -45,61 +46,65 @@ function m(e) {
45
46
  }
46
47
  } }),
47
48
  configResolved(e) {
48
- v = e.root;
49
+ y = e.root;
49
50
  },
50
51
  buildStart() {
51
52
  for (let e of [
52
- m,
53
- h,
53
+ p,
54
+ g,
54
55
  "package.json"
55
- ]) e && this.addWatchFile(l(v, e));
56
+ ]) e && this.addWatchFile(d(y, e));
56
57
  },
57
58
  generateBundle: {
58
59
  order: "post",
59
- async handler(e, i) {
60
- let o = Object.values(i), s = o.filter((e) => e.type === "chunk"), d = o.filter((e) => e.type === "asset").map((e) => e.fileName);
61
- d.length && this.error(`${c.name} emits ${d.join(", ")} beside its code, and an app file holds one module: no CSS import, no source map file, its art inlined.`);
62
- let [g] = s;
63
- (!g || s.length > 1) && this.error(`${c.name} builds to ${s.length} modules, and an app file holds one.`);
64
- for (let e of Object.keys(g.modules)) {
65
- let t = f(e);
66
- t && this.error(`${c.name} bundles ${t}. It composes the page's kit and brings none of its own.`);
60
+ async handler(t, r) {
61
+ let o = Object.values(r), c = o.filter((e) => e.type === "chunk"), l = o.filter((e) => e.type === "asset").map((e) => e.fileName);
62
+ l.length && this.error(`${n.name} emits ${l.join(", ")} beside its code, and an app file holds one module: no CSS import, no source map file, its art inlined.`);
63
+ let [u] = c;
64
+ (!u || c.length > 1) && this.error(`${n.name} builds to ${c.length} modules, and an app file holds one.`);
65
+ for (let e of Object.keys(u.modules)) {
66
+ let t = m(e);
67
+ t && this.error(`${n.name} bundles ${t}. It composes the page's kit and brings none of its own.`);
67
68
  }
68
- let b = [...g.imports, ...g.dynamicImports].filter((e) => !u.includes(e));
69
- b.length && this.error(`${c.name} imports ${b.join(", ")}. An application imports ${u.join(", ")} and its own files.`);
69
+ let _ = [...u.imports, ...u.dynamicImports].filter((e) => !f.includes(e));
70
+ _.length && this.error(`${n.name} imports ${_.join(", ")}. An application imports ${f.join(", ")} and its own files.`);
70
71
  try {
71
- let e = await p(v);
72
- if (!t("0.17.0", e)) throw Error(`package.json requires vintage-frames ${e}, and the build is on ${n}: move the range`);
73
- y = await r({
72
+ let t = await h(y);
73
+ if (!i("0.19.0", t)) throw Error(`package.json requires vintage-frames ${t}, and the build is on ${e}: move the range`);
74
+ b = await a({
74
75
  manifest: {
75
- id: c.id,
76
- name: c.name,
77
- version: c.version,
78
- requires: e,
79
- author: c.author
76
+ id: n.id,
77
+ name: n.name,
78
+ version: n.version,
79
+ requires: t,
80
+ author: n.author,
81
+ description: n.description
80
82
  },
81
- icon: new Uint8Array(await a(l(v, m))),
82
- code: g.code,
83
- artwork: h ? new Uint8Array(await a(l(v, h))) : void 0
83
+ icon: new Uint8Array(await s(d(y, p))),
84
+ code: u.code,
85
+ artwork: g ? new Uint8Array(await s(d(y, g))) : void 0
84
86
  });
85
87
  } catch (e) {
86
- this.error(`${c.name}: ${e.message}`);
88
+ this.error(`${n.name}: ${e.message}`);
87
89
  }
88
- delete i[g.fileName], this.emitFile({
90
+ delete r[u.fileName], this.emitFile({
89
91
  type: "asset",
90
- fileName: _,
91
- source: y
92
+ fileName: v,
93
+ source: b
92
94
  });
93
95
  }
94
96
  },
95
97
  async writeBundle() {
96
- if (!g || !y) return;
97
- let e = l(v, g);
98
- await i(e, { recursive: !0 }), await o(s(e, _), y);
98
+ if (!_ || !b) return;
99
+ let e = d(y, _);
100
+ await o(e, { recursive: !0 }), await c(l(e, v), b);
99
101
  }
100
102
  };
101
103
  }
102
- function h() {
104
+ function _(t) {
105
+ return t.api === void 0 ? `requires vintage-frames ${t.requires}, and this site has ${e}` : `is built for app API ${t.api}, and this site's kit runs app API 2`;
106
+ }
107
+ function v() {
103
108
  let r = "";
104
109
  return {
105
110
  name: "vintage-frames:app-files",
@@ -108,17 +113,17 @@ function h() {
108
113
  r = e.root;
109
114
  },
110
115
  async load(i) {
111
- let o = i.indexOf("?");
112
- if (o < 0) return null;
113
- let s = i.slice(0, o), l = new URLSearchParams(i.slice(o + 1));
114
- if (!s.endsWith(".png") || !(l.has("app") || l.has(d))) return null;
115
- this.addWatchFile(s);
116
- let u = c(r, s), f = await e(new Uint8Array(await a(s)));
117
- typeof f == "string" && this.error(`${u} is not an app file: ${f}.`);
118
- let { manifest: p, code: m } = f;
119
- return l.has(d) ? m : (t("0.17.0", p.requires) || this.error(`${u}: ${p.name} ${p.version} requires vintage-frames ${p.requires}, and this site has ${n}. Build ${p.name} on ${n} and copy its app file in.`), [
120
- `import factory from ${JSON.stringify(`${s}?${d}`)}`,
121
- `export const manifest = ${JSON.stringify(p)}`,
116
+ let a = i.indexOf("?");
117
+ if (a < 0) return null;
118
+ let o = i.slice(0, a), c = new URLSearchParams(i.slice(a + 1));
119
+ if (!o.endsWith(".png") || !(c.has("app") || c.has(p))) return null;
120
+ this.addWatchFile(o);
121
+ let l = u(r, o), d = await n(new Uint8Array(await s(o)));
122
+ typeof d == "string" && this.error(`${l} is not an app file: ${d}.`);
123
+ let { manifest: f, code: m } = d;
124
+ return c.has(p) ? m : (t(f) || this.error(`${l}: ${f.name} ${f.version} ${_(f)}. Build ${f.name} on ${e} and copy its app file in.`), [
125
+ `import factory from ${JSON.stringify(`${o}?${p}`)}`,
126
+ `export const manifest = ${JSON.stringify(f)}`,
122
127
  "export default (...args) => ({ ...factory(...args), icon: manifest.icon })",
123
128
  ""
124
129
  ].join("\n"));
@@ -126,4 +131,4 @@ function h() {
126
131
  };
127
132
  }
128
133
  //#endregion
129
- export { m as appFile, h as appFiles };
134
+ export { g as appFile, v as appFiles };
@@ -1,4 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
+ import '../styles/body-italic-font.js';
2
3
  /** Which member of the icon family paints — the two System 7 resource sizes. */
3
4
  export type VfIconSize = 'large' | 'small';
4
5
  /**
@@ -523,6 +524,12 @@ export declare class VfIcon extends VfIcon_base {
523
524
  * Selection inverts the ghost exactly as it inverts the art.
524
525
  */
525
526
  open: boolean;
527
+ /**
528
+ * The icon is an alias, an item standing for another: its name is set in
529
+ * italics, the body face's own, and the plate widens to the slant's last
530
+ * pixel.
531
+ */
532
+ alias: boolean;
526
533
  /**
527
534
  * Drag to move — `movable`, never `draggable`, which is a platform attribute
528
535
  * and accessor (see the class doc). Arrow keys move a focused icon too, which