@wildwinter/app-shell 0.48.0 → 0.50.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/dist/chunk-JOFRUWTP.js +431 -0
- package/dist/chunk-JOFRUWTP.js.map +1 -0
- package/dist/{chunk-CRUILFAT.js → chunk-TETY2BTN.js} +33 -22
- package/dist/chunk-TETY2BTN.js.map +1 -0
- package/dist/controls.css +103 -0
- package/dist/controls.css.map +1 -1
- package/dist/index.cjs +463 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/{property-list-BgZlkcbz.d.cts → property-list-DlpxIkv6.d.cts} +27 -5
- package/dist/{property-list-BgZlkcbz.d.ts → property-list-DlpxIkv6.d.ts} +27 -5
- package/dist/property-list.cjs +55 -20
- package/dist/property-list.cjs.map +1 -1
- package/dist/property-list.d.cts +1 -1
- package/dist/property-list.d.ts +1 -1
- package/dist/property-list.js +2 -1
- package/dist/value-control.cjs +811 -0
- package/dist/value-control.cjs.map +1 -0
- package/dist/value-control.d.cts +104 -0
- package/dist/value-control.d.ts +104 -0
- package/dist/value-control.js +4 -0
- package/dist/value-control.js.map +1 -0
- package/dist/window-guard.cjs +74 -0
- package/dist/window-guard.cjs.map +1 -0
- package/dist/window-guard.d.cts +32 -0
- package/dist/window-guard.d.ts +32 -0
- package/dist/window-guard.js +67 -0
- package/dist/window-guard.js.map +1 -0
- package/package.json +11 -1
- package/dist/chunk-CRUILFAT.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -3120,6 +3120,432 @@ function mountLinkStatus(host, opts) {
|
|
|
3120
3120
|
};
|
|
3121
3121
|
}
|
|
3122
3122
|
|
|
3123
|
+
// src/value-control.ts
|
|
3124
|
+
function fallbackText(decl) {
|
|
3125
|
+
const d = decl.default;
|
|
3126
|
+
if (d !== void 0 && d !== null) {
|
|
3127
|
+
if (typeof d === "boolean") return d ? "True" : "False";
|
|
3128
|
+
if (Array.isArray(d)) return d.length === 0 ? "none" : d.join(", ");
|
|
3129
|
+
return d === "" ? "none" : String(d);
|
|
3130
|
+
}
|
|
3131
|
+
switch (decl.type) {
|
|
3132
|
+
case "boolean":
|
|
3133
|
+
return "False";
|
|
3134
|
+
case "number":
|
|
3135
|
+
return "0";
|
|
3136
|
+
case "enum":
|
|
3137
|
+
return decl.values?.[0] ?? "none";
|
|
3138
|
+
case "quality":
|
|
3139
|
+
return decl.stages?.[0] ?? "first stage";
|
|
3140
|
+
case "flags":
|
|
3141
|
+
return "none";
|
|
3142
|
+
default:
|
|
3143
|
+
return "empty";
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
var defaultLabel = (decl) => `default (${fallbackText(decl)})`;
|
|
3147
|
+
function stageGateNote(gates) {
|
|
3148
|
+
if (gates <= 0) return "";
|
|
3149
|
+
return gates === 1 ? "1 gate compares against this ladder, and it now reads the new order." : `${gates} gates compare against this ladder, and each now reads the new order.`;
|
|
3150
|
+
}
|
|
3151
|
+
function rungStrip(opts) {
|
|
3152
|
+
const make = opts.doc === void 0 ? el : (tag, props) => {
|
|
3153
|
+
const node2 = opts.doc.createElement(tag);
|
|
3154
|
+
node2.className = props.className;
|
|
3155
|
+
if (props.text !== void 0) node2.textContent = props.text;
|
|
3156
|
+
if (props.tip !== void 0) {
|
|
3157
|
+
node2.title = props.tip;
|
|
3158
|
+
node2.setAttribute("aria-label", props.tip);
|
|
3159
|
+
}
|
|
3160
|
+
if (props.onClick) node2.addEventListener("click", props.onClick);
|
|
3161
|
+
if (tag === "button") node2.type = "button";
|
|
3162
|
+
return node2;
|
|
3163
|
+
};
|
|
3164
|
+
const strip = make("span", { className: "seg shell-rungs" });
|
|
3165
|
+
const decl = opts.decl ?? { type: "quality", stages: opts.stages };
|
|
3166
|
+
const unset = opts.value === void 0;
|
|
3167
|
+
const fallback = typeof decl.default === "string" && opts.stages.includes(decl.default) ? decl.default : opts.stages[0];
|
|
3168
|
+
const readOnly = opts.onPick === void 0;
|
|
3169
|
+
if (opts.onClear) {
|
|
3170
|
+
strip.append(make("button", {
|
|
3171
|
+
className: `seg-opt shell-rung shell-rung-default${unset ? " on" : ""}`,
|
|
3172
|
+
text: defaultLabel(decl),
|
|
3173
|
+
tip: unset ? "Not set here: the declared default holds" : "Clear the value, back to the default",
|
|
3174
|
+
onClick: () => {
|
|
3175
|
+
if (!unset) opts.onClear();
|
|
3176
|
+
}
|
|
3177
|
+
}));
|
|
3178
|
+
}
|
|
3179
|
+
for (const stage of opts.stages) {
|
|
3180
|
+
const current3 = opts.value === stage;
|
|
3181
|
+
const falls = unset && stage === fallback;
|
|
3182
|
+
const rung = make(readOnly ? "span" : "button", {
|
|
3183
|
+
className: `seg-opt shell-rung${current3 ? " on" : ""}${falls ? " fallback" : ""}`,
|
|
3184
|
+
text: stage,
|
|
3185
|
+
tip: current3 ? "The current stage" : falls ? "The stage it falls back to" : readOnly ? stage : opts.pickTip?.(stage) ?? `Set to "${stage}"`,
|
|
3186
|
+
...readOnly ? {} : { onClick: () => {
|
|
3187
|
+
if (!current3) opts.onPick(stage);
|
|
3188
|
+
} }
|
|
3189
|
+
});
|
|
3190
|
+
strip.append(rung);
|
|
3191
|
+
}
|
|
3192
|
+
if (!unset && !opts.stages.includes(opts.value)) {
|
|
3193
|
+
strip.append(make("span", {
|
|
3194
|
+
className: "seg-opt shell-rung stray on",
|
|
3195
|
+
text: opts.value,
|
|
3196
|
+
tip: `"${opts.value}" is not one of the stages: pick the one it belongs at now`
|
|
3197
|
+
}));
|
|
3198
|
+
}
|
|
3199
|
+
return strip;
|
|
3200
|
+
}
|
|
3201
|
+
var fallbackFlags = (decl) => Array.isArray(decl.default) ? decl.default.filter((v) => typeof v === "string") : [];
|
|
3202
|
+
function fieldValueProblem(decl, value) {
|
|
3203
|
+
if (value === void 0 || value === null || value === "") return "";
|
|
3204
|
+
switch (decl.type) {
|
|
3205
|
+
case "number":
|
|
3206
|
+
return typeof value === "number" && Number.isFinite(value) ? "" : "Not a number.";
|
|
3207
|
+
case "boolean":
|
|
3208
|
+
return typeof value === "boolean" ? "" : "Not True or False.";
|
|
3209
|
+
case "enum":
|
|
3210
|
+
return decl.values && !decl.values.includes(String(value)) ? `"${String(value)}" is not one of the values.` : "";
|
|
3211
|
+
case "quality":
|
|
3212
|
+
return decl.stages && !decl.stages.includes(String(value)) ? `"${String(value)}" is not one of the stages.` : "";
|
|
3213
|
+
case "flags": {
|
|
3214
|
+
if (!Array.isArray(value)) return "Not a list of flags.";
|
|
3215
|
+
const strays = decl.values ? value.filter((v) => !decl.values.includes(String(v))) : [];
|
|
3216
|
+
if (strays.length === 0) return "";
|
|
3217
|
+
return strays.length === 1 ? `"${String(strays[0])}" is not one of the values.` : `${strays.map((s) => `"${String(s)}"`).join(", ")} are not among the values.`;
|
|
3218
|
+
}
|
|
3219
|
+
default:
|
|
3220
|
+
return "";
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
var isTextual = (type) => type !== "boolean" && type !== "enum" && type !== "flags" && type !== "quality";
|
|
3224
|
+
var controlShape = (o) => JSON.stringify([
|
|
3225
|
+
o.decl.type,
|
|
3226
|
+
o.decl.values ?? null,
|
|
3227
|
+
o.decl.stages ?? null,
|
|
3228
|
+
o.decl.default ?? null,
|
|
3229
|
+
o.onCommit === void 0,
|
|
3230
|
+
o.disabled === true,
|
|
3231
|
+
o.controlClassName ?? "",
|
|
3232
|
+
o.decl.type === "quality" && (o.decl.stages?.length ?? 0) === 0
|
|
3233
|
+
]);
|
|
3234
|
+
function fieldRow(initial) {
|
|
3235
|
+
let opts = initial;
|
|
3236
|
+
const row = el("div", `shell-fieldrow${opts.className ? ` ${opts.className}` : ""}`);
|
|
3237
|
+
const caption = el("span", `shell-fieldrow-label${opts.labelClassName ? ` ${opts.labelClassName}` : ""}`);
|
|
3238
|
+
caption.id = `shell-fr-${Math.random().toString(36).slice(2, 9)}`;
|
|
3239
|
+
const body = el("div", "shell-fieldrow-body");
|
|
3240
|
+
const purpose = el("p", "shell-fieldrow-purpose");
|
|
3241
|
+
const problem = el("p", "shell-fieldrow-problem");
|
|
3242
|
+
problem.setAttribute("role", "status");
|
|
3243
|
+
row.append(caption, body);
|
|
3244
|
+
let shape = "";
|
|
3245
|
+
let show2 = () => {
|
|
3246
|
+
};
|
|
3247
|
+
let typing = "";
|
|
3248
|
+
const commit = (v) => {
|
|
3249
|
+
opts.onCommit?.(v);
|
|
3250
|
+
};
|
|
3251
|
+
const editable = () => opts.onCommit !== void 0 && opts.disabled !== true;
|
|
3252
|
+
const ctl = (base) => opts.controlClassName ? `${base.replace(/^field /, "")} ${opts.controlClassName}` : base;
|
|
3253
|
+
const labelledBy = (node2) => {
|
|
3254
|
+
node2.setAttribute("aria-labelledby", caption.id);
|
|
3255
|
+
const described = [purpose.id, problem.id].filter(Boolean).join(" ");
|
|
3256
|
+
if (described) node2.setAttribute("aria-describedby", described);
|
|
3257
|
+
};
|
|
3258
|
+
purpose.id = `${caption.id}-p`;
|
|
3259
|
+
problem.id = `${caption.id}-m`;
|
|
3260
|
+
const sayProblem = () => {
|
|
3261
|
+
const words = [typing || fieldValueProblem(opts.decl, opts.value), opts.problem ?? ""].filter((w) => w !== "");
|
|
3262
|
+
problem.textContent = words.join(" ");
|
|
3263
|
+
problem.hidden = words.length === 0;
|
|
3264
|
+
row.classList.toggle("has-problem", words.length > 0);
|
|
3265
|
+
row.classList.toggle("shell-fieldrow-textual", isTextual(opts.decl.type));
|
|
3266
|
+
};
|
|
3267
|
+
const build = () => {
|
|
3268
|
+
const decl = opts.decl;
|
|
3269
|
+
typing = "";
|
|
3270
|
+
if (decl.type === "quality" && (decl.stages?.length ?? 0) > 0) {
|
|
3271
|
+
const holder = el("span", "shell-fieldrow-rungs");
|
|
3272
|
+
show2 = (value) => {
|
|
3273
|
+
holder.replaceChildren(rungStrip({
|
|
3274
|
+
stages: decl.stages,
|
|
3275
|
+
value: typeof value === "string" && value !== "" ? value : void 0,
|
|
3276
|
+
decl,
|
|
3277
|
+
...editable() ? { onPick: (stage) => {
|
|
3278
|
+
opts = { ...opts, value: stage };
|
|
3279
|
+
show2(stage);
|
|
3280
|
+
sayProblem();
|
|
3281
|
+
commit(stage);
|
|
3282
|
+
}, onClear: () => {
|
|
3283
|
+
opts = { ...opts, value: void 0 };
|
|
3284
|
+
show2(void 0);
|
|
3285
|
+
sayProblem();
|
|
3286
|
+
commit(void 0);
|
|
3287
|
+
} } : {}
|
|
3288
|
+
}));
|
|
3289
|
+
const strip = holder.firstElementChild;
|
|
3290
|
+
if (strip) {
|
|
3291
|
+
strip.setAttribute("role", "group");
|
|
3292
|
+
labelledBy(strip);
|
|
3293
|
+
}
|
|
3294
|
+
};
|
|
3295
|
+
return holder;
|
|
3296
|
+
}
|
|
3297
|
+
if (decl.type === "boolean" || decl.type === "enum") {
|
|
3298
|
+
const sel = el("select", ctl("field shell-fieldrow-select"));
|
|
3299
|
+
labelledBy(sel);
|
|
3300
|
+
sel.disabled = !editable();
|
|
3301
|
+
const choices = decl.type === "boolean" ? [["true", "True"], ["false", "False"]] : (decl.values ?? []).map((v) => [v, v]);
|
|
3302
|
+
show2 = (value) => {
|
|
3303
|
+
const current3 = value === void 0 || value === null || value === "" ? "" : String(value);
|
|
3304
|
+
sel.replaceChildren();
|
|
3305
|
+
const add = (v, label2) => {
|
|
3306
|
+
const o = el("option", void 0, label2);
|
|
3307
|
+
o.value = v;
|
|
3308
|
+
sel.append(o);
|
|
3309
|
+
};
|
|
3310
|
+
add("", defaultLabel(decl));
|
|
3311
|
+
for (const [v, label2] of choices) add(v, label2);
|
|
3312
|
+
if (current3 !== "" && !choices.some(([v]) => v === current3)) add(current3, current3);
|
|
3313
|
+
sel.value = current3;
|
|
3314
|
+
};
|
|
3315
|
+
sel.addEventListener("change", () => {
|
|
3316
|
+
const raw = sel.value;
|
|
3317
|
+
const v = raw === "" ? void 0 : decl.type === "boolean" ? raw === "true" : raw;
|
|
3318
|
+
opts = { ...opts, value: v };
|
|
3319
|
+
sayProblem();
|
|
3320
|
+
commit(v);
|
|
3321
|
+
});
|
|
3322
|
+
return sel;
|
|
3323
|
+
}
|
|
3324
|
+
if (decl.type === "flags" && (decl.values?.length ?? 0) > 0) {
|
|
3325
|
+
const holder = el("span", "shell-flags");
|
|
3326
|
+
holder.setAttribute("role", "group");
|
|
3327
|
+
labelledBy(holder);
|
|
3328
|
+
show2 = (value) => {
|
|
3329
|
+
const unset = !Array.isArray(value);
|
|
3330
|
+
const held = unset ? fallbackFlags(decl) : value.map(String);
|
|
3331
|
+
holder.replaceChildren();
|
|
3332
|
+
const chip2 = (text, cls, pressed, tip2, onClick) => {
|
|
3333
|
+
const b = el(onClick && editable() ? "button" : "span", { className: `shell-flag${cls}`, text, tip: tip2 });
|
|
3334
|
+
b.setAttribute("aria-label", `${text}: ${tip2}`);
|
|
3335
|
+
if (b instanceof HTMLButtonElement) {
|
|
3336
|
+
b.type = "button";
|
|
3337
|
+
b.addEventListener("click", onClick);
|
|
3338
|
+
}
|
|
3339
|
+
if (pressed !== void 0) b.setAttribute("aria-pressed", String(pressed));
|
|
3340
|
+
holder.append(b);
|
|
3341
|
+
};
|
|
3342
|
+
const set = (next) => {
|
|
3343
|
+
opts = { ...opts, value: next };
|
|
3344
|
+
show2(next);
|
|
3345
|
+
sayProblem();
|
|
3346
|
+
commit(next);
|
|
3347
|
+
};
|
|
3348
|
+
chip2(
|
|
3349
|
+
defaultLabel(decl),
|
|
3350
|
+
` shell-flag-default${unset ? " on" : ""}`,
|
|
3351
|
+
void 0,
|
|
3352
|
+
unset ? "Not set here: the declared default holds" : "Clear the value, back to the default",
|
|
3353
|
+
unset ? void 0 : () => set(void 0)
|
|
3354
|
+
);
|
|
3355
|
+
for (const v of decl.values) {
|
|
3356
|
+
const on = held.includes(v);
|
|
3357
|
+
chip2(
|
|
3358
|
+
v,
|
|
3359
|
+
`${on ? " on" : ""}${on && unset ? " fallback" : ""}`,
|
|
3360
|
+
on,
|
|
3361
|
+
on ? `Unset ${v}` : `Set ${v}`,
|
|
3362
|
+
() => set(on ? held.filter((x) => x !== v) : decl.values.filter((x) => x === v || held.includes(x)).concat(held.filter((x) => !decl.values.includes(x))))
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
for (const v of held.filter((x) => !decl.values.includes(x))) {
|
|
3366
|
+
chip2(v, " stray on", true, `"${v}" is not one of the values: click to drop it`, () => set(held.filter((x) => x !== v)));
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
return holder;
|
|
3370
|
+
}
|
|
3371
|
+
if (decl.type === "flags") {
|
|
3372
|
+
const holder = el("span", "shell-fieldrow-chips");
|
|
3373
|
+
show2 = (value) => {
|
|
3374
|
+
const box = { values: Array.isArray(value) ? value.map(String) : [] };
|
|
3375
|
+
const chips = tagChips(box, () => {
|
|
3376
|
+
const next = box.values.length > 0 ? [...box.values] : void 0;
|
|
3377
|
+
opts = { ...opts, value: next };
|
|
3378
|
+
sayProblem();
|
|
3379
|
+
commit(next);
|
|
3380
|
+
});
|
|
3381
|
+
const input = chips.querySelector("input");
|
|
3382
|
+
if (input) {
|
|
3383
|
+
labelledBy(input);
|
|
3384
|
+
input.placeholder = Array.isArray(value) ? "Add value" : defaultLabel(decl);
|
|
3385
|
+
input.disabled = !editable();
|
|
3386
|
+
}
|
|
3387
|
+
chips.querySelectorAll("button").forEach((b) => {
|
|
3388
|
+
b.disabled = !editable();
|
|
3389
|
+
});
|
|
3390
|
+
holder.replaceChildren(chips);
|
|
3391
|
+
};
|
|
3392
|
+
return holder;
|
|
3393
|
+
}
|
|
3394
|
+
const multiline = decl.type === "multiline";
|
|
3395
|
+
const field = multiline ? el("textarea", ctl("field shell-fieldrow-text")) : el("input", ctl("field shell-fieldrow-text"));
|
|
3396
|
+
if (field instanceof HTMLInputElement) {
|
|
3397
|
+
field.type = "text";
|
|
3398
|
+
if (decl.type === "number") field.inputMode = "decimal";
|
|
3399
|
+
} else field.rows = 3;
|
|
3400
|
+
labelledBy(field);
|
|
3401
|
+
field.placeholder = defaultLabel(decl);
|
|
3402
|
+
field.disabled = !editable();
|
|
3403
|
+
const asText = (value) => value === void 0 || value === null ? "" : Array.isArray(value) ? value.join(", ") : String(value);
|
|
3404
|
+
const read = (raw) => {
|
|
3405
|
+
if (raw.trim() === "") return {};
|
|
3406
|
+
if (decl.type !== "number") return { value: raw };
|
|
3407
|
+
const n = Number(raw.trim());
|
|
3408
|
+
return Number.isFinite(n) ? { value: n } : { bad: "Not a number." };
|
|
3409
|
+
};
|
|
3410
|
+
let atFocus = "";
|
|
3411
|
+
show2 = (value) => {
|
|
3412
|
+
if (field.ownerDocument.activeElement === field || keeper.restoring(field) || keeper.holds(row)) return;
|
|
3413
|
+
field.value = asText(value);
|
|
3414
|
+
};
|
|
3415
|
+
const take = () => {
|
|
3416
|
+
const r = read(field.value);
|
|
3417
|
+
typing = r.bad ?? "";
|
|
3418
|
+
if (r.bad === void 0) opts = { ...opts, value: r.value };
|
|
3419
|
+
sayProblem();
|
|
3420
|
+
if (r.bad === void 0) commit(r.value);
|
|
3421
|
+
};
|
|
3422
|
+
field.addEventListener("focus", () => {
|
|
3423
|
+
if (!keeper.restoring(field)) atFocus = field.value;
|
|
3424
|
+
});
|
|
3425
|
+
field.addEventListener("input", take);
|
|
3426
|
+
field.addEventListener("keydown", (e) => {
|
|
3427
|
+
if (e.key !== "Escape" || field.value === atFocus) return;
|
|
3428
|
+
field.value = atFocus;
|
|
3429
|
+
take();
|
|
3430
|
+
});
|
|
3431
|
+
return field;
|
|
3432
|
+
};
|
|
3433
|
+
const draw = (next) => {
|
|
3434
|
+
const decl = next.decl;
|
|
3435
|
+
const label2 = next.label ?? decl.name;
|
|
3436
|
+
if (typeof label2 === "string") caption.textContent = label2;
|
|
3437
|
+
else caption.replaceChildren(label2);
|
|
3438
|
+
caption.className = `shell-fieldrow-label${next.labelClassName ? ` ${next.labelClassName}` : ""}`;
|
|
3439
|
+
row.className = `shell-fieldrow${next.className ? ` ${next.className}` : ""}`;
|
|
3440
|
+
const nextShape = controlShape(next);
|
|
3441
|
+
opts = next;
|
|
3442
|
+
if (nextShape !== shape) {
|
|
3443
|
+
shape = nextShape;
|
|
3444
|
+
body.replaceChildren(build());
|
|
3445
|
+
}
|
|
3446
|
+
show2(next.value);
|
|
3447
|
+
purpose.textContent = decl.purpose ?? "";
|
|
3448
|
+
if (decl.purpose) body.append(purpose);
|
|
3449
|
+
else purpose.remove();
|
|
3450
|
+
body.append(problem);
|
|
3451
|
+
sayProblem();
|
|
3452
|
+
};
|
|
3453
|
+
draw(initial);
|
|
3454
|
+
keeper.watch(row);
|
|
3455
|
+
return { el: row, update: draw };
|
|
3456
|
+
}
|
|
3457
|
+
var keeper = /* @__PURE__ */ (() => {
|
|
3458
|
+
const memos = /* @__PURE__ */ new WeakMap();
|
|
3459
|
+
const restoringNow = /* @__PURE__ */ new WeakSet();
|
|
3460
|
+
const redrawn = /* @__PURE__ */ new WeakMap();
|
|
3461
|
+
const put = (row) => {
|
|
3462
|
+
const memo = memos.get(row);
|
|
3463
|
+
if (!memo) return;
|
|
3464
|
+
const doc = row.ownerDocument;
|
|
3465
|
+
const active2 = doc.activeElement;
|
|
3466
|
+
if (!row.isConnected || !memo.field.isConnected || !row.contains(memo.field)) return;
|
|
3467
|
+
if (active2 && active2 !== doc.body && !row.contains(active2)) {
|
|
3468
|
+
memos.delete(row);
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3471
|
+
if (active2 === memo.field && memo.field.matches(":focus")) return;
|
|
3472
|
+
restoringNow.add(memo.field);
|
|
3473
|
+
try {
|
|
3474
|
+
memo.field.focus({ preventScroll: true });
|
|
3475
|
+
if (!(memo.field instanceof HTMLSelectElement) && memo.start !== null) memo.field.setSelectionRange(memo.start, memo.end ?? memo.start);
|
|
3476
|
+
} finally {
|
|
3477
|
+
restoringNow.delete(memo.field);
|
|
3478
|
+
}
|
|
3479
|
+
};
|
|
3480
|
+
return {
|
|
3481
|
+
watch(row) {
|
|
3482
|
+
const note = (e) => {
|
|
3483
|
+
const t = e.target;
|
|
3484
|
+
if (!(t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement)) return;
|
|
3485
|
+
const text = !(t instanceof HTMLSelectElement);
|
|
3486
|
+
memos.set(row, { field: t, start: text ? t.selectionStart : null, end: text ? t.selectionEnd : null });
|
|
3487
|
+
};
|
|
3488
|
+
row.addEventListener("focusin", note);
|
|
3489
|
+
row.addEventListener("input", note);
|
|
3490
|
+
row.addEventListener("keyup", note);
|
|
3491
|
+
row.addEventListener("mouseup", note);
|
|
3492
|
+
row.addEventListener("focusout", (e) => {
|
|
3493
|
+
const at = performance.now();
|
|
3494
|
+
const late = e.relatedTarget === null && at - (redrawn.get(row) ?? -Infinity) < 250;
|
|
3495
|
+
if (late && memos.has(row)) {
|
|
3496
|
+
queueMicrotask(() => put(row));
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
queueMicrotask(() => {
|
|
3500
|
+
if ((redrawn.get(row) ?? -Infinity) >= at) {
|
|
3501
|
+
put(row);
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
if (row.isConnected && !row.contains(row.ownerDocument.activeElement)) memos.delete(row);
|
|
3505
|
+
});
|
|
3506
|
+
});
|
|
3507
|
+
},
|
|
3508
|
+
/** Give the focus back to the row, if the author was in it and has not gone anywhere else since. */
|
|
3509
|
+
restore(row) {
|
|
3510
|
+
if (!memos.has(row)) return;
|
|
3511
|
+
redrawn.set(row, performance.now());
|
|
3512
|
+
queueMicrotask(() => put(row));
|
|
3513
|
+
},
|
|
3514
|
+
restoring: (field) => restoringNow.has(field),
|
|
3515
|
+
/** Forget the row's focus if the author has since gone somewhere else (before the row is redrawn). */
|
|
3516
|
+
settle(row) {
|
|
3517
|
+
const active2 = row.ownerDocument.activeElement;
|
|
3518
|
+
if (active2 && active2 !== row.ownerDocument.body && !row.contains(active2)) memos.delete(row);
|
|
3519
|
+
},
|
|
3520
|
+
/** The row the author is in (before a redraw takes it out). */
|
|
3521
|
+
holds: (row) => memos.has(row)
|
|
3522
|
+
};
|
|
3523
|
+
})();
|
|
3524
|
+
function fieldRows() {
|
|
3525
|
+
const rows = /* @__PURE__ */ new Map();
|
|
3526
|
+
return {
|
|
3527
|
+
row(key, opts) {
|
|
3528
|
+
const had = rows.get(key);
|
|
3529
|
+
if (had) {
|
|
3530
|
+
had.used = true;
|
|
3531
|
+
keeper.settle(had.handle.el);
|
|
3532
|
+
had.handle.update(opts);
|
|
3533
|
+
keeper.restore(had.handle.el);
|
|
3534
|
+
return had.handle.el;
|
|
3535
|
+
}
|
|
3536
|
+
const handle = fieldRow(opts);
|
|
3537
|
+
rows.set(key, { handle, used: true });
|
|
3538
|
+
return handle.el;
|
|
3539
|
+
},
|
|
3540
|
+
sweep() {
|
|
3541
|
+
for (const [key, entry] of rows) {
|
|
3542
|
+
if (!entry.used) rows.delete(key);
|
|
3543
|
+
else entry.used = false;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3123
3549
|
// src/property-list.ts
|
|
3124
3550
|
var PROPERTY_TYPES = [
|
|
3125
3551
|
["number", "Number"],
|
|
@@ -3135,35 +3561,37 @@ var option = (value, label2, selected) => {
|
|
|
3135
3561
|
o.selected = selected;
|
|
3136
3562
|
return o;
|
|
3137
3563
|
};
|
|
3138
|
-
var
|
|
3564
|
+
var NONE_CHOSEN = "\0none";
|
|
3139
3565
|
function defaultControl(p, onChange, opts = {}) {
|
|
3140
3566
|
const typed = opts.typed === true;
|
|
3141
3567
|
const write = (raw) => {
|
|
3142
|
-
if (raw === "")
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
} else if (typed && p.type === "boolean") p.default = raw === "true";
|
|
3568
|
+
if (raw === "") delete p.default;
|
|
3569
|
+
else if (raw === NONE_CHOSEN) p.default = "";
|
|
3570
|
+
else if (typed && p.type === "boolean") p.default = raw === "true";
|
|
3146
3571
|
else if (typed && p.type === "number") p.default = Number(raw);
|
|
3147
3572
|
else p.default = raw;
|
|
3148
3573
|
onChange?.();
|
|
3149
3574
|
};
|
|
3150
3575
|
const current3 = p.default === void 0 || p.default === null ? "" : String(p.default);
|
|
3576
|
+
const unsetLabel = opts.unsetLabel ?? defaultLabel({ ...p, default: void 0 });
|
|
3151
3577
|
if (p.type === "boolean") {
|
|
3152
3578
|
const sel = el("select", "field set-default");
|
|
3153
|
-
sel.append(option("",
|
|
3579
|
+
sel.append(option("", unsetLabel, current3 === ""), option("true", "True", current3 === "true"), option("false", "False", current3 === "false"));
|
|
3154
3580
|
sel.addEventListener("change", () => write(sel.value));
|
|
3155
3581
|
return sel;
|
|
3156
3582
|
}
|
|
3157
3583
|
if (p.type === "enum") {
|
|
3158
3584
|
const sel = el("select", "field set-default");
|
|
3159
|
-
|
|
3585
|
+
const none = opts.noneChosen === true && p.default === "";
|
|
3586
|
+
sel.append(option("", unsetLabel, current3 === "" && !none));
|
|
3587
|
+
if (opts.noneChosen) sel.append(option(NONE_CHOSEN, "(none)", none));
|
|
3160
3588
|
for (const v of p.values ?? []) sel.append(option(v, v, current3 === v));
|
|
3161
3589
|
sel.addEventListener("change", () => write(sel.value));
|
|
3162
3590
|
return sel;
|
|
3163
3591
|
}
|
|
3164
3592
|
if (p.type === "quality") {
|
|
3165
3593
|
const sel = el("select", "field set-default");
|
|
3166
|
-
sel.append(option("",
|
|
3594
|
+
sel.append(option("", unsetLabel, current3 === ""));
|
|
3167
3595
|
for (const v of p.stages ?? []) sel.append(option(v, v, current3 === v));
|
|
3168
3596
|
sel.addEventListener("change", () => write(sel.value));
|
|
3169
3597
|
return sel;
|
|
@@ -3175,7 +3603,7 @@ function defaultControl(p, onChange, opts = {}) {
|
|
|
3175
3603
|
}
|
|
3176
3604
|
const input = el("input", "field set-default");
|
|
3177
3605
|
input.type = p.type === "number" ? "number" : "text";
|
|
3178
|
-
input.placeholder = "Default";
|
|
3606
|
+
input.placeholder = p.type === "number" ? `Default (${fallbackText({ type: "number" })})` : "Default";
|
|
3179
3607
|
input.value = current3;
|
|
3180
3608
|
input.addEventListener("input", () => write(input.value));
|
|
3181
3609
|
return input;
|
|
@@ -3186,24 +3614,32 @@ function mountPropertyList(host, decls, opts = {}) {
|
|
|
3186
3614
|
const types = opts.types ?? PROPERTY_TYPES;
|
|
3187
3615
|
const list = el("div", "set-list");
|
|
3188
3616
|
const keep = (parts) => (parts ?? []).filter((p) => p !== null);
|
|
3617
|
+
const noun = opts.noun ?? "property";
|
|
3618
|
+
const Noun = noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
3619
|
+
const freeNames = opts.names === "free";
|
|
3189
3620
|
const row = (p, i) => {
|
|
3190
3621
|
const name = el("input", "field set-name");
|
|
3191
3622
|
name.type = "text";
|
|
3192
|
-
name.placeholder =
|
|
3623
|
+
name.placeholder = `${Noun} name`;
|
|
3193
3624
|
name.value = p.name;
|
|
3194
3625
|
name.spellcheck = false;
|
|
3195
|
-
name.
|
|
3196
|
-
|
|
3197
|
-
p.name = v;
|
|
3626
|
+
if (freeNames) name.addEventListener("input", () => {
|
|
3627
|
+
p.name = name.value;
|
|
3198
3628
|
changed();
|
|
3199
|
-
}
|
|
3629
|
+
});
|
|
3630
|
+
else {
|
|
3631
|
+
name.dataset["tip"] = PROPERTY_NAME_HINT;
|
|
3632
|
+
bindPropertyName(name, (v) => {
|
|
3633
|
+
p.name = v;
|
|
3634
|
+
changed();
|
|
3635
|
+
}, { hint: PROPERTY_NAME_HINT });
|
|
3636
|
+
}
|
|
3200
3637
|
guard.track(name);
|
|
3201
3638
|
const type = el("select", "field set-type");
|
|
3202
3639
|
for (const [v, label2] of types) type.append(option(v, label2, v === p.type));
|
|
3203
3640
|
type.addEventListener("change", () => {
|
|
3204
3641
|
p.type = type.value;
|
|
3205
|
-
|
|
3206
|
-
else p.default = "";
|
|
3642
|
+
delete p.default;
|
|
3207
3643
|
if (p.type === "enum" || p.type === "flags") p.values ??= [];
|
|
3208
3644
|
else delete p.values;
|
|
3209
3645
|
if (p.type === "quality") p.stages ??= [];
|
|
@@ -3230,7 +3666,7 @@ function mountPropertyList(host, decls, opts = {}) {
|
|
|
3230
3666
|
changed();
|
|
3231
3667
|
}
|
|
3232
3668
|
}, i === decls.length - 1);
|
|
3233
|
-
const del = iconBtn("close",
|
|
3669
|
+
const del = iconBtn("close", `Delete ${noun}`, () => {
|
|
3234
3670
|
decls.splice(i, 1);
|
|
3235
3671
|
render();
|
|
3236
3672
|
changed();
|
|
@@ -3255,7 +3691,7 @@ function mountPropertyList(host, decls, opts = {}) {
|
|
|
3255
3691
|
if (opts.purpose !== false) {
|
|
3256
3692
|
const purpose = el("input", "field set-purpose");
|
|
3257
3693
|
purpose.type = "text";
|
|
3258
|
-
purpose.placeholder =
|
|
3694
|
+
purpose.placeholder = `What this ${noun} is for`;
|
|
3259
3695
|
purpose.value = p.purpose ?? "";
|
|
3260
3696
|
purpose.addEventListener("input", () => {
|
|
3261
3697
|
if (purpose.value.trim()) p.purpose = purpose.value;
|
|
@@ -3285,9 +3721,9 @@ function mountPropertyList(host, decls, opts = {}) {
|
|
|
3285
3721
|
host.append(list, add);
|
|
3286
3722
|
return {
|
|
3287
3723
|
firstDuplicate: () => guard.firstDuplicate(),
|
|
3288
|
-
firstIllegalName: () => firstIllegalPropertyName(host),
|
|
3724
|
+
firstIllegalName: () => freeNames ? null : firstIllegalPropertyName(host),
|
|
3289
3725
|
// The Save gate takes both faults; the field's own rollover says which.
|
|
3290
|
-
firstInvalid: () => guard.firstDuplicate() ?? firstIllegalPropertyName(host),
|
|
3726
|
+
firstInvalid: () => guard.firstDuplicate() ?? (freeNames ? null : firstIllegalPropertyName(host)),
|
|
3291
3727
|
render
|
|
3292
3728
|
};
|
|
3293
3729
|
}
|
|
@@ -3390,6 +3826,7 @@ exports.createNavHistory = createNavHistory;
|
|
|
3390
3826
|
exports.createSaveController = createSaveController;
|
|
3391
3827
|
exports.debounce = debounce;
|
|
3392
3828
|
exports.defaultControl = defaultControl;
|
|
3829
|
+
exports.defaultLabel = defaultLabel;
|
|
3393
3830
|
exports.defaultProblemCopy = defaultProblemCopy;
|
|
3394
3831
|
exports.describeProblem = describeProblem;
|
|
3395
3832
|
exports.dialogFrame = dialogFrame;
|
|
@@ -3397,7 +3834,11 @@ exports.dismissToast = dismissToast;
|
|
|
3397
3834
|
exports.dupGuard = dupGuard;
|
|
3398
3835
|
exports.el = el;
|
|
3399
3836
|
exports.expandableRow = expandableRow;
|
|
3837
|
+
exports.fallbackText = fallbackText;
|
|
3400
3838
|
exports.feedUpdaterDownloadProgress = feedUpdaterDownloadProgress;
|
|
3839
|
+
exports.fieldRow = fieldRow;
|
|
3840
|
+
exports.fieldRows = fieldRows;
|
|
3841
|
+
exports.fieldValueProblem = fieldValueProblem;
|
|
3401
3842
|
exports.firstIllegalPropertyName = firstIllegalPropertyName;
|
|
3402
3843
|
exports.focusNewRow = focusNewRow;
|
|
3403
3844
|
exports.foldVc = foldVc;
|
|
@@ -3462,11 +3903,13 @@ exports.renderStepperBar = renderStepperBar;
|
|
|
3462
3903
|
exports.revalidatePropertyRefs = revalidatePropertyRefs;
|
|
3463
3904
|
exports.revealRow = revealRow;
|
|
3464
3905
|
exports.revealRowWhenReady = revealRowWhenReady;
|
|
3906
|
+
exports.rungStrip = rungStrip;
|
|
3465
3907
|
exports.saveIndicator = saveIndicator;
|
|
3466
3908
|
exports.setKeyPlatform = setKeyPlatform;
|
|
3467
3909
|
exports.showAbout = showAbout;
|
|
3468
3910
|
exports.showUpdaterDialog = showUpdaterDialog;
|
|
3469
3911
|
exports.stageChips = stageChips;
|
|
3912
|
+
exports.stageGateNote = stageGateNote;
|
|
3470
3913
|
exports.staleBar = staleBar;
|
|
3471
3914
|
exports.tagChips = tagChips;
|
|
3472
3915
|
exports.tipAt = tipAt;
|