@wildwinter/app-shell 0.44.0 → 0.46.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.
package/README.md CHANGED
@@ -163,7 +163,8 @@ openKitGallery({
163
163
  sections: [
164
164
  { caption: "Start from a kit", items: [
165
165
  { id: "starter", name: "Starter project", blurb: "Two cards that already work together.",
166
- play: "Press Play: one card opens the next.", lands: ["One box", "Two cards"] },
166
+ play: "Press Play: one card opens the next.", features: ["One box", "Playable at once"],
167
+ lands: ["One box", "Two cards"] },
167
168
  ] },
168
169
  { caption: "Learn from a finished project", action: "Open a Copy", usesDetails: false,
169
170
  items: [{ id: "hamlet", name: "The Hamlet", blurb: "Small.", badge: "Start here" }] },
@@ -174,10 +175,6 @@ openKitGallery({
174
175
 
175
176
  - **What a kit is made of stays in the app.** The shell knows names, words and
176
177
  pictures, never boxes or scenes.
177
- - **Pictures are masks.** `image` is a transparent file whose opaque pixels are
178
- the ink, drawn in `--kit-art-ink` on `--kit-art-plate`. A dark theme sets a
179
- paper-toned plate and a dark ink rather than inverting, which turns an
180
- engraving into a photographic negative.
181
178
  - Single click chooses, double-click chooses and goes, Enter in the name field
182
179
  creates; the name is required when asked for.
183
180
  - **v0.42: on the welcome too.** A welcome group with `tiles: true` draws its
@@ -191,6 +188,10 @@ openKitGallery({
191
188
  - **v0.44:** `onChoose` runs whenever an item is chosen, so an app can show or
192
189
  hide its own fields per kit; `validate` holds the pick until the app's own
193
190
  field is filled, marking and focusing it the way an empty name is.
191
+ - **v0.46:** `features`, a kit's features as pills on its tile (the welcome's
192
+ too) and in the panel, replace both `shows` and pictures. Pictures were tried
193
+ in 0.41 to 0.45 and ruled out by the author; `image` and the `--kit-art-*`
194
+ tokens are gone.
194
195
 
195
196
  ## Roadmap
196
197
 
@@ -1,4 +1,4 @@
1
- import { kitTile } from './chunk-MS3BSTQB.js';
1
+ import { kitTile } from './chunk-J6RSONXM.js';
2
2
  import { el } from './chunk-UG2P777F.js';
3
3
 
4
4
  // src/welcome.ts
@@ -18,7 +18,7 @@ function mountWelcome(host, opts) {
18
18
  name: item.name,
19
19
  blurb: item.hint ?? "",
20
20
  ...item.badge !== void 0 ? { badge: item.badge } : {},
21
- ...item.image !== void 0 ? { image: item.image } : {}
21
+ ...item.features !== void 0 ? { features: item.features } : {}
22
22
  }, item.onOpen))) : el("div", "welcome-list", ...g.items.map((item) => {
23
23
  const b = el(
24
24
  "button",
@@ -72,5 +72,5 @@ function mountWelcome(host, opts) {
72
72
  }
73
73
 
74
74
  export { mountWelcome };
75
- //# sourceMappingURL=chunk-OHVWMLRM.js.map
76
- //# sourceMappingURL=chunk-OHVWMLRM.js.map
75
+ //# sourceMappingURL=chunk-CUFREPJ5.js.map
76
+ //# sourceMappingURL=chunk-CUFREPJ5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/welcome.ts"],"names":[],"mappings":"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","file":"chunk-OHVWMLRM.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The welcome screen: what an app shows with no project open. Title, one line\n// under it, the actions (Open, New), an optional teaching line, optional\n// captioned groups (Storyletter's shipped examples), and the recents.\n//\n// Both apps drew this with the same twelve CSS rules under two class prefixes\n// (`.recent-name` / `.wel-recent-name`), four of them byte-identical. The CSS\n// here is Patterpad's; the captioned groups are Storyletter's, as an optional\n// slot, drawn as a plain list of rows (name + hint) rather than cards.\n//\n// The whole screen is a drag region (the window has no title bar to grab\n// here) and every control opts out. Styles ship as welcome.css.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\nimport { kitTile } from \"./kit-gallery.js\";\n\nexport interface WelcomeAction {\n label: string;\n onClick: () => void;\n /** The one accent button (Open a project…). */\n primary?: boolean;\n}\n\nexport interface WelcomeRecent {\n /** What the project calls itself, with the folder stem as the fallback. */\n name: string;\n /** Beside the name, because two projects may share a name and the folder\n * is how you tell them apart. */\n path: string;\n onOpen: () => void;\n}\n\nexport interface WelcomeGroupItem {\n name: string;\n /** What choosing it gets you, said before the click. */\n hint?: string;\n /** Tiled groups only: a word or two beside the name (\"Start here\"). */\n badge?: string;\n /** Tiled groups only: the picture, as the kit gallery draws it. */\n image?: string;\n onOpen: () => void;\n}\n\nexport interface WelcomeGroup {\n /** Sentence-case label text (\"Learn from a finished project\"); never an overline. */\n caption: string;\n /** The consequence of a click, said before the click (\"Each opens as your\n * own copy, in a folder you choose.\"). */\n note?: string;\n items: WelcomeGroupItem[];\n /** Draw the items as the kit gallery's tiles rather than rows (the kit\n * gallery brief, section 6: the welcome shows the kits and the examples as\n * the gallery shows them). Needs kit-gallery.css beside welcome.css. */\n tiles?: boolean;\n}\n\nexport interface WelcomeOptions {\n title: string;\n sub: string;\n actions: WelcomeAction[];\n recents: WelcomeRecent[];\n groups?: WelcomeGroup[];\n /** The teaching door: a line under the actions, quieter than a button\n * (\"New to Patter? Take the interactive tour\"). The app builds and styles\n * it; a text action in it is a link, not bare muted text. */\n tourLine?: HTMLElement;\n /** The recents caption. Default \"Recent\". */\n recentsCaption?: string;\n /** How many recents to show. Default 5. */\n maxRecents?: number;\n /** A sentence about why the last open failed, under everything. */\n error?: string;\n}\n\nexport interface Welcome {\n el: HTMLElement;\n /** Redraw the recents (the list changes as projects open and close). */\n setRecents(recents: WelcomeRecent[]): void;\n setError(error: string | undefined): void;\n}\n\n/** Mount the welcome into `host` (replacing its children). */\nexport function mountWelcome(host: HTMLElement, opts: WelcomeOptions): Welcome {\n const caption = (text: string): HTMLElement => el(\"h2\", \"welcome-caption\", text);\n const group = (label: string, ...body: (Node | null)[]): HTMLElement =>\n el(\"section\", \"welcome-group\", caption(label), ...body);\n\n const actions = el(\"div\", \"welcome-actions\");\n for (const a of opts.actions) {\n const b = el(\"button\", { className: `btn${a.primary ? \" primary\" : \"\"}`, text: a.label, onClick: a.onClick });\n b.type = \"button\";\n actions.append(b);\n }\n\n const groups = (opts.groups ?? []).map((g) => group(g.caption,\n g.note !== undefined ? el(\"p\", \"welcome-note\", g.note) : null,\n g.tiles\n ? el(\"div\", \"kit-gallery-grid welcome-tiles\", ...g.items.map((item) => kitTile({\n name: item.name, blurb: item.hint ?? \"\",\n ...(item.badge !== undefined ? { badge: item.badge } : {}),\n ...(item.image !== undefined ? { image: item.image } : {}),\n }, item.onOpen)))\n : el(\"div\", \"welcome-list\", ...g.items.map((item) => {\n const b = el(\"button\", { className: \"welcome-item\", onClick: item.onOpen },\n el(\"span\", \"welcome-item-name\", item.name),\n item.hint !== undefined ? el(\"span\", \"welcome-item-hint\", item.hint) : null);\n b.type = \"button\";\n return b;\n })),\n ));\n\n const recentsHost = el(\"div\", \"welcome-recents-slot\");\n const drawRecents = (recents: WelcomeRecent[]): void => {\n const shown = recents.slice(0, opts.maxRecents ?? 5);\n recentsHost.replaceChildren();\n if (shown.length === 0) return;\n recentsHost.append(group(opts.recentsCaption ?? \"Recent\",\n el(\"div\", \"welcome-recents\", ...shown.map((r) => {\n const b = el(\"button\", { className: \"welcome-recent\", onClick: r.onOpen },\n el(\"span\", \"welcome-recent-name\", r.name),\n el(\"span\", \"welcome-recent-path\", r.path));\n b.type = \"button\";\n return b;\n })),\n ));\n };\n drawRecents(opts.recents);\n\n const error = el(\"p\", \"welcome-error\");\n const setError = (text: string | undefined): void => {\n error.textContent = text ?? \"\";\n error.hidden = text === undefined || text === \"\";\n };\n setError(opts.error);\n\n const root = el(\"div\", \"welcome\", el(\"div\", \"welcome-card\",\n el(\"h1\", \"welcome-title\", opts.title),\n el(\"p\", \"welcome-sub\", opts.sub),\n actions,\n opts.tourLine ? el(\"div\", \"welcome-line\", opts.tourLine) : null,\n ...groups,\n recentsHost,\n error,\n ));\n host.replaceChildren(root);\n return { el: root, setRecents: drawRecents, setError };\n}\n"]}
1
+ {"version":3,"sources":["../src/welcome.ts"],"names":[],"mappings":"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","file":"chunk-CUFREPJ5.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The welcome screen: what an app shows with no project open. Title, one line\n// under it, the actions (Open, New), an optional teaching line, optional\n// captioned groups (Storyletter's shipped examples), and the recents.\n//\n// Both apps drew this with the same twelve CSS rules under two class prefixes\n// (`.recent-name` / `.wel-recent-name`), four of them byte-identical. The CSS\n// here is Patterpad's; the captioned groups are Storyletter's, as an optional\n// slot, drawn as a plain list of rows (name + hint) rather than cards.\n//\n// The whole screen is a drag region (the window has no title bar to grab\n// here) and every control opts out. Styles ship as welcome.css.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\nimport { kitTile } from \"./kit-gallery.js\";\n\nexport interface WelcomeAction {\n label: string;\n onClick: () => void;\n /** The one accent button (Open a project…). */\n primary?: boolean;\n}\n\nexport interface WelcomeRecent {\n /** What the project calls itself, with the folder stem as the fallback. */\n name: string;\n /** Beside the name, because two projects may share a name and the folder\n * is how you tell them apart. */\n path: string;\n onOpen: () => void;\n}\n\nexport interface WelcomeGroupItem {\n name: string;\n /** What choosing it gets you, said before the click. */\n hint?: string;\n /** Tiled groups only: a word or two beside the name (\"Start here\"). */\n badge?: string;\n /** Tiled groups only: the kit's feature pills, as the gallery draws them. */\n features?: string[];\n onOpen: () => void;\n}\n\nexport interface WelcomeGroup {\n /** Sentence-case label text (\"Learn from a finished project\"); never an overline. */\n caption: string;\n /** The consequence of a click, said before the click (\"Each opens as your\n * own copy, in a folder you choose.\"). */\n note?: string;\n items: WelcomeGroupItem[];\n /** Draw the items as the kit gallery's tiles rather than rows (the kit\n * gallery brief, section 6: the welcome shows the kits and the examples as\n * the gallery shows them). Needs kit-gallery.css beside welcome.css. */\n tiles?: boolean;\n}\n\nexport interface WelcomeOptions {\n title: string;\n sub: string;\n actions: WelcomeAction[];\n recents: WelcomeRecent[];\n groups?: WelcomeGroup[];\n /** The teaching door: a line under the actions, quieter than a button\n * (\"New to Patter? Take the interactive tour\"). The app builds and styles\n * it; a text action in it is a link, not bare muted text. */\n tourLine?: HTMLElement;\n /** The recents caption. Default \"Recent\". */\n recentsCaption?: string;\n /** How many recents to show. Default 5. */\n maxRecents?: number;\n /** A sentence about why the last open failed, under everything. */\n error?: string;\n}\n\nexport interface Welcome {\n el: HTMLElement;\n /** Redraw the recents (the list changes as projects open and close). */\n setRecents(recents: WelcomeRecent[]): void;\n setError(error: string | undefined): void;\n}\n\n/** Mount the welcome into `host` (replacing its children). */\nexport function mountWelcome(host: HTMLElement, opts: WelcomeOptions): Welcome {\n const caption = (text: string): HTMLElement => el(\"h2\", \"welcome-caption\", text);\n const group = (label: string, ...body: (Node | null)[]): HTMLElement =>\n el(\"section\", \"welcome-group\", caption(label), ...body);\n\n const actions = el(\"div\", \"welcome-actions\");\n for (const a of opts.actions) {\n const b = el(\"button\", { className: `btn${a.primary ? \" primary\" : \"\"}`, text: a.label, onClick: a.onClick });\n b.type = \"button\";\n actions.append(b);\n }\n\n const groups = (opts.groups ?? []).map((g) => group(g.caption,\n g.note !== undefined ? el(\"p\", \"welcome-note\", g.note) : null,\n g.tiles\n ? el(\"div\", \"kit-gallery-grid welcome-tiles\", ...g.items.map((item) => kitTile({\n name: item.name, blurb: item.hint ?? \"\",\n ...(item.badge !== undefined ? { badge: item.badge } : {}),\n ...(item.features !== undefined ? { features: item.features } : {}),\n }, item.onOpen)))\n : el(\"div\", \"welcome-list\", ...g.items.map((item) => {\n const b = el(\"button\", { className: \"welcome-item\", onClick: item.onOpen },\n el(\"span\", \"welcome-item-name\", item.name),\n item.hint !== undefined ? el(\"span\", \"welcome-item-hint\", item.hint) : null);\n b.type = \"button\";\n return b;\n })),\n ));\n\n const recentsHost = el(\"div\", \"welcome-recents-slot\");\n const drawRecents = (recents: WelcomeRecent[]): void => {\n const shown = recents.slice(0, opts.maxRecents ?? 5);\n recentsHost.replaceChildren();\n if (shown.length === 0) return;\n recentsHost.append(group(opts.recentsCaption ?? \"Recent\",\n el(\"div\", \"welcome-recents\", ...shown.map((r) => {\n const b = el(\"button\", { className: \"welcome-recent\", onClick: r.onOpen },\n el(\"span\", \"welcome-recent-name\", r.name),\n el(\"span\", \"welcome-recent-path\", r.path));\n b.type = \"button\";\n return b;\n })),\n ));\n };\n drawRecents(opts.recents);\n\n const error = el(\"p\", \"welcome-error\");\n const setError = (text: string | undefined): void => {\n error.textContent = text ?? \"\";\n error.hidden = text === undefined || text === \"\";\n };\n setError(opts.error);\n\n const root = el(\"div\", \"welcome\", el(\"div\", \"welcome-card\",\n el(\"h1\", \"welcome-title\", opts.title),\n el(\"p\", \"welcome-sub\", opts.sub),\n actions,\n opts.tourLine ? el(\"div\", \"welcome-line\", opts.tourLine) : null,\n ...groups,\n recentsHost,\n error,\n ));\n host.replaceChildren(root);\n return { el: root, setRecents: drawRecents, setError };\n}\n"]}
@@ -2,28 +2,22 @@ import { dialogFrame } from './chunk-YRFJLJUH.js';
2
2
  import { el } from './chunk-UG2P777F.js';
3
3
 
4
4
  // src/kit-gallery.ts
5
- function setArt(host, image) {
6
- host.replaceChildren();
7
- host.hidden = image === void 0;
8
- if (image === void 0) return;
9
- const ink = el("span", "kit-gallery-ink");
10
- ink.style.setProperty("--kit-art", `url("${image.replace(/"/g, "%22")}")`);
11
- host.append(ink);
5
+ function featurePills(features) {
6
+ if (!features || features.length === 0) return null;
7
+ return el("span", "kit-gallery-features", ...features.map((f) => el("span", "kit-gallery-feature", f)));
12
8
  }
13
9
  function kitTile(item, onClick) {
14
- const art = el("div", "kit-gallery-art");
15
- setArt(art, item.image);
16
10
  const tile = el(
17
11
  "button",
18
12
  { className: "kit-gallery-tile", onClick },
19
- art,
20
13
  el(
21
14
  "span",
22
15
  "kit-gallery-tile-head",
23
16
  el("span", "kit-gallery-tile-name", item.name),
24
17
  item.badge !== void 0 ? el("span", "kit-gallery-badge", item.badge) : null
25
18
  ),
26
- el("span", "kit-gallery-tile-blurb", item.blurb)
19
+ el("span", "kit-gallery-tile-blurb", item.blurb),
20
+ featurePills(item.features)
27
21
  );
28
22
  tile.type = "button";
29
23
  return tile;
@@ -45,24 +39,22 @@ function openKitGallery(opts) {
45
39
  ...nameInput ? [el("label", "kit-gallery-label", opts.nameLabel ?? "Name"), nameInput] : [],
46
40
  ...opts.details ? [opts.details] : []
47
41
  );
48
- const panelArt = el("div", "kit-gallery-art kit-gallery-panel-art");
49
42
  const panelName = el("h3", "kit-gallery-panel-name");
50
43
  const panelBlurb = el("p", "kit-gallery-panel-blurb");
51
44
  const panelDetail = el("p", "kit-gallery-panel-detail");
52
45
  const panelPlay = el("p", "kit-gallery-play");
53
- const panelShows = el("p", "kit-gallery-shows");
46
+ const panelFeatures = el("div", "kit-gallery-panel-features");
54
47
  const panelLands = el("ul", "kit-gallery-lands");
55
48
  const panelLandsCaption = el("p", "kit-gallery-lands-caption", "What you get");
56
49
  const panelNote = el("p", "kit-gallery-note");
57
50
  const panel = el(
58
51
  "div",
59
52
  "kit-gallery-panel",
60
- panelArt,
61
53
  panelName,
54
+ panelFeatures,
62
55
  panelBlurb,
63
56
  panelDetail,
64
57
  panelPlay,
65
- panelShows,
66
58
  panelLandsCaption,
67
59
  panelLands,
68
60
  panelNote,
@@ -102,12 +94,13 @@ function openKitGallery(opts) {
102
94
  chosen = next;
103
95
  for (const [tid, tile] of tiles) tile.setAttribute("aria-pressed", String(tid === id));
104
96
  const { item, section } = chosen;
105
- setArt(panelArt, item.image);
106
97
  panelName.textContent = item.name;
107
98
  setText(panelBlurb, item.blurb);
108
99
  setText(panelDetail, item.detail);
109
100
  setText(panelPlay, item.play);
110
- setText(panelShows, item.shows !== void 0 ? `Shows: ${item.shows}` : void 0);
101
+ const pills = featurePills(item.features);
102
+ panelFeatures.replaceChildren(...pills ? [pills] : []);
103
+ panelFeatures.hidden = pills === null;
111
104
  panelLands.replaceChildren(...(item.lands ?? []).map((l) => el("li", void 0, l)));
112
105
  panelLands.hidden = (item.lands ?? []).length === 0;
113
106
  panelLandsCaption.hidden = panelLands.hidden;
@@ -165,5 +158,5 @@ function openKitGallery(opts) {
165
158
  }
166
159
 
167
160
  export { kitTile, openKitGallery };
168
- //# sourceMappingURL=chunk-MS3BSTQB.js.map
169
- //# sourceMappingURL=chunk-MS3BSTQB.js.map
161
+ //# sourceMappingURL=chunk-J6RSONXM.js.map
162
+ //# sourceMappingURL=chunk-J6RSONXM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/kit-gallery.ts"],"names":[],"mappings":"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","file":"chunk-J6RSONXM.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The kit gallery: pick a starting point by what you want to make.\n//\n// The new-thing moment for the family (New Project, New Box, New Scene), drawn\n// once. Adobe's New Document is the shape: tiles on the left, a details panel\n// on the right, one screen. Not a wizard: a wizard asks for settings before\n// anything has been chosen, and the choice is the part that teaches.\n//\n// Storyletter grew this as `kit-picker.ts` (one component at two scales on\n// <dialog>, \"a shell candidate, held back until it had been used in earnest\")\n// and the kit gallery brief moved it here, 2026-09-27\n// (storylet-studio/design/kit-gallery.md). What a kit IS made of stays in\n// each app: this file knows names, words and pictures, never boxes or scenes.\n//\n// Sections are shelves (\"Start from a kit\", \"Learn from a finished project\"),\n// and each says what its button does, because opening a copy of an example\n// and creating from a kit are different acts behind the same kind of tile.\n// The app's own fields (a project name, version control, a build path) sit in\n// the details panel, shown only for the sections that use them.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\nimport { dialogFrame } from \"./dialog.js\";\n\nexport interface KitGalleryItem<T extends string> {\n id: T;\n /** What you are making, in the author's terms, not the model's. */\n name: string;\n /** What it is FOR: the panel's first line, and the tile's unless `tile` says otherwise. */\n blurb: string;\n /** The tile's line, when the blurb is longer than a tile holds (three\n * lines). A whole short sentence reads better than a long one cut off. */\n tile?: string;\n /** More, for the panel only. */\n detail?: string;\n /** What pressing Play (or Run) shows, said as a promise. */\n play?: string;\n /** What it has, in a word or two each (\"Drawn map\", \"Copies\"): pills on the tile and in the\n * panel, so kits compare at a glance before any is chosen. The author's ask, 2026-09-27, in\n * place of pictures, which were ruled out the same day. */\n features?: string[];\n /** What lands, in the concrete, as a short list in the panel. */\n lands?: string[];\n /** A word or two on the tile (\"Start here\"). */\n badge?: string;\n}\n\nexport interface KitGallerySection<T extends string> {\n /** Sentence-case label text (\"Start from a kit\"). Absent: no caption. */\n caption?: string;\n /** The consequence of a click, said before the click. */\n note?: string;\n items: KitGalleryItem<T>[];\n /** The primary button's label while an item here is chosen. Default \"Create\". */\n action?: string;\n /** Show the app's detail fields (and ask for the name) for this section.\n * Default true. An example is opened, not named, so its shelf says false. */\n usesDetails?: boolean;\n}\n\nexport interface KitGalleryChoice {\n /** Present when a name was asked for and the chosen section uses details. */\n name?: string;\n}\n\nexport interface KitGalleryOptions<T extends string> {\n /** The dialog's heading: \"New project\", \"New box\", \"New scene\". */\n title: string;\n /** What the thing being made IS, in concrete terms. */\n what?: string;\n sections: KitGallerySection<T>[];\n /** Ask for a name in the details panel, with this placeholder. Required\n * when asked: making a thing without one would have to invent a folder. */\n namePlaceholder?: string;\n /** The name field's label. Default \"Name\". */\n nameLabel?: string;\n /** The app's own fields, below the name (version control, a build path). */\n details?: HTMLElement;\n /** Runs whenever an item is chosen, the opening one included, so the app can show or hide its\n * own fields for it (New Scene's Speaker, for a kit with lines). */\n onChoose?: (id: T) => void;\n /** Checked before `onPick`, after the name: return the app's field that still needs filling (it\n * is focused and marked, and nothing is made), or null when all is well. */\n validate?: (id: T) => HTMLElement | null;\n /** Runs as the name is typed (and once on open, with \"\"), so the app's own\n * fields can follow it: Patterpad's folder preview and default publish path. */\n onNameInput?: (name: string) => void;\n /** The item chosen when the gallery opens. Default: the first. */\n initial?: T;\n onPick: (id: T, choice: KitGalleryChoice) => void;\n /** Runs once the gallery has closed, whichever way. */\n onClose?: () => void;\n}\n\nexport interface KitGallery {\n readonly dialog: HTMLDialogElement;\n close(): void;\n}\n\n/** A kit's features as pills, or null when it lists none. */\nfunction featurePills(features: string[] | undefined): HTMLElement | null {\n if (!features || features.length === 0) return null;\n return el(\"span\", \"kit-gallery-features\", ...features.map((f) => el(\"span\", \"kit-gallery-feature\", f)));\n}\n\n/** One tile: the name with its badge, the blurb, the feature pills. The gallery's\n * own, and the welcome screen's, so a kit looks the same wherever it is met\n * (welcome.ts draws a tiled group with it; import kit-gallery.css there too). */\nexport function kitTile(item: { name: string; blurb: string; badge?: string; features?: string[] }, onClick: () => void): HTMLButtonElement {\n const tile = el(\"button\", { className: \"kit-gallery-tile\", onClick },\n el(\"span\", \"kit-gallery-tile-head\",\n el(\"span\", \"kit-gallery-tile-name\", item.name),\n item.badge !== undefined ? el(\"span\", \"kit-gallery-badge\", item.badge) : null),\n el(\"span\", \"kit-gallery-tile-blurb\", item.blurb),\n featurePills(item.features)) as HTMLButtonElement;\n tile.type = \"button\";\n return tile;\n}\n\n/** Open the gallery as a modal dialog on the family's frame. */\nexport function openKitGallery<T extends string>(opts: KitGalleryOptions<T>): KitGallery {\n const frame = dialogFrame({ title: opts.title, ...(opts.what !== undefined ? { sub: opts.what } : {}), className: \"kit-gallery\", ...(opts.onClose ? { onClose: opts.onClose } : {}) });\n const { dialog } = frame;\n\n const entries = opts.sections.flatMap((section, s) => section.items.map((item) => ({ item, section, s })));\n if (entries.length === 0) throw new Error(\"openKitGallery: no items to choose from\");\n let chosen = entries.find((e) => e.item.id === opts.initial) ?? entries[0]!;\n\n // --- the details panel ----------------------------------------------------\n const nameInput = opts.namePlaceholder === undefined ? undefined : el(\"input\", { className: \"field kit-gallery-name\" }) as HTMLInputElement;\n if (nameInput) {\n nameInput.placeholder = opts.namePlaceholder!;\n nameInput.setAttribute(\"aria-label\", opts.nameLabel ?? \"Name\");\n }\n const fields = el(\"div\", \"kit-gallery-fields\",\n ...(nameInput ? [el(\"label\", \"kit-gallery-label\", opts.nameLabel ?? \"Name\"), nameInput] : []),\n ...(opts.details ? [opts.details] : []));\n\n const panelName = el(\"h3\", \"kit-gallery-panel-name\");\n const panelBlurb = el(\"p\", \"kit-gallery-panel-blurb\");\n const panelDetail = el(\"p\", \"kit-gallery-panel-detail\");\n const panelPlay = el(\"p\", \"kit-gallery-play\");\n const panelFeatures = el(\"div\", \"kit-gallery-panel-features\");\n const panelLands = el(\"ul\", \"kit-gallery-lands\");\n const panelLandsCaption = el(\"p\", \"kit-gallery-lands-caption\", \"What you get\");\n const panelNote = el(\"p\", \"kit-gallery-note\");\n const panel = el(\"div\", \"kit-gallery-panel\",\n panelName, panelFeatures, panelBlurb, panelDetail, panelPlay, panelLandsCaption, panelLands, panelNote, fields);\n\n const primary = el(\"button\", { className: \"btn primary\", onClick: () => commit() }) as HTMLButtonElement;\n primary.type = \"button\";\n const cancel = el(\"button\", { className: \"btn\", text: \"Cancel\", onClick: () => frame.close() }) as HTMLButtonElement;\n cancel.type = \"button\";\n\n const setText = (node: HTMLElement, text: string | undefined): void => {\n node.textContent = text ?? \"\";\n node.hidden = text === undefined || text === \"\";\n };\n\n // --- the tiles --------------------------------------------------------------\n const tiles = new Map<T, HTMLButtonElement>();\n const shelves = opts.sections.map((section) => {\n const grid = el(\"div\", \"kit-gallery-grid\", ...section.items.map((item) => {\n const tile = kitTile({ ...item, blurb: item.tile ?? item.blurb }, () => choose(item.id));\n tile.dataset.kit = item.id;\n // Double-click is the desktop's \"choose and go\"; a single click only chooses,\n // so a tile can be read in the panel before anything is made.\n tile.addEventListener(\"dblclick\", () => { choose(item.id); commit(); });\n tiles.set(item.id, tile);\n return tile;\n }));\n return el(\"section\", \"kit-gallery-shelf\",\n section.caption !== undefined ? el(\"h3\", \"kit-gallery-caption\", section.caption) : null,\n section.note !== undefined ? el(\"p\", \"kit-gallery-shelf-note\", section.note) : null,\n grid);\n });\n\n const choose = (id: T): void => {\n const next = entries.find((e) => e.item.id === id);\n if (!next) return;\n chosen = next;\n for (const [tid, tile] of tiles) tile.setAttribute(\"aria-pressed\", String(tid === id));\n const { item, section } = chosen;\n panelName.textContent = item.name;\n setText(panelBlurb, item.blurb);\n setText(panelDetail, item.detail);\n setText(panelPlay, item.play);\n const pills = featurePills(item.features);\n panelFeatures.replaceChildren(...(pills ? [pills] : []));\n panelFeatures.hidden = pills === null;\n panelLands.replaceChildren(...(item.lands ?? []).map((l) => el(\"li\", undefined, l)));\n panelLands.hidden = (item.lands ?? []).length === 0;\n panelLandsCaption.hidden = panelLands.hidden;\n const details = section.usesDetails !== false;\n fields.hidden = !details || (nameInput === undefined && opts.details === undefined);\n // Without the fields, the shelf's note is what the click will do next\n // (\"a folder you choose\"), so the panel repeats it beside the button.\n setText(panelNote, details ? undefined : section.note);\n primary.textContent = section.action ?? \"Create\";\n opts.onChoose?.(item.id);\n };\n\n const commit = (): void => {\n const { item, section } = chosen;\n const details = section.usesDetails !== false;\n let name: string | undefined;\n if (details && nameInput) {\n name = nameInput.value.trim();\n if (name === \"\") { nameInput.focus(); nameInput.classList.add(\"kit-gallery-missing\"); return; }\n }\n const missing = opts.validate?.(item.id) ?? null;\n if (missing) {\n missing.classList.add(\"kit-gallery-missing\");\n missing.addEventListener(\"input\", () => missing.classList.remove(\"kit-gallery-missing\"), { once: true });\n missing.focus();\n return;\n }\n frame.close();\n opts.onPick(item.id, name !== undefined ? { name } : {});\n };\n\n if (nameInput) {\n nameInput.addEventListener(\"input\", () => {\n nameInput.classList.remove(\"kit-gallery-missing\");\n opts.onNameInput?.(nameInput.value);\n });\n opts.onNameInput?.(\"\");\n // Enter creates with what is chosen: the author has said what it should be\n // called, and the tile they are looking at is the one they meant.\n nameInput.addEventListener(\"keydown\", (e) => {\n e.stopPropagation();\n if (e.key !== \"Enter\") return;\n e.preventDefault();\n commit();\n });\n }\n\n frame.body.append(el(\"div\", \"kit-gallery-layout\", el(\"div\", \"kit-gallery-shelves\", ...shelves), panel));\n frame.actions.append(cancel, primary);\n dialog.addEventListener(\"click\", (e) => { if (e.target === dialog) frame.close(); });\n\n choose(chosen.item.id);\n frame.open();\n // Focus where the next keystroke is wanted: the name when one is asked for\n // and the opening shelf uses it, otherwise the chosen tile.\n if (nameInput && chosen.section.usesDetails !== false) nameInput.focus();\n else tiles.get(chosen.item.id)?.focus();\n\n return { dialog, close: () => frame.close() };\n}\n"]}
package/dist/index.cjs CHANGED
@@ -2699,28 +2699,22 @@ function showUpdaterDialog(opts) {
2699
2699
  }
2700
2700
 
2701
2701
  // src/kit-gallery.ts
2702
- function setArt(host, image) {
2703
- host.replaceChildren();
2704
- host.hidden = image === void 0;
2705
- if (image === void 0) return;
2706
- const ink = el("span", "kit-gallery-ink");
2707
- ink.style.setProperty("--kit-art", `url("${image.replace(/"/g, "%22")}")`);
2708
- host.append(ink);
2702
+ function featurePills(features) {
2703
+ if (!features || features.length === 0) return null;
2704
+ return el("span", "kit-gallery-features", ...features.map((f) => el("span", "kit-gallery-feature", f)));
2709
2705
  }
2710
2706
  function kitTile(item, onClick) {
2711
- const art = el("div", "kit-gallery-art");
2712
- setArt(art, item.image);
2713
2707
  const tile = el(
2714
2708
  "button",
2715
2709
  { className: "kit-gallery-tile", onClick },
2716
- art,
2717
2710
  el(
2718
2711
  "span",
2719
2712
  "kit-gallery-tile-head",
2720
2713
  el("span", "kit-gallery-tile-name", item.name),
2721
2714
  item.badge !== void 0 ? el("span", "kit-gallery-badge", item.badge) : null
2722
2715
  ),
2723
- el("span", "kit-gallery-tile-blurb", item.blurb)
2716
+ el("span", "kit-gallery-tile-blurb", item.blurb),
2717
+ featurePills(item.features)
2724
2718
  );
2725
2719
  tile.type = "button";
2726
2720
  return tile;
@@ -2742,24 +2736,22 @@ function openKitGallery(opts) {
2742
2736
  ...nameInput ? [el("label", "kit-gallery-label", opts.nameLabel ?? "Name"), nameInput] : [],
2743
2737
  ...opts.details ? [opts.details] : []
2744
2738
  );
2745
- const panelArt = el("div", "kit-gallery-art kit-gallery-panel-art");
2746
2739
  const panelName = el("h3", "kit-gallery-panel-name");
2747
2740
  const panelBlurb = el("p", "kit-gallery-panel-blurb");
2748
2741
  const panelDetail = el("p", "kit-gallery-panel-detail");
2749
2742
  const panelPlay = el("p", "kit-gallery-play");
2750
- const panelShows = el("p", "kit-gallery-shows");
2743
+ const panelFeatures = el("div", "kit-gallery-panel-features");
2751
2744
  const panelLands = el("ul", "kit-gallery-lands");
2752
2745
  const panelLandsCaption = el("p", "kit-gallery-lands-caption", "What you get");
2753
2746
  const panelNote = el("p", "kit-gallery-note");
2754
2747
  const panel = el(
2755
2748
  "div",
2756
2749
  "kit-gallery-panel",
2757
- panelArt,
2758
2750
  panelName,
2751
+ panelFeatures,
2759
2752
  panelBlurb,
2760
2753
  panelDetail,
2761
2754
  panelPlay,
2762
- panelShows,
2763
2755
  panelLandsCaption,
2764
2756
  panelLands,
2765
2757
  panelNote,
@@ -2799,12 +2791,13 @@ function openKitGallery(opts) {
2799
2791
  chosen = next;
2800
2792
  for (const [tid, tile] of tiles) tile.setAttribute("aria-pressed", String(tid === id));
2801
2793
  const { item, section } = chosen;
2802
- setArt(panelArt, item.image);
2803
2794
  panelName.textContent = item.name;
2804
2795
  setText(panelBlurb, item.blurb);
2805
2796
  setText(panelDetail, item.detail);
2806
2797
  setText(panelPlay, item.play);
2807
- setText(panelShows, item.shows !== void 0 ? `Shows: ${item.shows}` : void 0);
2798
+ const pills = featurePills(item.features);
2799
+ panelFeatures.replaceChildren(...pills ? [pills] : []);
2800
+ panelFeatures.hidden = pills === null;
2808
2801
  panelLands.replaceChildren(...(item.lands ?? []).map((l) => el("li", void 0, l)));
2809
2802
  panelLands.hidden = (item.lands ?? []).length === 0;
2810
2803
  panelLandsCaption.hidden = panelLands.hidden;
@@ -2878,7 +2871,7 @@ function mountWelcome(host, opts) {
2878
2871
  name: item.name,
2879
2872
  blurb: item.hint ?? "",
2880
2873
  ...item.badge !== void 0 ? { badge: item.badge } : {},
2881
- ...item.image !== void 0 ? { image: item.image } : {}
2874
+ ...item.features !== void 0 ? { features: item.features } : {}
2882
2875
  }, item.onOpen))) : el("div", "welcome-list", ...g.items.map((item) => {
2883
2876
  const b = el(
2884
2877
  "button",