@wildwinter/app-shell 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/LICENSES.md +62 -0
  2. package/README.md +8 -0
  3. package/dist/about.css +17 -38
  4. package/dist/about.css.map +1 -1
  5. package/dist/anchored.css +9 -9
  6. package/dist/anchored.css.map +1 -1
  7. package/dist/app-store.cjs +15 -0
  8. package/dist/app-store.cjs.map +1 -1
  9. package/dist/app-store.d.cts +25 -1
  10. package/dist/app-store.d.ts +25 -1
  11. package/dist/app-store.js +14 -1
  12. package/dist/app-store.js.map +1 -1
  13. package/dist/chunk-2ECJUTSX.js +97 -0
  14. package/dist/chunk-2ECJUTSX.js.map +1 -0
  15. package/dist/chunk-DS4XXAZS.js +56 -0
  16. package/dist/chunk-DS4XXAZS.js.map +1 -0
  17. package/dist/chunk-EHR2KBZC.js +82 -0
  18. package/dist/chunk-EHR2KBZC.js.map +1 -0
  19. package/dist/chunk-IU7EY6BI.js +26 -0
  20. package/dist/chunk-IU7EY6BI.js.map +1 -0
  21. package/dist/chunk-OPZPFOPS.js +66 -0
  22. package/dist/chunk-OPZPFOPS.js.map +1 -0
  23. package/dist/chunk-P4G4ERG6.js +60 -0
  24. package/dist/chunk-P4G4ERG6.js.map +1 -0
  25. package/dist/chunk-P6WLAEKZ.js +71 -0
  26. package/dist/chunk-P6WLAEKZ.js.map +1 -0
  27. package/dist/chunk-R3NXPHRZ.js +534 -0
  28. package/dist/chunk-R3NXPHRZ.js.map +1 -0
  29. package/dist/chunk-SDOKZTAR.js +87 -0
  30. package/dist/chunk-SDOKZTAR.js.map +1 -0
  31. package/dist/chunk-SEWJLUOD.js +70 -0
  32. package/dist/chunk-SEWJLUOD.js.map +1 -0
  33. package/dist/chunk-UG2P777F.js +587 -0
  34. package/dist/chunk-UG2P777F.js.map +1 -0
  35. package/dist/chunk-YRFJLJUH.js +55 -0
  36. package/dist/chunk-YRFJLJUH.js.map +1 -0
  37. package/dist/chunk-YWMSQFXX.js +146 -0
  38. package/dist/chunk-YWMSQFXX.js.map +1 -0
  39. package/dist/comments.css +16 -14
  40. package/dist/comments.css.map +1 -1
  41. package/dist/confirm.css +2 -55
  42. package/dist/confirm.css.map +1 -1
  43. package/dist/context-menu.cjs +250 -0
  44. package/dist/context-menu.cjs.map +1 -0
  45. package/dist/context-menu.css +64 -0
  46. package/dist/context-menu.css.map +1 -0
  47. package/dist/context-menu.d.cts +25 -0
  48. package/dist/context-menu.d.ts +25 -0
  49. package/dist/context-menu.js +5 -0
  50. package/dist/context-menu.js.map +1 -0
  51. package/dist/controls.css +214 -0
  52. package/dist/controls.css.map +1 -0
  53. package/dist/dialog.cjs +222 -0
  54. package/dist/dialog.cjs.map +1 -0
  55. package/dist/dialog.css +56 -0
  56. package/dist/dialog.css.map +1 -0
  57. package/dist/dialog.d.cts +28 -0
  58. package/dist/dialog.d.ts +28 -0
  59. package/dist/dialog.js +5 -0
  60. package/dist/dialog.js.map +1 -0
  61. package/dist/floating.cjs +83 -0
  62. package/dist/floating.cjs.map +1 -0
  63. package/dist/floating.d.cts +25 -0
  64. package/dist/floating.d.ts +25 -0
  65. package/dist/floating.js +4 -0
  66. package/dist/floating.js.map +1 -0
  67. package/dist/identity.css +15 -50
  68. package/dist/identity.css.map +1 -1
  69. package/dist/index.cjs +1553 -260
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +385 -152
  72. package/dist/index.d.ts +385 -152
  73. package/dist/index.js +264 -823
  74. package/dist/index.js.map +1 -1
  75. package/dist/job.css +24 -1
  76. package/dist/job.css.map +1 -1
  77. package/dist/keys.cjs +155 -0
  78. package/dist/keys.cjs.map +1 -0
  79. package/dist/keys.css +41 -0
  80. package/dist/keys.css.map +1 -0
  81. package/dist/keys.d.cts +57 -0
  82. package/dist/keys.d.ts +57 -0
  83. package/dist/keys.js +3 -0
  84. package/dist/keys.js.map +1 -0
  85. package/dist/link-status.cjs +430 -0
  86. package/dist/link-status.cjs.map +1 -0
  87. package/dist/link-status.css +76 -0
  88. package/dist/link-status.css.map +1 -0
  89. package/dist/link-status.d.cts +53 -0
  90. package/dist/link-status.d.ts +53 -0
  91. package/dist/link-status.js +5 -0
  92. package/dist/link-status.js.map +1 -0
  93. package/dist/menu.cjs +62 -1
  94. package/dist/menu.cjs.map +1 -1
  95. package/dist/menu.d.cts +88 -1
  96. package/dist/menu.d.ts +88 -1
  97. package/dist/menu.js +1 -1
  98. package/dist/notes-editor.css +9 -52
  99. package/dist/notes-editor.css.map +1 -1
  100. package/dist/pane-shell.css +23 -5
  101. package/dist/pane-shell.css.map +1 -1
  102. package/dist/problems.cjs +61 -0
  103. package/dist/problems.cjs.map +1 -0
  104. package/dist/problems.d.cts +57 -0
  105. package/dist/problems.d.ts +57 -0
  106. package/dist/problems.js +3 -0
  107. package/dist/problems.js.map +1 -0
  108. package/dist/property-list-D3VZCJzi.d.cts +162 -0
  109. package/dist/property-list-D3VZCJzi.d.ts +162 -0
  110. package/dist/property-list.cjs +784 -0
  111. package/dist/property-list.cjs.map +1 -0
  112. package/dist/property-list.d.cts +1 -0
  113. package/dist/property-list.d.ts +1 -0
  114. package/dist/property-list.js +4 -0
  115. package/dist/property-list.js.map +1 -0
  116. package/dist/save.css +1 -1
  117. package/dist/save.css.map +1 -1
  118. package/dist/settings.css +62 -18
  119. package/dist/settings.css.map +1 -1
  120. package/dist/stale.css +4 -1
  121. package/dist/stale.css.map +1 -1
  122. package/dist/stepper.css +47 -3
  123. package/dist/stepper.css.map +1 -1
  124. package/dist/toast.css +5 -5
  125. package/dist/toast.css.map +1 -1
  126. package/dist/tokens.css +20 -0
  127. package/dist/tokens.css.map +1 -1
  128. package/dist/tool-window.cjs +63 -0
  129. package/dist/tool-window.cjs.map +1 -1
  130. package/dist/tool-window.css +8 -4
  131. package/dist/tool-window.css.map +1 -1
  132. package/dist/tool-window.d.cts +67 -1
  133. package/dist/tool-window.d.ts +67 -1
  134. package/dist/tool-window.js +63 -1
  135. package/dist/tool-window.js.map +1 -1
  136. package/dist/tooltip.css +3 -3
  137. package/dist/tooltip.css.map +1 -1
  138. package/dist/updater-view.cjs +283 -0
  139. package/dist/updater-view.cjs.map +1 -0
  140. package/dist/updater-view.d.cts +23 -0
  141. package/dist/updater-view.d.ts +23 -0
  142. package/dist/updater-view.js +6 -0
  143. package/dist/updater-view.js.map +1 -0
  144. package/dist/updater.cjs +12 -16
  145. package/dist/updater.cjs.map +1 -1
  146. package/dist/updater.css +44 -0
  147. package/dist/updater.css.map +1 -0
  148. package/dist/updater.js +12 -16
  149. package/dist/updater.js.map +1 -1
  150. package/dist/util.cjs +78 -0
  151. package/dist/util.cjs.map +1 -0
  152. package/dist/util.d.cts +37 -0
  153. package/dist/util.d.ts +37 -0
  154. package/dist/util.js +3 -0
  155. package/dist/util.js.map +1 -0
  156. package/dist/vc.css +4 -3
  157. package/dist/vc.css.map +1 -1
  158. package/dist/welcome.cjs +215 -0
  159. package/dist/welcome.cjs.map +1 -0
  160. package/dist/welcome.css +112 -0
  161. package/dist/welcome.css.map +1 -0
  162. package/dist/welcome.d.cts +55 -0
  163. package/dist/welcome.d.ts +55 -0
  164. package/dist/welcome.js +4 -0
  165. package/dist/welcome.js.map +1 -0
  166. package/package.json +72 -2
  167. package/dist/chunk-QDPFU4WX.js +0 -43
  168. package/dist/chunk-QDPFU4WX.js.map +0 -1
@@ -0,0 +1,534 @@
1
+ import { el, iconBtn, moveItem, labelled, tagChips, stageChips } from './chunk-UG2P777F.js';
2
+
3
+ // src/settings.ts
4
+ function expandableRow(opts) {
5
+ const row = el("div", "set-row");
6
+ if (opts.name !== void 0) row.dataset.name = opts.name;
7
+ const line = el("div", "set-rowline");
8
+ if (opts.details && opts.details.length) {
9
+ const details = el("div", "set-details");
10
+ details.hidden = true;
11
+ for (const d of opts.details) details.append(d);
12
+ const toggle = el("button", "set-expand");
13
+ toggle.type = "button";
14
+ toggle.dataset.tip = "More";
15
+ toggle.setAttribute("aria-label", "More");
16
+ toggle.setAttribute("aria-expanded", "false");
17
+ toggle.addEventListener("click", () => {
18
+ const open = details.hidden;
19
+ details.hidden = !open;
20
+ toggle.setAttribute("aria-expanded", String(open));
21
+ toggle.classList.toggle("open", open);
22
+ });
23
+ line.append(toggle);
24
+ opts.line.forEach((c) => line.append(c));
25
+ row.append(line, details);
26
+ } else {
27
+ opts.line.forEach((c) => line.append(c));
28
+ row.append(line);
29
+ }
30
+ return row;
31
+ }
32
+ function revealRow(within, name) {
33
+ const row = [...within.querySelectorAll(".set-row")].find((r) => r.dataset.name === name);
34
+ if (!row) return false;
35
+ const toggle = row.querySelector(".set-rowline > .set-expand");
36
+ if (toggle && toggle.getAttribute("aria-expanded") !== "true") toggle.click();
37
+ const still = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
38
+ row.scrollIntoView({ block: "center", behavior: still ? "auto" : "smooth" });
39
+ row.classList.remove("landed");
40
+ void row.offsetWidth;
41
+ row.classList.add("landed");
42
+ row.addEventListener("animationend", () => row.classList.remove("landed"), { once: true });
43
+ return true;
44
+ }
45
+ function revealRowWhenReady(within, name, tries = 12) {
46
+ return new Promise((resolve) => {
47
+ const attempt = (left) => {
48
+ if (revealRow(within, name)) {
49
+ resolve(true);
50
+ return;
51
+ }
52
+ if (left <= 0) {
53
+ resolve(false);
54
+ return;
55
+ }
56
+ requestAnimationFrame(() => attempt(left - 1));
57
+ };
58
+ attempt(tries);
59
+ });
60
+ }
61
+ function focusNewRow(listEl) {
62
+ const last = listEl?.lastElementChild;
63
+ if (!(last instanceof HTMLElement)) return;
64
+ last.scrollIntoView({ block: "nearest" });
65
+ const name = last.querySelector("input.set-name") ?? last.querySelector("input");
66
+ name?.focus();
67
+ }
68
+ var DUP_MESSAGE = "Another row already has this name.";
69
+ function dupGuard() {
70
+ const items = [];
71
+ const check = () => {
72
+ const counts = /* @__PURE__ */ new Map();
73
+ for (const it of items) {
74
+ const k = it.key().trim().toLowerCase();
75
+ if (k) counts.set(k, (counts.get(k) ?? 0) + 1);
76
+ }
77
+ let any = false;
78
+ for (const it of items) {
79
+ const k = it.key().trim().toLowerCase();
80
+ const dup = !!k && (counts.get(k) ?? 0) > 1;
81
+ it.input.classList.toggle("invalid", dup);
82
+ if (dup) {
83
+ it.input.dataset["tip"] = DUP_MESSAGE;
84
+ any = true;
85
+ } else if (it.input.dataset["tip"] === DUP_MESSAGE) delete it.input.dataset["tip"];
86
+ }
87
+ return any;
88
+ };
89
+ return {
90
+ reset() {
91
+ items.length = 0;
92
+ },
93
+ track(input, key = () => input.value, also) {
94
+ items.push({ input, key });
95
+ input.addEventListener("input", check);
96
+ for (const a of also ?? []) a.addEventListener("input", check);
97
+ },
98
+ check,
99
+ firstDuplicate() {
100
+ check();
101
+ return items.find((it) => it.input.classList.contains("invalid"))?.input ?? null;
102
+ }
103
+ };
104
+ }
105
+ function mountSettingsDialog(opts) {
106
+ const dialog = el("dialog", "settings-dialog");
107
+ const form = el("form", "settings-form");
108
+ form.method = "dialog";
109
+ const tabs = el("nav", "settings-tabs");
110
+ const panels = el("div", "settings-panels");
111
+ const error = el("p", "settings-error");
112
+ error.hidden = true;
113
+ const cancel = el("button", "settings-cancel", "Cancel");
114
+ cancel.type = "button";
115
+ cancel.value = "cancel";
116
+ const save = el("button", "settings-save primary", "Save");
117
+ save.value = "save";
118
+ const panelFor = /* @__PURE__ */ new Map();
119
+ const tabFor = /* @__PURE__ */ new Map();
120
+ const handles = /* @__PURE__ */ new Map();
121
+ let active = opts.sections[0]?.id ?? "";
122
+ let lastGroup;
123
+ for (const s of opts.sections) {
124
+ if (s.group && s.group !== lastGroup) {
125
+ tabs.append(el("div", "settings-group", s.group));
126
+ lastGroup = s.group;
127
+ }
128
+ const tab = el("button", "settings-tab", s.label);
129
+ tab.type = "button";
130
+ tab.dataset["tab"] = s.id;
131
+ tab.addEventListener("click", () => {
132
+ if (!tab.classList.contains("is-disabled")) showTab(s.id);
133
+ });
134
+ tabs.append(tab);
135
+ tabFor.set(s.id, tab);
136
+ const panel = el("section", "settings-panel");
137
+ panel.dataset["panel"] = s.id;
138
+ panel.hidden = true;
139
+ panels.append(panel);
140
+ panelFor.set(s.id, panel);
141
+ }
142
+ function showTab(id) {
143
+ active = id;
144
+ for (const [sid, t] of tabFor) t.classList.toggle("active", sid === id);
145
+ for (const [sid, p] of panelFor) p.hidden = sid !== id;
146
+ }
147
+ const usable = (s) => !tabFor.get(s.id)?.classList.contains("is-disabled");
148
+ function refreshTabs() {
149
+ for (const s of opts.sections) {
150
+ const tab = tabFor.get(s.id);
151
+ const reason = s.disabled?.() ?? null;
152
+ tab.classList.toggle("is-disabled", reason !== null);
153
+ if (reason !== null) {
154
+ tab.dataset["tip"] = reason;
155
+ tab.setAttribute("aria-disabled", "true");
156
+ } else {
157
+ delete tab.dataset["tip"];
158
+ tab.removeAttribute("aria-disabled");
159
+ }
160
+ }
161
+ if (!opts.sections.some((s) => s.id === active && usable(s))) {
162
+ const first = opts.sections.find(usable);
163
+ if (first) showTab(first.id);
164
+ }
165
+ }
166
+ form.append(
167
+ el("h2", "settings-title", opts.title),
168
+ el("div", "settings-body", tabs, panels),
169
+ el("div", "settings-actions", error, cancel, save)
170
+ );
171
+ dialog.append(form);
172
+ document.body.append(dialog);
173
+ form.addEventListener("keydown", (e) => {
174
+ if (e.key === "Enter" && e.target?.tagName === "INPUT") e.preventDefault();
175
+ });
176
+ cancel.addEventListener("click", () => dialog.close("cancel"));
177
+ save.addEventListener("click", (e) => {
178
+ e.preventDefault();
179
+ error.hidden = true;
180
+ for (const s of opts.sections) {
181
+ const found = handles.get(s.id)?.firstInvalid?.();
182
+ if (!found) continue;
183
+ const bad = found instanceof HTMLElement ? found : found.el;
184
+ const message = (found instanceof HTMLElement ? void 0 : found.message) ?? bad.dataset["tip"];
185
+ showTab(s.id);
186
+ bad.focus();
187
+ error.textContent = message || "Fix the highlighted fields first.";
188
+ error.hidden = false;
189
+ return;
190
+ }
191
+ void (async () => {
192
+ await opts.onSave();
193
+ dialog.close("save");
194
+ })();
195
+ });
196
+ return {
197
+ open(tabId) {
198
+ handles.clear();
199
+ for (const s of opts.sections) {
200
+ const panel = panelFor.get(s.id);
201
+ panel.replaceChildren();
202
+ handles.set(s.id, s.mount(panel));
203
+ }
204
+ error.hidden = true;
205
+ showTab(tabId ?? active ?? opts.sections[0]?.id ?? "");
206
+ refreshTabs();
207
+ dialog.showModal();
208
+ },
209
+ refreshTabs,
210
+ destroy() {
211
+ dialog.remove();
212
+ }
213
+ };
214
+ }
215
+
216
+ // src/property-names.ts
217
+ var RESERVED_PROPERTY_NAMES = ["true", "false", "and", "or", "not"];
218
+ var PROPERTY_NAME_HINT = "Lower case letters, digits, and underscores. It can't start with a digit. No hyphens or spaces.";
219
+ function propertyNameify(text) {
220
+ const trimmed = text.trim();
221
+ const deliberateLeading = trimmed.startsWith("_");
222
+ let out = trimmed.toLowerCase().replace(/['’]/g, "").replace(/[^a-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
223
+ if (out === "") return "";
224
+ if (deliberateLeading || /^[0-9]/.test(out)) out = `_${out}`;
225
+ if (RESERVED_PROPERTY_NAMES.includes(out)) out = `${out}_`;
226
+ return out;
227
+ }
228
+ function isValidPropertyName(name) {
229
+ return /^[a-z_][a-z0-9_]*$/.test(name) && !RESERVED_PROPERTY_NAMES.includes(name);
230
+ }
231
+ function isCaseOnlyPropertyName(name) {
232
+ return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());
233
+ }
234
+
235
+ // src/property-name-field.ts
236
+ var refSyncs = /* @__PURE__ */ new WeakMap();
237
+ function propertyNameProblem(name) {
238
+ if (isValidPropertyName(name)) return void 0;
239
+ const suggestion = propertyNameify(name);
240
+ const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
241
+ if (RESERVED_PROPERTY_NAMES.includes(name.toLowerCase())) {
242
+ return `Can't be used. "${name.toLowerCase()}" is a keyword in expressions.${tail}`;
243
+ }
244
+ if (name.includes("-")) return `Can't be used. A hyphen reads as subtraction in an expression.${tail}`;
245
+ if (/^[0-9]/.test(name)) return `Can't be used. A name can't start with a digit.${tail}`;
246
+ if (name !== name.toLowerCase()) {
247
+ return `Can't be used. Expressions fold names, so this would look for "${name.toLowerCase()}".${tail}`;
248
+ }
249
+ return `Can't be used. Only lower case letters, digits, and underscores.${tail}`;
250
+ }
251
+ function bindPropertyName(input, onCommit, opts = {}) {
252
+ const hint = opts.hint;
253
+ const sync = () => {
254
+ const v = input.value.trim();
255
+ const problem = v === "" ? void 0 : propertyNameProblem(v);
256
+ input.classList.toggle("illegal", !!problem);
257
+ if (problem) input.title = problem;
258
+ else if (input.title.startsWith("Can't be used")) {
259
+ if (hint) input.title = hint;
260
+ else input.removeAttribute("title");
261
+ }
262
+ };
263
+ input.addEventListener("input", () => {
264
+ onCommit(input.value);
265
+ sync();
266
+ });
267
+ input.addEventListener("keydown", (e) => {
268
+ if (e.key !== "Tab" || e.shiftKey) return;
269
+ const typed = input.value.trim();
270
+ if (typed === "" || isValidPropertyName(typed)) return;
271
+ const coerced = propertyNameify(typed);
272
+ if (!coerced || coerced === input.value) return;
273
+ e.preventDefault();
274
+ input.value = coerced;
275
+ onCommit(coerced);
276
+ sync();
277
+ });
278
+ sync();
279
+ }
280
+ function firstIllegalPropertyName(host) {
281
+ return host.querySelector("input.illegal");
282
+ }
283
+ function nearest(name, known) {
284
+ const n = name.toLowerCase();
285
+ const byPrefix = known.filter((k) => k.toLowerCase().startsWith(n) || n.startsWith(k.toLowerCase()));
286
+ if (byPrefix.length === 1) return byPrefix[0];
287
+ let best;
288
+ let bestScore = 3;
289
+ for (const k of known) {
290
+ const a = k.toLowerCase();
291
+ if (Math.abs(a.length - n.length) > 2) continue;
292
+ let prev = Array.from({ length: n.length + 1 }, (_, i) => i);
293
+ for (let i = 1; i <= a.length; i++) {
294
+ const row = [i];
295
+ for (let j = 1; j <= n.length; j++) {
296
+ row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === n[j - 1] ? 0 : 1));
297
+ }
298
+ prev = row;
299
+ }
300
+ const d = prev[n.length];
301
+ if (d < bestScore) {
302
+ bestScore = d;
303
+ best = k;
304
+ }
305
+ }
306
+ return best;
307
+ }
308
+ function propertyRefProblem(name, opts) {
309
+ const scope = opts.scope ? `@${opts.scope}` : "that scope";
310
+ const folded = name.toLowerCase();
311
+ if (!isValidPropertyName(folded)) {
312
+ const suggestion = propertyNameify(name);
313
+ const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
314
+ if (RESERVED_PROPERTY_NAMES.includes(folded)) return `Can't be used. "${folded}" is a keyword in expressions.${tail}`;
315
+ if (name.includes("-")) return `Can't be used. A hyphen reads as subtraction in an expression.${tail}`;
316
+ if (/^[0-9]/.test(name)) return `Can't be used. A name can't start with a digit.${tail}`;
317
+ return `Can't be used. Only lower case letters, digits, and underscores.${tail}`;
318
+ }
319
+ const known = opts.known();
320
+ if (known.some((k) => k.toLowerCase() === folded)) return void 0;
321
+ const near = nearest(folded, known);
322
+ if (near) return `No ${scope} property is called "${name}". Did you mean "${near}"?`;
323
+ if (known.length === 0) return `No ${scope} properties are declared yet. Declare one above first.`;
324
+ const shown = known.slice(0, 3).map((k) => `"${k}"`).join(", ");
325
+ const more = known.length > 3 ? `, and ${known.length - 3} more` : "";
326
+ return `No ${scope} property is called "${name}". Pick one of ${shown}${more}.`;
327
+ }
328
+ function bindPropertyRef(input, onCommit, opts) {
329
+ const list = document.createElement("datalist");
330
+ list.id = `shell-propref-${Math.random().toString(36).slice(2, 9)}`;
331
+ input.setAttribute("list", list.id);
332
+ const attach = () => {
333
+ if (!list.isConnected && input.parentNode) input.after(list);
334
+ };
335
+ const sync = () => {
336
+ attach();
337
+ const known = opts.known();
338
+ list.replaceChildren(...known.map((k) => {
339
+ const o = document.createElement("option");
340
+ o.value = k;
341
+ return o;
342
+ }));
343
+ const v = input.value.trim();
344
+ const problem = v === "" ? void 0 : propertyRefProblem(v, opts);
345
+ input.classList.toggle("illegal", !!problem);
346
+ if (problem) input.title = problem;
347
+ else if (input.title.startsWith("Can't be used") || input.title.startsWith("No ")) {
348
+ if (opts.hint) input.title = opts.hint;
349
+ else input.removeAttribute("title");
350
+ }
351
+ };
352
+ refSyncs.set(input, sync);
353
+ input.addEventListener("input", () => {
354
+ onCommit(input.value);
355
+ sync();
356
+ });
357
+ input.addEventListener("keydown", (e) => {
358
+ if (e.key !== "Tab" || e.shiftKey) return;
359
+ const typed = input.value.trim();
360
+ if (typed === "" || isValidPropertyName(typed.toLowerCase())) return;
361
+ const coerced = propertyNameify(typed);
362
+ if (!coerced || coerced === input.value) return;
363
+ e.preventDefault();
364
+ input.value = coerced;
365
+ onCommit(coerced);
366
+ sync();
367
+ });
368
+ sync();
369
+ }
370
+ function revalidatePropertyRefs(host) {
371
+ for (const input of host.querySelectorAll("input")) refSyncs.get(input)?.();
372
+ }
373
+
374
+ // src/property-list.ts
375
+ var PROPERTY_TYPES = [
376
+ ["number", "Number"],
377
+ ["boolean", "True / False"],
378
+ ["string", "Text"],
379
+ ["enum", "List"],
380
+ ["flags", "Flags"],
381
+ ["quality", "Quality"]
382
+ ];
383
+ var option = (value, label, selected) => {
384
+ const o = el("option", void 0, label);
385
+ o.value = value;
386
+ o.selected = selected;
387
+ return o;
388
+ };
389
+ var NONE = "(none)";
390
+ function defaultControl(p, onChange, opts = {}) {
391
+ const typed = opts.typed === true;
392
+ const write = (raw) => {
393
+ if (raw === "") {
394
+ if (typed) delete p.default;
395
+ else p.default = "";
396
+ } else if (typed && p.type === "boolean") p.default = raw === "true";
397
+ else if (typed && p.type === "number") p.default = Number(raw);
398
+ else p.default = raw;
399
+ onChange?.();
400
+ };
401
+ const current = p.default === void 0 || p.default === null ? "" : String(p.default);
402
+ if (p.type === "boolean") {
403
+ const sel = el("select", "field set-default");
404
+ sel.append(option("", NONE, current === ""), option("true", "True", current === "true"), option("false", "False", current === "false"));
405
+ sel.addEventListener("change", () => write(sel.value));
406
+ return sel;
407
+ }
408
+ if (p.type === "enum") {
409
+ const sel = el("select", "field set-default");
410
+ sel.append(option("", NONE, current === ""));
411
+ for (const v of p.values ?? []) sel.append(option(v, v, current === v));
412
+ sel.addEventListener("change", () => write(sel.value));
413
+ return sel;
414
+ }
415
+ if (p.type === "quality") {
416
+ const sel = el("select", "field set-default");
417
+ sel.append(option("", "(first stage)", current === ""));
418
+ for (const v of p.stages ?? []) sel.append(option(v, v, current === v));
419
+ sel.addEventListener("change", () => write(sel.value));
420
+ return sel;
421
+ }
422
+ if (p.type === "flags") {
423
+ const s = el("span", "set-flagnote", "Starts empty");
424
+ s.dataset["tip"] = "A flags property starts with none set.";
425
+ return s;
426
+ }
427
+ const input = el("input", "field set-default");
428
+ input.type = p.type === "number" ? "number" : "text";
429
+ input.placeholder = "Default";
430
+ input.value = current;
431
+ input.addEventListener("input", () => write(input.value));
432
+ return input;
433
+ }
434
+ function mountPropertyList(host, decls, opts = {}) {
435
+ const changed = () => opts.onChange?.();
436
+ const guard = dupGuard();
437
+ const types = opts.types ?? PROPERTY_TYPES;
438
+ const list = el("div", "set-list");
439
+ const keep = (parts) => (parts ?? []).filter((p) => p !== null);
440
+ const row = (p, i) => {
441
+ const name = el("input", "field set-name");
442
+ name.type = "text";
443
+ name.placeholder = "Property name";
444
+ name.value = p.name;
445
+ name.spellcheck = false;
446
+ name.dataset["tip"] = PROPERTY_NAME_HINT;
447
+ bindPropertyName(name, (v) => {
448
+ p.name = v;
449
+ changed();
450
+ }, { hint: PROPERTY_NAME_HINT });
451
+ guard.track(name);
452
+ const type = el("select", "field set-type");
453
+ for (const [v, label] of types) type.append(option(v, label, v === p.type));
454
+ type.addEventListener("change", () => {
455
+ p.type = type.value;
456
+ if (opts.typed) delete p.default;
457
+ else p.default = "";
458
+ if (p.type === "enum" || p.type === "flags") p.values ??= [];
459
+ else delete p.values;
460
+ if (p.type === "quality") p.stages ??= [];
461
+ else delete p.stages;
462
+ render();
463
+ changed();
464
+ });
465
+ let def = defaultControl(p, changed, opts);
466
+ const refreshDefault = () => {
467
+ const fresh = defaultControl(p, changed, opts);
468
+ def.replaceWith(fresh);
469
+ def = fresh;
470
+ changed();
471
+ };
472
+ const up = iconBtn("up", "Move up", () => {
473
+ if (moveItem(decls, i, -1)) {
474
+ render();
475
+ changed();
476
+ }
477
+ }, i === 0);
478
+ const down = iconBtn("down", "Move down", () => {
479
+ if (moveItem(decls, i, 1)) {
480
+ render();
481
+ changed();
482
+ }
483
+ }, i === decls.length - 1);
484
+ const del = iconBtn("close", "Delete property", () => {
485
+ decls.splice(i, 1);
486
+ render();
487
+ changed();
488
+ }, false, true);
489
+ const details = keep(opts.extraDetails?.(p, i));
490
+ if (p.type === "enum" || p.type === "flags") details.push(labelled("Values", tagChips(p, refreshDefault)));
491
+ if (p.type === "quality") details.push(labelled("Stages (in order)", stageChips(p, refreshDefault)));
492
+ if (opts.purpose !== false) {
493
+ const purpose = el("input", "field set-purpose");
494
+ purpose.type = "text";
495
+ purpose.placeholder = "What this property is for";
496
+ purpose.value = p.purpose ?? "";
497
+ purpose.addEventListener("input", () => {
498
+ if (purpose.value.trim()) p.purpose = purpose.value;
499
+ else delete p.purpose;
500
+ changed();
501
+ });
502
+ details.push(labelled("Purpose", purpose));
503
+ }
504
+ return expandableRow({ line: [name, type, def, ...keep(opts.extraLine?.(p, i)), up, down, del], details, name: p.name });
505
+ };
506
+ const render = () => {
507
+ guard.reset();
508
+ list.replaceChildren();
509
+ if (decls.length === 0) list.append(el("p", "empty", opts.emptyText ?? "No properties yet."));
510
+ else decls.forEach((p, i) => list.append(row(p, i)));
511
+ guard.check();
512
+ };
513
+ render();
514
+ const add = el("button", "btn set-add", opts.addLabel ?? "+ Add property");
515
+ add.type = "button";
516
+ add.addEventListener("click", () => {
517
+ decls.push(opts.newDecl ? opts.newDecl() : { name: "", type: types[0]?.[0] ?? "string", ...opts.typed ? {} : { default: "" } });
518
+ render();
519
+ changed();
520
+ focusNewRow(list);
521
+ });
522
+ host.append(list, add);
523
+ return {
524
+ firstDuplicate: () => guard.firstDuplicate(),
525
+ firstIllegalName: () => firstIllegalPropertyName(host),
526
+ // The Save gate takes both faults; the field's own rollover says which.
527
+ firstInvalid: () => guard.firstDuplicate() ?? firstIllegalPropertyName(host),
528
+ render
529
+ };
530
+ }
531
+
532
+ export { PROPERTY_NAME_HINT, PROPERTY_TYPES, RESERVED_PROPERTY_NAMES, bindPropertyName, bindPropertyRef, defaultControl, dupGuard, expandableRow, firstIllegalPropertyName, focusNewRow, isCaseOnlyPropertyName, isValidPropertyName, mountPropertyList, mountSettingsDialog, propertyNameProblem, propertyNameify, propertyRefProblem, revalidatePropertyRefs, revealRow, revealRowWhenReady };
533
+ //# sourceMappingURL=chunk-R3NXPHRZ.js.map
534
+ //# sourceMappingURL=chunk-R3NXPHRZ.js.map