@wildwinter/app-shell 0.40.0 → 0.41.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 +44 -0
- package/dist/{chunk-DS4XXAZS.js → chunk-4OAORWEM.js} +2 -2
- package/dist/chunk-4OAORWEM.js.map +1 -0
- package/dist/chunk-BZ3MQDWM.js +153 -0
- package/dist/chunk-BZ3MQDWM.js.map +1 -0
- package/dist/{chunk-SDOKZTAR.js → chunk-G2JT3HZH.js} +15 -2
- package/dist/chunk-G2JT3HZH.js.map +1 -0
- package/dist/index.cjs +161 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/kit-gallery.cjs +369 -0
- package/dist/kit-gallery.cjs.map +1 -0
- package/dist/kit-gallery.css +179 -0
- package/dist/kit-gallery.css.map +1 -0
- package/dist/kit-gallery.d.cts +64 -0
- package/dist/kit-gallery.d.ts +64 -0
- package/dist/kit-gallery.js +6 -0
- package/dist/kit-gallery.js.map +1 -0
- package/dist/link-status.cjs +13 -0
- package/dist/link-status.cjs.map +1 -1
- package/dist/link-status.js +1 -1
- package/dist/problems.cjs.map +1 -1
- package/dist/problems.js +1 -1
- package/dist/stepper.css +1 -0
- package/dist/stepper.css.map +1 -1
- package/package.json +10 -1
- package/dist/chunk-DS4XXAZS.js.map +0 -1
- package/dist/chunk-SDOKZTAR.js.map +0 -1
package/README.md
CHANGED
|
@@ -131,12 +131,56 @@ needing and never had.
|
|
|
131
131
|
revealRow(document, "mood");
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
- **`--linkstatus-reserve`** - how much of the window's bottom-right corner the
|
|
135
|
+
live-link chip takes, published on the document while the chip is visible (`0px`
|
|
136
|
+
while hidden) and re-measured as its address comes and goes. The chip floats over
|
|
137
|
+
that corner, and both apps had things drawn under it. The stepper bar leaves the
|
|
138
|
+
room itself; a host strip that can end in the corner opts in with
|
|
139
|
+
`padding-right: calc(<its own> + var(--linkstatus-reserve, 0px))`.
|
|
140
|
+
|
|
134
141
|
- **`--ok`** joins `--danger` and `--warn` in `tokens.css`, with a `light-dark()`
|
|
135
142
|
default every palette should override in its own `theme.css`. It is the only
|
|
136
143
|
colour in the grammar layer, and it is there because it kept not being
|
|
137
144
|
anywhere: "all is well" was being met with raw green literals that could not
|
|
138
145
|
theme-shift.
|
|
139
146
|
|
|
147
|
+
## v0.41 (the kit gallery)
|
|
148
|
+
|
|
149
|
+
The new-thing moment (New Project, New Box, New Scene), drawn once: tiles on
|
|
150
|
+
the left, the chosen kit said in full on the right, one screen, on the dialog
|
|
151
|
+
frame. Shelves say what their button does, so a kit ("Create") and a worked
|
|
152
|
+
example ("Open a Copy") can sit side by side.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { openKitGallery } from "@wildwinter/app-shell";
|
|
156
|
+
import "@wildwinter/app-shell/kit-gallery.css";
|
|
157
|
+
|
|
158
|
+
openKitGallery({
|
|
159
|
+
title: "New project",
|
|
160
|
+
what: "A project is one game's worth of storylets.",
|
|
161
|
+
namePlaceholder: "The Village",
|
|
162
|
+
details: vcsFields, // the app's own fields, under the name
|
|
163
|
+
sections: [
|
|
164
|
+
{ caption: "Start from a kit", items: [
|
|
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"] },
|
|
167
|
+
] },
|
|
168
|
+
{ caption: "Learn from a finished project", action: "Open a Copy", usesDetails: false,
|
|
169
|
+
items: [{ id: "hamlet", name: "The Hamlet", blurb: "Small.", badge: "Start here" }] },
|
|
170
|
+
],
|
|
171
|
+
onPick: (id, { name }) => { /* the app makes it */ },
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- **What a kit is made of stays in the app.** The shell knows names, words and
|
|
176
|
+
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
|
+
- Single click chooses, double-click chooses and goes, Enter in the name field
|
|
182
|
+
creates; the name is required when asked for.
|
|
183
|
+
|
|
140
184
|
## Roadmap
|
|
141
185
|
|
|
142
186
|
Later slices lift the rest of the common shell from Patterpad (canonical),
|
|
@@ -52,5 +52,5 @@ var defaultProblemCopy = {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
export { defaultProblemCopy, describeProblem, problemLine, problemName };
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
56
|
-
//# sourceMappingURL=chunk-
|
|
55
|
+
//# sourceMappingURL=chunk-4OAORWEM.js.map
|
|
56
|
+
//# sourceMappingURL=chunk-4OAORWEM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/problems.ts"],"names":[],"mappings":"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","file":"chunk-4OAORWEM.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Problem copy: a validator's diagnostic rewritten for the person at the\n// keyboard, through a table the app fills.\n//\n// Patterpad's `humanizeProblem` (its renderer.ts) proved the shape: a dozen\n// validator categories each mapped to a plain sentence with a next step, naming\n// things by their title, and a fallback that softens the technical residue.\n// Storyletter showed the raw compiler string and labelled it `path [where]`\n// (ui-review-2026-09, finding 17), the bracketed id that \"Diagnostics name what\n// the author can see\" (design-language.md) rules out for anything but a\n// DANGLING reference. This module is the translator; the tables are the\n// apps', because the codes are theirs. `defaultProblemCopy` covers the handful\n// of codes both validators raise in some form, as a floor an app spreads into\n// its own table.\n// ---------------------------------------------------------------------------\n\n/** What a translator needs to know about a problem. `code` is the app's own\n * category (Patterpad's `detail`, or a code Storyletter derives from its\n * compiler message); `title` is the author-facing name of the thing the\n * problem is about, when the app can resolve one; `where` is the raw\n * reference (an id) for when it cannot. */\nexport interface ProblemLike {\n code?: string;\n message: string;\n path?: string;\n where?: string;\n title?: string;\n}\n\nexport interface ProblemCopy {\n /** The sentence: what is wrong, in the author's words. */\n text: string;\n /** What to do about it, when there is one thing to do. */\n next?: string;\n /** The reference this problem is about no longer has a name to give, so the\n * raw id may be shown, written `(id ...)` so it cannot be mistaken for a\n * name. Set by the table entry; the translator never guesses it. */\n dangling?: boolean;\n}\n\nexport type ProblemCopyEntry = (problem: ProblemLike) => ProblemCopy;\nexport type ProblemCopyTable = Record<string, ProblemCopyEntry>;\n\n/** The name to call the thing a problem is about: its title in curly quotes,\n * \"(id ...)\" only when the reference dangles and there is nothing else,\n * and \"This\" otherwise, because the bar's \"go to\" shows the reader what\n * \"this\" is and an id is not something they can look for. */\nexport function problemName(p: ProblemLike, dangling = false): string {\n if (p.title) return `“${p.title}”`;\n if (dangling && p.where) return `(id ${p.where})`;\n return \"This\";\n}\n\n/**\n * Translate `problem` through `table`. A matching entry decides the copy; with\n * no entry the raw message is kept and prefixed by the title when there is one\n * (\"Burner Rig: duplicate id\"). Nothing here ever writes `[where]`; an id\n * appears only as `(id ...)`, only when the entry says the reference dangles,\n * and only when there is no title to say instead.\n */\nexport function describeProblem(problem: ProblemLike, table: ProblemCopyTable = {}): ProblemCopy {\n const entry = problem.code ? table[problem.code] : undefined;\n if (entry) {\n const copy = entry(problem);\n const text = copy.dangling && !problem.title && problem.where && !copy.text.includes(`(id ${problem.where})`)\n ? `${copy.text} (id ${problem.where})`\n : copy.text;\n return copy.next ? { text, next: copy.next } : { text };\n }\n const message = problem.message.replace(/\\s*\\[[^\\]]*\\]\\s*$/, \"\").trim();\n return { text: problem.title ? `${problem.title}: ${message}` : message };\n}\n\n/** `describeProblem` as one line for a bar that has room for one: the sentence,\n * then the next step after a space. */\nexport function problemLine(problem: ProblemLike, table?: ProblemCopyTable): string {\n const { text, next } = describeProblem(problem, table);\n return next ? `${text} ${next}` : text;\n}\n\n/**\n * The codes both validators raise in some form, in the family's voice. An app\n * spreads this into its own table and adds its own codes over it; a code an\n * app does not raise costs nothing.\n *\n * duplicate-gameid two things share one Game ID\n * invalid-gameid a Game ID that breaks the address rule\n * dangling-reference a jump / tag / property points at something gone\n * unknown-property a condition or effect names an undeclared property\n * missing-name a thing that needs a name has none\n * empty a container with nothing in it\n * stale-build the playable build is older than the source\n * merge-conflict a file still carries conflict markers\n */\nexport const defaultProblemCopy: ProblemCopyTable = {\n \"duplicate-gameid\": (p) => ({\n text: `The Game ID on ${problemName(p)} is already used elsewhere.`,\n next: \"Each one must be unique; change one of them.\",\n }),\n \"invalid-gameid\": (p) => ({\n text: `The Game ID on ${problemName(p)} isn't valid.`,\n next: \"Use lowercase letters, digits and hyphens.\",\n }),\n \"dangling-reference\": (p) => ({\n text: `${problemName(p, true)} points at something that no longer exists.`,\n next: \"Choose where it goes.\",\n dangling: true,\n }),\n \"unknown-property\": (p) => ({\n text: `${problemName(p)} uses a property that isn't set up yet.`,\n next: \"Declare it in the project settings, or fix the name.\",\n }),\n \"missing-name\": (p) => ({ text: `${problemName(p)} needs a name.` }),\n \"empty\": (p) => ({\n text: `${problemName(p)} is empty.`,\n next: \"Add something inside it, or remove it.\",\n }),\n \"stale-build\": (p) => ({\n text: `Your playable build${p.path ? ` (${p.path})` : \"\"} is out of date.`,\n next: \"It refreshes the next time you publish.\",\n }),\n \"merge-conflict\": (p) => ({\n text: `${p.path ?? \"This file\"} still has an unresolved merge conflict in it.`,\n next: \"Resolve it in your version control tool, then reopen the project.\",\n }),\n};\n"]}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { dialogFrame } from './chunk-YRFJLJUH.js';
|
|
2
|
+
import { el } from './chunk-UG2P777F.js';
|
|
3
|
+
|
|
4
|
+
// src/kit-gallery.ts
|
|
5
|
+
function openKitGallery(opts) {
|
|
6
|
+
const frame = dialogFrame({ title: opts.title, ...opts.what !== void 0 ? { sub: opts.what } : {}, className: "kit-gallery", ...opts.onClose ? { onClose: opts.onClose } : {} });
|
|
7
|
+
const { dialog } = frame;
|
|
8
|
+
const entries = opts.sections.flatMap((section, s) => section.items.map((item) => ({ item, section, s })));
|
|
9
|
+
if (entries.length === 0) throw new Error("openKitGallery: no items to choose from");
|
|
10
|
+
let chosen = entries.find((e) => e.item.id === opts.initial) ?? entries[0];
|
|
11
|
+
const nameInput = opts.namePlaceholder === void 0 ? void 0 : el("input", { className: "field kit-gallery-name" });
|
|
12
|
+
if (nameInput) {
|
|
13
|
+
nameInput.placeholder = opts.namePlaceholder;
|
|
14
|
+
nameInput.setAttribute("aria-label", opts.nameLabel ?? "Name");
|
|
15
|
+
}
|
|
16
|
+
const fields = el(
|
|
17
|
+
"div",
|
|
18
|
+
"kit-gallery-fields",
|
|
19
|
+
...nameInput ? [el("label", "kit-gallery-label", opts.nameLabel ?? "Name"), nameInput] : [],
|
|
20
|
+
...opts.details ? [opts.details] : []
|
|
21
|
+
);
|
|
22
|
+
const panelArt = el("div", "kit-gallery-art kit-gallery-panel-art");
|
|
23
|
+
const panelName = el("h3", "kit-gallery-panel-name");
|
|
24
|
+
const panelBlurb = el("p", "kit-gallery-panel-blurb");
|
|
25
|
+
const panelDetail = el("p", "kit-gallery-panel-detail");
|
|
26
|
+
const panelPlay = el("p", "kit-gallery-play");
|
|
27
|
+
const panelShows = el("p", "kit-gallery-shows");
|
|
28
|
+
const panelLands = el("ul", "kit-gallery-lands");
|
|
29
|
+
const panelLandsCaption = el("p", "kit-gallery-lands-caption", "What you get");
|
|
30
|
+
const panelNote = el("p", "kit-gallery-note");
|
|
31
|
+
const panel = el(
|
|
32
|
+
"div",
|
|
33
|
+
"kit-gallery-panel",
|
|
34
|
+
panelArt,
|
|
35
|
+
panelName,
|
|
36
|
+
panelBlurb,
|
|
37
|
+
panelDetail,
|
|
38
|
+
panelPlay,
|
|
39
|
+
panelShows,
|
|
40
|
+
panelLandsCaption,
|
|
41
|
+
panelLands,
|
|
42
|
+
panelNote,
|
|
43
|
+
fields
|
|
44
|
+
);
|
|
45
|
+
const primary = el("button", { className: "btn primary", onClick: () => commit() });
|
|
46
|
+
primary.type = "button";
|
|
47
|
+
const cancel = el("button", { className: "btn", text: "Cancel", onClick: () => frame.close() });
|
|
48
|
+
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
|
+
const setText = (node, text) => {
|
|
58
|
+
node.textContent = text ?? "";
|
|
59
|
+
node.hidden = text === void 0 || text === "";
|
|
60
|
+
};
|
|
61
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
62
|
+
const shelves = opts.sections.map((section) => {
|
|
63
|
+
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";
|
|
79
|
+
tile.dataset.kit = item.id;
|
|
80
|
+
tile.addEventListener("dblclick", () => {
|
|
81
|
+
choose(item.id);
|
|
82
|
+
commit();
|
|
83
|
+
});
|
|
84
|
+
tiles.set(item.id, tile);
|
|
85
|
+
return tile;
|
|
86
|
+
}));
|
|
87
|
+
return el(
|
|
88
|
+
"section",
|
|
89
|
+
"kit-gallery-shelf",
|
|
90
|
+
section.caption !== void 0 ? el("h3", "kit-gallery-caption", section.caption) : null,
|
|
91
|
+
section.note !== void 0 ? el("p", "kit-gallery-shelf-note", section.note) : null,
|
|
92
|
+
grid
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
const choose = (id) => {
|
|
96
|
+
const next = entries.find((e) => e.item.id === id);
|
|
97
|
+
if (!next) return;
|
|
98
|
+
chosen = next;
|
|
99
|
+
for (const [tid, tile] of tiles) tile.setAttribute("aria-pressed", String(tid === id));
|
|
100
|
+
const { item, section } = chosen;
|
|
101
|
+
setArt(panelArt, item.image);
|
|
102
|
+
panelName.textContent = item.name;
|
|
103
|
+
setText(panelBlurb, item.blurb);
|
|
104
|
+
setText(panelDetail, item.detail);
|
|
105
|
+
setText(panelPlay, item.play);
|
|
106
|
+
setText(panelShows, item.shows !== void 0 ? `Shows: ${item.shows}` : void 0);
|
|
107
|
+
panelLands.replaceChildren(...(item.lands ?? []).map((l) => el("li", void 0, l)));
|
|
108
|
+
panelLands.hidden = (item.lands ?? []).length === 0;
|
|
109
|
+
panelLandsCaption.hidden = panelLands.hidden;
|
|
110
|
+
const details = section.usesDetails !== false;
|
|
111
|
+
fields.hidden = !details || nameInput === void 0 && opts.details === void 0;
|
|
112
|
+
setText(panelNote, details ? void 0 : section.note);
|
|
113
|
+
primary.textContent = section.action ?? "Create";
|
|
114
|
+
};
|
|
115
|
+
const commit = () => {
|
|
116
|
+
const { item, section } = chosen;
|
|
117
|
+
const details = section.usesDetails !== false;
|
|
118
|
+
let name;
|
|
119
|
+
if (details && nameInput) {
|
|
120
|
+
name = nameInput.value.trim();
|
|
121
|
+
if (name === "") {
|
|
122
|
+
nameInput.focus();
|
|
123
|
+
nameInput.classList.add("kit-gallery-missing");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
frame.close();
|
|
128
|
+
opts.onPick(item.id, name !== void 0 ? { name } : {});
|
|
129
|
+
};
|
|
130
|
+
if (nameInput) {
|
|
131
|
+
nameInput.addEventListener("input", () => nameInput.classList.remove("kit-gallery-missing"));
|
|
132
|
+
nameInput.addEventListener("keydown", (e) => {
|
|
133
|
+
e.stopPropagation();
|
|
134
|
+
if (e.key !== "Enter") return;
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
commit();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
frame.body.append(el("div", "kit-gallery-layout", el("div", "kit-gallery-shelves", ...shelves), panel));
|
|
140
|
+
frame.actions.append(cancel, primary);
|
|
141
|
+
dialog.addEventListener("click", (e) => {
|
|
142
|
+
if (e.target === dialog) frame.close();
|
|
143
|
+
});
|
|
144
|
+
choose(chosen.item.id);
|
|
145
|
+
frame.open();
|
|
146
|
+
if (nameInput && chosen.section.usesDetails !== false) nameInput.focus();
|
|
147
|
+
else tiles.get(chosen.item.id)?.focus();
|
|
148
|
+
return { dialog, close: () => frame.close() };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { openKitGallery };
|
|
152
|
+
//# sourceMappingURL=chunk-BZ3MQDWM.js.map
|
|
153
|
+
//# sourceMappingURL=chunk-BZ3MQDWM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/kit-gallery.ts"],"names":[],"mappings":"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","file":"chunk-BZ3MQDWM.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: one line on the tile, and the panel's first line. */\n blurb: 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 /** 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/** 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 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 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 art = el(\"div\", \"kit-gallery-art\");\n setArt(art, item.image);\n const tile = el(\"button\", { className: \"kit-gallery-tile\", onClick: () => choose(item.id) },\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 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\", () => nameInput.classList.remove(\"kit-gallery-missing\"));\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"]}
|
|
@@ -27,6 +27,8 @@ function linkAddress(s) {
|
|
|
27
27
|
if (s.state !== "listening" && s.state !== "connected") return void 0;
|
|
28
28
|
return s.address ?? `ws://127.0.0.1:${s.port}`;
|
|
29
29
|
}
|
|
30
|
+
var CORNER_INSET = 14.5;
|
|
31
|
+
var CORNER_GAP = 12;
|
|
30
32
|
function mountLinkStatus(host, opts) {
|
|
31
33
|
const wrap = el("div", "linkstatus");
|
|
32
34
|
wrap.hidden = true;
|
|
@@ -40,6 +42,15 @@ function mountLinkStatus(host, opts) {
|
|
|
40
42
|
wrap.append(...opts.lead ?? [], url, toggle);
|
|
41
43
|
host.append(wrap);
|
|
42
44
|
let status = { state: "off" };
|
|
45
|
+
const root = host.ownerDocument.documentElement;
|
|
46
|
+
const publishReserve = () => {
|
|
47
|
+
const width = wrap.hidden ? 0 : wrap.getBoundingClientRect().width;
|
|
48
|
+
root.style.setProperty(
|
|
49
|
+
"--linkstatus-reserve",
|
|
50
|
+
width > 0 ? `${Math.ceil(width + CORNER_INSET + CORNER_GAP)}px` : "0px"
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
if (typeof ResizeObserver !== "undefined") new ResizeObserver(publishReserve).observe(wrap);
|
|
43
54
|
const render = () => {
|
|
44
55
|
toggle.className = `linkstatus-toggle ${linkStatusClass(status)}`;
|
|
45
56
|
const tip = linkStatusTip(status);
|
|
@@ -51,6 +62,7 @@ function mountLinkStatus(host, opts) {
|
|
|
51
62
|
url.dataset["tip"] = "Click to copy the live link address";
|
|
52
63
|
url.hidden = false;
|
|
53
64
|
} else url.hidden = true;
|
|
65
|
+
publishReserve();
|
|
54
66
|
};
|
|
55
67
|
const apply = (s) => {
|
|
56
68
|
status = s;
|
|
@@ -75,6 +87,7 @@ function mountLinkStatus(host, opts) {
|
|
|
75
87
|
status: () => status,
|
|
76
88
|
setVisible(on) {
|
|
77
89
|
wrap.hidden = !on;
|
|
90
|
+
publishReserve();
|
|
78
91
|
},
|
|
79
92
|
toggle() {
|
|
80
93
|
toggle.click();
|
|
@@ -83,5 +96,5 @@ function mountLinkStatus(host, opts) {
|
|
|
83
96
|
}
|
|
84
97
|
|
|
85
98
|
export { linkAddress, linkStatusClass, linkStatusTip, mountLinkStatus };
|
|
86
|
-
//# sourceMappingURL=chunk-
|
|
87
|
-
//# sourceMappingURL=chunk-
|
|
99
|
+
//# sourceMappingURL=chunk-G2JT3HZH.js.map
|
|
100
|
+
//# sourceMappingURL=chunk-G2JT3HZH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/link-status.ts"],"names":[],"mappings":"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","file":"chunk-G2JT3HZH.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The live-link status chip: a compact control in the bottom-right corner for\n// the loopback server a running game connects to. All the state collapses to\n// one coloured \"connect\" icon (grey off, amber listening, green connected and\n// in sync, red connected on a different build); click it to toggle the link\n// (off -> listening -> stop), and while the link is up the copiable ws://\n// address sits beside it. Patterpad's debug-panel.ts and Storyletter's\n// live-link.ts were this file twice, character for character down to the\n// 1000ms `.copied` revert. Styles ship as link-status.css.\n//\n// What the game sends is the app's business; this is the control and the\n// status only.\n// ---------------------------------------------------------------------------\n\nimport { el } from \"./dom.js\";\nimport { iconNode } from \"./icons.js\";\nimport { copyWithFeedback } from \"./util.js\";\n\nexport type LinkStatus =\n | { state: \"off\" }\n | { state: \"error\"; message: string }\n | { state: \"listening\"; port: number; address?: string }\n | {\n state: \"connected\"; port: number; address?: string;\n /** What connected, if it said. */\n project?: string;\n /** Whether the game's build matches the editor's. */\n build?: \"match\" | \"stale\" | \"unknown\";\n /** One extra sentence for the tip (Storyletter names the live boxes). */\n note?: string;\n };\n\n/** The chip's colour class for a state: the four. */\nexport type LinkStatusClass = \"off\" | \"listening\" | \"live\" | \"stale\";\n\nexport interface LinkStatusOptions {\n /** A word beside the chip (\"Live link\"), so a bottom-corner plug is not a\n * mystery. Omitted draws none. */\n label?: string;\n /** Start when off or failed, stop otherwise. A returned status is applied;\n * a status pushed later through `apply` works as well. */\n onToggle: (current: LinkStatus) => LinkStatus | void | Promise<LinkStatus | void>;\n /** Runs after the address is copied. The copy itself is the chip's. */\n onCopy?: (address: string) => void;\n /** Controls before the address (Patterpad's flow picker). */\n lead?: HTMLElement[];\n}\n\nexport interface LinkStatusChip {\n el: HTMLElement;\n apply(status: LinkStatus): void;\n status(): LinkStatus;\n /** Show / hide the control (shown when a project is open). */\n setVisible(on: boolean): void;\n /** Toggle the link (a menu item routes here). */\n toggle(): void;\n}\n\nexport function linkStatusClass(s: LinkStatus): LinkStatusClass {\n if (s.state === \"off\" || s.state === \"error\") return \"off\";\n if (s.state === \"listening\") return \"listening\";\n return s.build === \"stale\" ? \"stale\" : \"live\";\n}\n\n/** The tooltip, spelling the state out. One wording for the family. */\nexport function linkStatusTip(s: LinkStatus): string {\n switch (s.state) {\n case \"off\": return \"Live link is off. Click to start listening.\";\n case \"error\": return `Live link failed (${s.message}). Click to retry.`;\n case \"listening\": return \"Live link is listening for a game. Click to stop.\";\n case \"connected\": {\n const who = s.project !== undefined ? ` to ${s.project}` : \"\";\n const build = s.build === \"stale\" ? \" on a different build, so save or rebuild to re-sync\"\n : s.build === \"match\" ? \" and in sync\" : \"\";\n const note = s.note ? ` ${s.note}` : \"\";\n return `Live link is connected${who}${build}.${note} Click to stop.`;\n }\n }\n}\n\n/** The address a game connects to. */\nexport function linkAddress(s: LinkStatus): string | undefined {\n if (s.state !== \"listening\" && s.state !== \"connected\") return undefined;\n return s.address ?? `ws://127.0.0.1:${s.port}`;\n}\n\n/** Where the chip sits, which link-status.css says too: its right inset, and the\n * breathing room anything beside it keeps. Kept beside the reserve that uses\n * them, since the reserve is a promise about the CSS. */\nconst CORNER_INSET = 14.5;\nconst CORNER_GAP = 12;\n\n/** Mount the chip into `host` (document.body in both apps: it is fixed to the\n * window's corner). Hidden until `setVisible(true)`. */\nexport function mountLinkStatus(host: HTMLElement, opts: LinkStatusOptions): LinkStatusChip {\n const wrap = el(\"div\", \"linkstatus\"); wrap.hidden = true;\n const url = el(\"button\", \"linkstatus-url\"); url.type = \"button\"; url.hidden = true;\n const toggle = el(\"button\", \"linkstatus-toggle off\"); toggle.type = \"button\"; toggle.append(iconNode(\"connect\"));\n if (opts.label !== undefined) wrap.append(el(\"span\", \"linkstatus-word\", opts.label));\n wrap.append(...(opts.lead ?? []), url, toggle);\n host.append(wrap);\n\n let status: LinkStatus = { state: \"off\" };\n\n // The corner the chip floats over belongs to whatever the host put there: a\n // problems bar's error link and its buttons, a canvas strip's last control.\n // Both apps had it drawn under the chip (2026-09-17). So the chip publishes\n // how much of the corner it takes, as --linkstatus-reserve on the document,\n // and the things that can sit there leave that much room (the stepper bar\n // does, in stepper.css; a host's own strips opt in). 0px while hidden.\n // Re-measured on every render, because the address comes and goes, and on a\n // resize where the platform offers one, because a web font landing moves it.\n const root = host.ownerDocument.documentElement;\n const publishReserve = (): void => {\n const width = wrap.hidden ? 0 : wrap.getBoundingClientRect().width;\n root.style.setProperty(\"--linkstatus-reserve\",\n width > 0 ? `${Math.ceil(width + CORNER_INSET + CORNER_GAP)}px` : \"0px\");\n };\n if (typeof ResizeObserver !== \"undefined\") new ResizeObserver(publishReserve).observe(wrap);\n\n const render = (): void => {\n toggle.className = `linkstatus-toggle ${linkStatusClass(status)}`;\n const tip = linkStatusTip(status);\n toggle.dataset[\"tip\"] = tip;\n toggle.setAttribute(\"aria-label\", tip);\n // The copiable address means something once the server is up.\n const addr = linkAddress(status);\n if (addr !== undefined) {\n url.textContent = addr;\n url.dataset[\"tip\"] = \"Click to copy the live link address\";\n url.hidden = false;\n } else url.hidden = true;\n publishReserve();\n };\n const apply = (s: LinkStatus): void => { status = s; render(); };\n render();\n\n toggle.addEventListener(\"click\", () => {\n const out = opts.onToggle(status);\n void Promise.resolve(out).then((next) => { if (next) apply(next); });\n });\n url.addEventListener(\"click\", () => {\n const addr = url.textContent ?? \"\";\n if (!addr) return;\n void copyWithFeedback(url, addr);\n opts.onCopy?.(addr);\n });\n\n return {\n el: wrap, apply,\n status: () => status,\n setVisible(on: boolean): void { wrap.hidden = !on; publishReserve(); },\n toggle(): void { toggle.click(); },\n };\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -2763,6 +2763,153 @@ function mountWelcome(host, opts) {
|
|
|
2763
2763
|
return { el: root, setRecents: drawRecents, setError };
|
|
2764
2764
|
}
|
|
2765
2765
|
|
|
2766
|
+
// src/kit-gallery.ts
|
|
2767
|
+
function openKitGallery(opts) {
|
|
2768
|
+
const frame = dialogFrame({ title: opts.title, ...opts.what !== void 0 ? { sub: opts.what } : {}, className: "kit-gallery", ...opts.onClose ? { onClose: opts.onClose } : {} });
|
|
2769
|
+
const { dialog } = frame;
|
|
2770
|
+
const entries = opts.sections.flatMap((section, s) => section.items.map((item) => ({ item, section, s })));
|
|
2771
|
+
if (entries.length === 0) throw new Error("openKitGallery: no items to choose from");
|
|
2772
|
+
let chosen = entries.find((e) => e.item.id === opts.initial) ?? entries[0];
|
|
2773
|
+
const nameInput = opts.namePlaceholder === void 0 ? void 0 : el("input", { className: "field kit-gallery-name" });
|
|
2774
|
+
if (nameInput) {
|
|
2775
|
+
nameInput.placeholder = opts.namePlaceholder;
|
|
2776
|
+
nameInput.setAttribute("aria-label", opts.nameLabel ?? "Name");
|
|
2777
|
+
}
|
|
2778
|
+
const fields = el(
|
|
2779
|
+
"div",
|
|
2780
|
+
"kit-gallery-fields",
|
|
2781
|
+
...nameInput ? [el("label", "kit-gallery-label", opts.nameLabel ?? "Name"), nameInput] : [],
|
|
2782
|
+
...opts.details ? [opts.details] : []
|
|
2783
|
+
);
|
|
2784
|
+
const panelArt = el("div", "kit-gallery-art kit-gallery-panel-art");
|
|
2785
|
+
const panelName = el("h3", "kit-gallery-panel-name");
|
|
2786
|
+
const panelBlurb = el("p", "kit-gallery-panel-blurb");
|
|
2787
|
+
const panelDetail = el("p", "kit-gallery-panel-detail");
|
|
2788
|
+
const panelPlay = el("p", "kit-gallery-play");
|
|
2789
|
+
const panelShows = el("p", "kit-gallery-shows");
|
|
2790
|
+
const panelLands = el("ul", "kit-gallery-lands");
|
|
2791
|
+
const panelLandsCaption = el("p", "kit-gallery-lands-caption", "What you get");
|
|
2792
|
+
const panelNote = el("p", "kit-gallery-note");
|
|
2793
|
+
const panel = el(
|
|
2794
|
+
"div",
|
|
2795
|
+
"kit-gallery-panel",
|
|
2796
|
+
panelArt,
|
|
2797
|
+
panelName,
|
|
2798
|
+
panelBlurb,
|
|
2799
|
+
panelDetail,
|
|
2800
|
+
panelPlay,
|
|
2801
|
+
panelShows,
|
|
2802
|
+
panelLandsCaption,
|
|
2803
|
+
panelLands,
|
|
2804
|
+
panelNote,
|
|
2805
|
+
fields
|
|
2806
|
+
);
|
|
2807
|
+
const primary = el("button", { className: "btn primary", onClick: () => commit() });
|
|
2808
|
+
primary.type = "button";
|
|
2809
|
+
const cancel = el("button", { className: "btn", text: "Cancel", onClick: () => frame.close() });
|
|
2810
|
+
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
|
+
const setText = (node2, text) => {
|
|
2820
|
+
node2.textContent = text ?? "";
|
|
2821
|
+
node2.hidden = text === void 0 || text === "";
|
|
2822
|
+
};
|
|
2823
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
2824
|
+
const shelves = opts.sections.map((section) => {
|
|
2825
|
+
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";
|
|
2841
|
+
tile.dataset.kit = item.id;
|
|
2842
|
+
tile.addEventListener("dblclick", () => {
|
|
2843
|
+
choose(item.id);
|
|
2844
|
+
commit();
|
|
2845
|
+
});
|
|
2846
|
+
tiles.set(item.id, tile);
|
|
2847
|
+
return tile;
|
|
2848
|
+
}));
|
|
2849
|
+
return el(
|
|
2850
|
+
"section",
|
|
2851
|
+
"kit-gallery-shelf",
|
|
2852
|
+
section.caption !== void 0 ? el("h3", "kit-gallery-caption", section.caption) : null,
|
|
2853
|
+
section.note !== void 0 ? el("p", "kit-gallery-shelf-note", section.note) : null,
|
|
2854
|
+
grid
|
|
2855
|
+
);
|
|
2856
|
+
});
|
|
2857
|
+
const choose = (id) => {
|
|
2858
|
+
const next = entries.find((e) => e.item.id === id);
|
|
2859
|
+
if (!next) return;
|
|
2860
|
+
chosen = next;
|
|
2861
|
+
for (const [tid, tile] of tiles) tile.setAttribute("aria-pressed", String(tid === id));
|
|
2862
|
+
const { item, section } = chosen;
|
|
2863
|
+
setArt(panelArt, item.image);
|
|
2864
|
+
panelName.textContent = item.name;
|
|
2865
|
+
setText(panelBlurb, item.blurb);
|
|
2866
|
+
setText(panelDetail, item.detail);
|
|
2867
|
+
setText(panelPlay, item.play);
|
|
2868
|
+
setText(panelShows, item.shows !== void 0 ? `Shows: ${item.shows}` : void 0);
|
|
2869
|
+
panelLands.replaceChildren(...(item.lands ?? []).map((l) => el("li", void 0, l)));
|
|
2870
|
+
panelLands.hidden = (item.lands ?? []).length === 0;
|
|
2871
|
+
panelLandsCaption.hidden = panelLands.hidden;
|
|
2872
|
+
const details = section.usesDetails !== false;
|
|
2873
|
+
fields.hidden = !details || nameInput === void 0 && opts.details === void 0;
|
|
2874
|
+
setText(panelNote, details ? void 0 : section.note);
|
|
2875
|
+
primary.textContent = section.action ?? "Create";
|
|
2876
|
+
};
|
|
2877
|
+
const commit = () => {
|
|
2878
|
+
const { item, section } = chosen;
|
|
2879
|
+
const details = section.usesDetails !== false;
|
|
2880
|
+
let name;
|
|
2881
|
+
if (details && nameInput) {
|
|
2882
|
+
name = nameInput.value.trim();
|
|
2883
|
+
if (name === "") {
|
|
2884
|
+
nameInput.focus();
|
|
2885
|
+
nameInput.classList.add("kit-gallery-missing");
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
frame.close();
|
|
2890
|
+
opts.onPick(item.id, name !== void 0 ? { name } : {});
|
|
2891
|
+
};
|
|
2892
|
+
if (nameInput) {
|
|
2893
|
+
nameInput.addEventListener("input", () => nameInput.classList.remove("kit-gallery-missing"));
|
|
2894
|
+
nameInput.addEventListener("keydown", (e) => {
|
|
2895
|
+
e.stopPropagation();
|
|
2896
|
+
if (e.key !== "Enter") return;
|
|
2897
|
+
e.preventDefault();
|
|
2898
|
+
commit();
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
frame.body.append(el("div", "kit-gallery-layout", el("div", "kit-gallery-shelves", ...shelves), panel));
|
|
2902
|
+
frame.actions.append(cancel, primary);
|
|
2903
|
+
dialog.addEventListener("click", (e) => {
|
|
2904
|
+
if (e.target === dialog) frame.close();
|
|
2905
|
+
});
|
|
2906
|
+
choose(chosen.item.id);
|
|
2907
|
+
frame.open();
|
|
2908
|
+
if (nameInput && chosen.section.usesDetails !== false) nameInput.focus();
|
|
2909
|
+
else tiles.get(chosen.item.id)?.focus();
|
|
2910
|
+
return { dialog, close: () => frame.close() };
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2766
2913
|
// src/link-status.ts
|
|
2767
2914
|
function linkStatusClass(s) {
|
|
2768
2915
|
if (s.state === "off" || s.state === "error") return "off";
|
|
@@ -2789,6 +2936,8 @@ function linkAddress(s) {
|
|
|
2789
2936
|
if (s.state !== "listening" && s.state !== "connected") return void 0;
|
|
2790
2937
|
return s.address ?? `ws://127.0.0.1:${s.port}`;
|
|
2791
2938
|
}
|
|
2939
|
+
var CORNER_INSET = 14.5;
|
|
2940
|
+
var CORNER_GAP = 12;
|
|
2792
2941
|
function mountLinkStatus(host, opts) {
|
|
2793
2942
|
const wrap = el("div", "linkstatus");
|
|
2794
2943
|
wrap.hidden = true;
|
|
@@ -2802,6 +2951,15 @@ function mountLinkStatus(host, opts) {
|
|
|
2802
2951
|
wrap.append(...opts.lead ?? [], url, toggle);
|
|
2803
2952
|
host.append(wrap);
|
|
2804
2953
|
let status = { state: "off" };
|
|
2954
|
+
const root = host.ownerDocument.documentElement;
|
|
2955
|
+
const publishReserve = () => {
|
|
2956
|
+
const width = wrap.hidden ? 0 : wrap.getBoundingClientRect().width;
|
|
2957
|
+
root.style.setProperty(
|
|
2958
|
+
"--linkstatus-reserve",
|
|
2959
|
+
width > 0 ? `${Math.ceil(width + CORNER_INSET + CORNER_GAP)}px` : "0px"
|
|
2960
|
+
);
|
|
2961
|
+
};
|
|
2962
|
+
if (typeof ResizeObserver !== "undefined") new ResizeObserver(publishReserve).observe(wrap);
|
|
2805
2963
|
const render = () => {
|
|
2806
2964
|
toggle.className = `linkstatus-toggle ${linkStatusClass(status)}`;
|
|
2807
2965
|
const tip2 = linkStatusTip(status);
|
|
@@ -2813,6 +2971,7 @@ function mountLinkStatus(host, opts) {
|
|
|
2813
2971
|
url.dataset["tip"] = "Click to copy the live link address";
|
|
2814
2972
|
url.hidden = false;
|
|
2815
2973
|
} else url.hidden = true;
|
|
2974
|
+
publishReserve();
|
|
2816
2975
|
};
|
|
2817
2976
|
const apply = (s) => {
|
|
2818
2977
|
status = s;
|
|
@@ -2837,6 +2996,7 @@ function mountLinkStatus(host, opts) {
|
|
|
2837
2996
|
status: () => status,
|
|
2838
2997
|
setVisible(on) {
|
|
2839
2998
|
wrap.hidden = !on;
|
|
2999
|
+
publishReserve();
|
|
2840
3000
|
},
|
|
2841
3001
|
toggle() {
|
|
2842
3002
|
toggle.click();
|
|
@@ -3148,6 +3308,7 @@ exports.openAnchoredPanel = openAnchoredPanel;
|
|
|
3148
3308
|
exports.openComments = openComments;
|
|
3149
3309
|
exports.openContextMenu = openContextMenu;
|
|
3150
3310
|
exports.openGameIdEditor = openGameIdEditor;
|
|
3311
|
+
exports.openKitGallery = openKitGallery;
|
|
3151
3312
|
exports.openNotesEditor = openNotesEditor;
|
|
3152
3313
|
exports.openPopover = openPopover;
|
|
3153
3314
|
exports.paintVcBadges = paintVcBadges;
|