@wildwinter/app-shell 0.41.0 → 0.43.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
@@ -180,6 +180,14 @@ openKitGallery({
180
180
  engraving into a photographic negative.
181
181
  - Single click chooses, double-click chooses and goes, Enter in the name field
182
182
  creates; the name is required when asked for.
183
+ - **v0.42: on the welcome too.** A welcome group with `tiles: true` draws its
184
+ items with the gallery's own tile (`kitTile`), so a kit looks the same on the
185
+ welcome as in New Project; import `kit-gallery.css` beside `welcome.css`. An
186
+ item's `tile` is a shorter line for the tile when its blurb runs past the
187
+ three lines a tile shows.
188
+ - **v0.43:** `onNameInput` runs as the name is typed (and once on open), so an
189
+ app's own fields can follow it: Patterpad's "Saved as" preview and its
190
+ default publish path.
183
191
 
184
192
  ## Roadmap
185
193
 
@@ -2,6 +2,32 @@ 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);
12
+ }
13
+ function kitTile(item, onClick) {
14
+ const art = el("div", "kit-gallery-art");
15
+ setArt(art, item.image);
16
+ const tile = el(
17
+ "button",
18
+ { className: "kit-gallery-tile", onClick },
19
+ art,
20
+ el(
21
+ "span",
22
+ "kit-gallery-tile-head",
23
+ el("span", "kit-gallery-tile-name", item.name),
24
+ item.badge !== void 0 ? el("span", "kit-gallery-badge", item.badge) : null
25
+ ),
26
+ el("span", "kit-gallery-tile-blurb", item.blurb)
27
+ );
28
+ tile.type = "button";
29
+ return tile;
30
+ }
5
31
  function openKitGallery(opts) {
6
32
  const frame = dialogFrame({ title: opts.title, ...opts.what !== void 0 ? { sub: opts.what } : {}, className: "kit-gallery", ...opts.onClose ? { onClose: opts.onClose } : {} });
7
33
  const { dialog } = frame;
@@ -46,14 +72,6 @@ function openKitGallery(opts) {
46
72
  primary.type = "button";
47
73
  const cancel = el("button", { className: "btn", text: "Cancel", onClick: () => frame.close() });
48
74
  cancel.type = "button";
49
- const setArt = (host, image) => {
50
- host.replaceChildren();
51
- host.hidden = image === void 0;
52
- if (image === void 0) return;
53
- const ink = el("span", "kit-gallery-ink");
54
- ink.style.setProperty("--kit-art", `url("${image.replace(/"/g, "%22")}")`);
55
- host.append(ink);
56
- };
57
75
  const setText = (node, text) => {
58
76
  node.textContent = text ?? "";
59
77
  node.hidden = text === void 0 || text === "";
@@ -61,21 +79,7 @@ function openKitGallery(opts) {
61
79
  const tiles = /* @__PURE__ */ new Map();
62
80
  const shelves = opts.sections.map((section) => {
63
81
  const grid = el("div", "kit-gallery-grid", ...section.items.map((item) => {
64
- const art = el("div", "kit-gallery-art");
65
- setArt(art, item.image);
66
- const tile = el(
67
- "button",
68
- { className: "kit-gallery-tile", onClick: () => choose(item.id) },
69
- art,
70
- el(
71
- "span",
72
- "kit-gallery-tile-head",
73
- el("span", "kit-gallery-tile-name", item.name),
74
- item.badge !== void 0 ? el("span", "kit-gallery-badge", item.badge) : null
75
- ),
76
- el("span", "kit-gallery-tile-blurb", item.blurb)
77
- );
78
- tile.type = "button";
82
+ const tile = kitTile({ ...item, blurb: item.tile ?? item.blurb }, () => choose(item.id));
79
83
  tile.dataset.kit = item.id;
80
84
  tile.addEventListener("dblclick", () => {
81
85
  choose(item.id);
@@ -128,7 +132,11 @@ function openKitGallery(opts) {
128
132
  opts.onPick(item.id, name !== void 0 ? { name } : {});
129
133
  };
130
134
  if (nameInput) {
131
- nameInput.addEventListener("input", () => nameInput.classList.remove("kit-gallery-missing"));
135
+ nameInput.addEventListener("input", () => {
136
+ nameInput.classList.remove("kit-gallery-missing");
137
+ opts.onNameInput?.(nameInput.value);
138
+ });
139
+ opts.onNameInput?.("");
132
140
  nameInput.addEventListener("keydown", (e) => {
133
141
  e.stopPropagation();
134
142
  if (e.key !== "Enter") return;
@@ -148,6 +156,6 @@ function openKitGallery(opts) {
148
156
  return { dialog, close: () => frame.close() };
149
157
  }
150
158
 
151
- export { openKitGallery };
152
- //# sourceMappingURL=chunk-BZ3MQDWM.js.map
153
- //# sourceMappingURL=chunk-BZ3MQDWM.js.map
159
+ export { kitTile, openKitGallery };
160
+ //# sourceMappingURL=chunk-MU2WO6KQ.js.map
161
+ //# sourceMappingURL=chunk-MU2WO6KQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/kit-gallery.ts"],"names":[],"mappings":"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","file":"chunk-MU2WO6KQ.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 /** The model's parts it puts to work, second and quieter (\"Shows: ...\"). */\n shows?: 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 /** An illustration: a transparent image whose opaque pixels are the ink.\n * Drawn as a mask in `--kit-art-ink` on `--kit-art-plate`, so one file\n * serves both themes and the app chooses the colours. */\n image?: 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 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/** Draw `image` into an art box as a mask (see kit-gallery.css), or hide the box. */\nfunction setArt(host: HTMLElement, image: string | undefined): void {\n host.replaceChildren();\n host.hidden = image === undefined;\n if (image === undefined) return;\n const ink = el(\"span\", \"kit-gallery-ink\");\n ink.style.setProperty(\"--kit-art\", `url(\"${image.replace(/\"/g, \"%22\")}\")`);\n host.append(ink);\n}\n\n/** One tile: the picture, the name with its badge, the blurb. 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; image?: string }, onClick: () => void): HTMLButtonElement {\n const art = el(\"div\", \"kit-gallery-art\");\n setArt(art, item.image);\n const tile = el(\"button\", { className: \"kit-gallery-tile\", onClick },\n art,\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)) 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 panelArt = el(\"div\", \"kit-gallery-art kit-gallery-panel-art\");\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 panelShows = el(\"p\", \"kit-gallery-shows\");\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 panelArt, panelName, panelBlurb, panelDetail, panelPlay, panelShows, 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 setArt(panelArt, item.image);\n panelName.textContent = item.name;\n setText(panelBlurb, item.blurb);\n setText(panelDetail, item.detail);\n setText(panelPlay, item.play);\n setText(panelShows, item.shows !== undefined ? `Shows: ${item.shows}` : undefined);\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 };\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 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"]}
@@ -1,3 +1,4 @@
1
+ import { kitTile } from './chunk-MU2WO6KQ.js';
1
2
  import { el } from './chunk-UG2P777F.js';
2
3
 
3
4
  // src/welcome.ts
@@ -13,7 +14,12 @@ function mountWelcome(host, opts) {
13
14
  const groups = (opts.groups ?? []).map((g) => group(
14
15
  g.caption,
15
16
  g.note !== void 0 ? el("p", "welcome-note", g.note) : null,
16
- el("div", "welcome-list", ...g.items.map((item) => {
17
+ g.tiles ? el("div", "kit-gallery-grid welcome-tiles", ...g.items.map((item) => kitTile({
18
+ name: item.name,
19
+ blurb: item.hint ?? "",
20
+ ...item.badge !== void 0 ? { badge: item.badge } : {},
21
+ ...item.image !== void 0 ? { image: item.image } : {}
22
+ }, item.onOpen))) : el("div", "welcome-list", ...g.items.map((item) => {
17
23
  const b = el(
18
24
  "button",
19
25
  { className: "welcome-item", onClick: item.onOpen },
@@ -66,5 +72,5 @@ function mountWelcome(host, opts) {
66
72
  }
67
73
 
68
74
  export { mountWelcome };
69
- //# sourceMappingURL=chunk-SEWJLUOD.js.map
70
- //# sourceMappingURL=chunk-SEWJLUOD.js.map
75
+ //# sourceMappingURL=chunk-URLZYOYR.js.map
76
+ //# sourceMappingURL=chunk-URLZYOYR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/welcome.ts"],"names":[],"mappings":"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","file":"chunk-URLZYOYR.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"]}
package/dist/index.cjs CHANGED
@@ -2698,72 +2698,33 @@ function showUpdaterDialog(opts) {
2698
2698
  });
2699
2699
  }
2700
2700
 
2701
- // src/welcome.ts
2702
- function mountWelcome(host, opts) {
2703
- const caption = (text) => el("h2", "welcome-caption", text);
2704
- const group = (label2, ...body) => el("section", "welcome-group", caption(label2), ...body);
2705
- const actions = el("div", "welcome-actions");
2706
- for (const a of opts.actions) {
2707
- const b = el("button", { className: `btn${a.primary ? " primary" : ""}`, text: a.label, onClick: a.onClick });
2708
- b.type = "button";
2709
- actions.append(b);
2710
- }
2711
- const groups = (opts.groups ?? []).map((g) => group(
2712
- g.caption,
2713
- g.note !== void 0 ? el("p", "welcome-note", g.note) : null,
2714
- el("div", "welcome-list", ...g.items.map((item) => {
2715
- const b = el(
2716
- "button",
2717
- { className: "welcome-item", onClick: item.onOpen },
2718
- el("span", "welcome-item-name", item.name),
2719
- item.hint !== void 0 ? el("span", "welcome-item-hint", item.hint) : null
2720
- );
2721
- b.type = "button";
2722
- return b;
2723
- }))
2724
- ));
2725
- const recentsHost = el("div", "welcome-recents-slot");
2726
- const drawRecents = (recents) => {
2727
- const shown = recents.slice(0, opts.maxRecents ?? 5);
2728
- recentsHost.replaceChildren();
2729
- if (shown.length === 0) return;
2730
- recentsHost.append(group(
2731
- opts.recentsCaption ?? "Recent",
2732
- el("div", "welcome-recents", ...shown.map((r) => {
2733
- const b = el(
2734
- "button",
2735
- { className: "welcome-recent", onClick: r.onOpen },
2736
- el("span", "welcome-recent-name", r.name),
2737
- el("span", "welcome-recent-path", r.path)
2738
- );
2739
- b.type = "button";
2740
- return b;
2741
- }))
2742
- ));
2743
- };
2744
- drawRecents(opts.recents);
2745
- const error = el("p", "welcome-error");
2746
- const setError = (text) => {
2747
- error.textContent = text ?? "";
2748
- error.hidden = text === void 0 || text === "";
2749
- };
2750
- setError(opts.error);
2751
- const root = el("div", "welcome", el(
2752
- "div",
2753
- "welcome-card",
2754
- el("h1", "welcome-title", opts.title),
2755
- el("p", "welcome-sub", opts.sub),
2756
- actions,
2757
- opts.tourLine ? el("div", "welcome-line", opts.tourLine) : null,
2758
- ...groups,
2759
- recentsHost,
2760
- error
2761
- ));
2762
- host.replaceChildren(root);
2763
- return { el: root, setRecents: drawRecents, setError };
2764
- }
2765
-
2766
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);
2709
+ }
2710
+ function kitTile(item, onClick) {
2711
+ const art = el("div", "kit-gallery-art");
2712
+ setArt(art, item.image);
2713
+ const tile = el(
2714
+ "button",
2715
+ { className: "kit-gallery-tile", onClick },
2716
+ art,
2717
+ el(
2718
+ "span",
2719
+ "kit-gallery-tile-head",
2720
+ el("span", "kit-gallery-tile-name", item.name),
2721
+ item.badge !== void 0 ? el("span", "kit-gallery-badge", item.badge) : null
2722
+ ),
2723
+ el("span", "kit-gallery-tile-blurb", item.blurb)
2724
+ );
2725
+ tile.type = "button";
2726
+ return tile;
2727
+ }
2767
2728
  function openKitGallery(opts) {
2768
2729
  const frame = dialogFrame({ title: opts.title, ...opts.what !== void 0 ? { sub: opts.what } : {}, className: "kit-gallery", ...opts.onClose ? { onClose: opts.onClose } : {} });
2769
2730
  const { dialog } = frame;
@@ -2808,14 +2769,6 @@ function openKitGallery(opts) {
2808
2769
  primary.type = "button";
2809
2770
  const cancel = el("button", { className: "btn", text: "Cancel", onClick: () => frame.close() });
2810
2771
  cancel.type = "button";
2811
- const setArt = (host, image) => {
2812
- host.replaceChildren();
2813
- host.hidden = image === void 0;
2814
- if (image === void 0) return;
2815
- const ink = el("span", "kit-gallery-ink");
2816
- ink.style.setProperty("--kit-art", `url("${image.replace(/"/g, "%22")}")`);
2817
- host.append(ink);
2818
- };
2819
2772
  const setText = (node2, text) => {
2820
2773
  node2.textContent = text ?? "";
2821
2774
  node2.hidden = text === void 0 || text === "";
@@ -2823,21 +2776,7 @@ function openKitGallery(opts) {
2823
2776
  const tiles = /* @__PURE__ */ new Map();
2824
2777
  const shelves = opts.sections.map((section) => {
2825
2778
  const grid = el("div", "kit-gallery-grid", ...section.items.map((item) => {
2826
- const art = el("div", "kit-gallery-art");
2827
- setArt(art, item.image);
2828
- const tile = el(
2829
- "button",
2830
- { className: "kit-gallery-tile", onClick: () => choose(item.id) },
2831
- art,
2832
- el(
2833
- "span",
2834
- "kit-gallery-tile-head",
2835
- el("span", "kit-gallery-tile-name", item.name),
2836
- item.badge !== void 0 ? el("span", "kit-gallery-badge", item.badge) : null
2837
- ),
2838
- el("span", "kit-gallery-tile-blurb", item.blurb)
2839
- );
2840
- tile.type = "button";
2779
+ const tile = kitTile({ ...item, blurb: item.tile ?? item.blurb }, () => choose(item.id));
2841
2780
  tile.dataset.kit = item.id;
2842
2781
  tile.addEventListener("dblclick", () => {
2843
2782
  choose(item.id);
@@ -2890,7 +2829,11 @@ function openKitGallery(opts) {
2890
2829
  opts.onPick(item.id, name !== void 0 ? { name } : {});
2891
2830
  };
2892
2831
  if (nameInput) {
2893
- nameInput.addEventListener("input", () => nameInput.classList.remove("kit-gallery-missing"));
2832
+ nameInput.addEventListener("input", () => {
2833
+ nameInput.classList.remove("kit-gallery-missing");
2834
+ opts.onNameInput?.(nameInput.value);
2835
+ });
2836
+ opts.onNameInput?.("");
2894
2837
  nameInput.addEventListener("keydown", (e) => {
2895
2838
  e.stopPropagation();
2896
2839
  if (e.key !== "Enter") return;
@@ -2910,6 +2853,76 @@ function openKitGallery(opts) {
2910
2853
  return { dialog, close: () => frame.close() };
2911
2854
  }
2912
2855
 
2856
+ // src/welcome.ts
2857
+ function mountWelcome(host, opts) {
2858
+ const caption = (text) => el("h2", "welcome-caption", text);
2859
+ const group = (label2, ...body) => el("section", "welcome-group", caption(label2), ...body);
2860
+ const actions = el("div", "welcome-actions");
2861
+ for (const a of opts.actions) {
2862
+ const b = el("button", { className: `btn${a.primary ? " primary" : ""}`, text: a.label, onClick: a.onClick });
2863
+ b.type = "button";
2864
+ actions.append(b);
2865
+ }
2866
+ const groups = (opts.groups ?? []).map((g) => group(
2867
+ g.caption,
2868
+ g.note !== void 0 ? el("p", "welcome-note", g.note) : null,
2869
+ g.tiles ? el("div", "kit-gallery-grid welcome-tiles", ...g.items.map((item) => kitTile({
2870
+ name: item.name,
2871
+ blurb: item.hint ?? "",
2872
+ ...item.badge !== void 0 ? { badge: item.badge } : {},
2873
+ ...item.image !== void 0 ? { image: item.image } : {}
2874
+ }, item.onOpen))) : el("div", "welcome-list", ...g.items.map((item) => {
2875
+ const b = el(
2876
+ "button",
2877
+ { className: "welcome-item", onClick: item.onOpen },
2878
+ el("span", "welcome-item-name", item.name),
2879
+ item.hint !== void 0 ? el("span", "welcome-item-hint", item.hint) : null
2880
+ );
2881
+ b.type = "button";
2882
+ return b;
2883
+ }))
2884
+ ));
2885
+ const recentsHost = el("div", "welcome-recents-slot");
2886
+ const drawRecents = (recents) => {
2887
+ const shown = recents.slice(0, opts.maxRecents ?? 5);
2888
+ recentsHost.replaceChildren();
2889
+ if (shown.length === 0) return;
2890
+ recentsHost.append(group(
2891
+ opts.recentsCaption ?? "Recent",
2892
+ el("div", "welcome-recents", ...shown.map((r) => {
2893
+ const b = el(
2894
+ "button",
2895
+ { className: "welcome-recent", onClick: r.onOpen },
2896
+ el("span", "welcome-recent-name", r.name),
2897
+ el("span", "welcome-recent-path", r.path)
2898
+ );
2899
+ b.type = "button";
2900
+ return b;
2901
+ }))
2902
+ ));
2903
+ };
2904
+ drawRecents(opts.recents);
2905
+ const error = el("p", "welcome-error");
2906
+ const setError = (text) => {
2907
+ error.textContent = text ?? "";
2908
+ error.hidden = text === void 0 || text === "";
2909
+ };
2910
+ setError(opts.error);
2911
+ const root = el("div", "welcome", el(
2912
+ "div",
2913
+ "welcome-card",
2914
+ el("h1", "welcome-title", opts.title),
2915
+ el("p", "welcome-sub", opts.sub),
2916
+ actions,
2917
+ opts.tourLine ? el("div", "welcome-line", opts.tourLine) : null,
2918
+ ...groups,
2919
+ recentsHost,
2920
+ error
2921
+ ));
2922
+ host.replaceChildren(root);
2923
+ return { el: root, setRecents: drawRecents, setError };
2924
+ }
2925
+
2913
2926
  // src/link-status.ts
2914
2927
  function linkStatusClass(s) {
2915
2928
  if (s.state === "off" || s.state === "error") return "off";
@@ -3290,6 +3303,7 @@ exports.keyHint = keyHint;
3290
3303
  exports.keyLabel = keyLabel;
3291
3304
  exports.keyLegends = keyLegends;
3292
3305
  exports.keyPlatform = keyPlatform;
3306
+ exports.kitTile = kitTile;
3293
3307
  exports.labelled = labelled;
3294
3308
  exports.linkAddress = linkAddress;
3295
3309
  exports.linkStatusClass = linkStatusClass;