@wildwinter/app-shell 0.29.0 → 0.31.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/index.d.cts CHANGED
@@ -379,6 +379,65 @@ declare function isValidPropertyName(name: string): boolean;
379
379
  */
380
380
  declare function isCaseOnlyPropertyName(name: string): boolean;
381
381
 
382
+ /**
383
+ * One line saying what is wrong with this name, or undefined when nothing is.
384
+ *
385
+ * Each branch names what actually HAPPENS rather than reciting the rule, because
386
+ * the person reading it is usually somebody wondering why a condition does
387
+ * nothing. Kept to one line: it lands in a rollover on the field, and hosts
388
+ * repeat it in the Save error rather than inventing a summary of their own.
389
+ */
390
+ declare function propertyNameProblem(name: string): string | undefined;
391
+ /**
392
+ * Wire a name field: mark it live, explain the fault in its rollover, and coerce
393
+ * on Tab. `onCommit` receives what the author typed, VERBATIM, on every input.
394
+ *
395
+ * `.illegal` rather than dupGuard's `.invalid`, because both can be true of one
396
+ * field and `dupGuard.check()` owns that class: it would clear this marking every
397
+ * time it ran, including at the moment of a blocked Save.
398
+ *
399
+ * An empty field is not marked. An unfinished row is a different thing from an
400
+ * illegal one, and hosts prune blank names on save.
401
+ */
402
+ declare function bindPropertyName(input: HTMLInputElement, onCommit: (value: string) => void, opts?: {
403
+ hint?: string;
404
+ }): void;
405
+ /**
406
+ * The first field in `host` holding a name no expression could reach, for a Save
407
+ * gate. Hosts hand this back beside `dupGuard.firstDuplicate()`: two faults, two
408
+ * sets of words, one gate.
409
+ */
410
+ declare function firstIllegalPropertyName(host: ParentNode): HTMLInputElement | null;
411
+ interface PropertyRefOptions {
412
+ /** The names that exist, read fresh on every check so a declaration added in the
413
+ * same dialog clears the complaint. Compared case-insensitively, since the
414
+ * parser folds. */
415
+ known: () => string[];
416
+ /** The scope token this field addresses, for the wording ("no @world property..."). */
417
+ scope?: string;
418
+ /** Shown on the field when there is nothing to complain about. */
419
+ hint?: string;
420
+ }
421
+ /** Why this reference cannot be used, or undefined when it can. */
422
+ declare function propertyRefProblem(name: string, opts: PropertyRefOptions): string | undefined;
423
+ /**
424
+ * Wire a field that POINTS at a declared property: same manners as a declaration
425
+ * field (mark, do not rewrite, Tab coerces), plus the declared names offered as a
426
+ * datalist, because the rule is declare-then-reference and a picker makes that
427
+ * painless rather than pedantic.
428
+ *
429
+ * Marks `.illegal` for an undeclared name too, which gates Save wherever the host
430
+ * already asks `firstIllegalPropertyName`.
431
+ */
432
+ declare function bindPropertyRef(input: HTMLInputElement, onCommit: (value: string) => void, opts: PropertyRefOptions): void;
433
+ /**
434
+ * Re-check every bound reference field inside `host`. Hosts call this when the
435
+ * DECLARATIONS change, since a reference's validity depends on a list it does not
436
+ * own: rename a property and the driver pointing at it is wrong from that moment,
437
+ * with nothing typed into it.
438
+ */
439
+ declare function revalidatePropertyRefs(host: ParentNode): void;
440
+
382
441
  interface GameIdEditorOptions {
383
442
  anchor: HTMLElement;
384
443
  /** The pinned address, or "" when it is being derived. */
@@ -759,4 +818,4 @@ interface StepperBarOptions {
759
818
  /** Draw (or hide) the bar in `host`. Idempotent: call it again with new state. */
760
819
  declare function renderStepperBar(host: HTMLElement, opts: StepperBarOptions): void;
761
820
 
762
- export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isCaseOnlyPropertyName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameify, renderStepperBar, saveIndicator, showAbout, staleBar, tagChips, tipAt, tipBold, vcBadgeFor };
821
+ export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, type PropertyRefOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type TooltipOptions, type VcMap, askIdentity, bindPropertyName, bindPropertyRef, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, firstIllegalPropertyName, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isCaseOnlyPropertyName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, staleBar, tagChips, tipAt, tipBold, vcBadgeFor };
package/dist/index.d.ts CHANGED
@@ -379,6 +379,65 @@ declare function isValidPropertyName(name: string): boolean;
379
379
  */
380
380
  declare function isCaseOnlyPropertyName(name: string): boolean;
381
381
 
382
+ /**
383
+ * One line saying what is wrong with this name, or undefined when nothing is.
384
+ *
385
+ * Each branch names what actually HAPPENS rather than reciting the rule, because
386
+ * the person reading it is usually somebody wondering why a condition does
387
+ * nothing. Kept to one line: it lands in a rollover on the field, and hosts
388
+ * repeat it in the Save error rather than inventing a summary of their own.
389
+ */
390
+ declare function propertyNameProblem(name: string): string | undefined;
391
+ /**
392
+ * Wire a name field: mark it live, explain the fault in its rollover, and coerce
393
+ * on Tab. `onCommit` receives what the author typed, VERBATIM, on every input.
394
+ *
395
+ * `.illegal` rather than dupGuard's `.invalid`, because both can be true of one
396
+ * field and `dupGuard.check()` owns that class: it would clear this marking every
397
+ * time it ran, including at the moment of a blocked Save.
398
+ *
399
+ * An empty field is not marked. An unfinished row is a different thing from an
400
+ * illegal one, and hosts prune blank names on save.
401
+ */
402
+ declare function bindPropertyName(input: HTMLInputElement, onCommit: (value: string) => void, opts?: {
403
+ hint?: string;
404
+ }): void;
405
+ /**
406
+ * The first field in `host` holding a name no expression could reach, for a Save
407
+ * gate. Hosts hand this back beside `dupGuard.firstDuplicate()`: two faults, two
408
+ * sets of words, one gate.
409
+ */
410
+ declare function firstIllegalPropertyName(host: ParentNode): HTMLInputElement | null;
411
+ interface PropertyRefOptions {
412
+ /** The names that exist, read fresh on every check so a declaration added in the
413
+ * same dialog clears the complaint. Compared case-insensitively, since the
414
+ * parser folds. */
415
+ known: () => string[];
416
+ /** The scope token this field addresses, for the wording ("no @world property..."). */
417
+ scope?: string;
418
+ /** Shown on the field when there is nothing to complain about. */
419
+ hint?: string;
420
+ }
421
+ /** Why this reference cannot be used, or undefined when it can. */
422
+ declare function propertyRefProblem(name: string, opts: PropertyRefOptions): string | undefined;
423
+ /**
424
+ * Wire a field that POINTS at a declared property: same manners as a declaration
425
+ * field (mark, do not rewrite, Tab coerces), plus the declared names offered as a
426
+ * datalist, because the rule is declare-then-reference and a picker makes that
427
+ * painless rather than pedantic.
428
+ *
429
+ * Marks `.illegal` for an undeclared name too, which gates Save wherever the host
430
+ * already asks `firstIllegalPropertyName`.
431
+ */
432
+ declare function bindPropertyRef(input: HTMLInputElement, onCommit: (value: string) => void, opts: PropertyRefOptions): void;
433
+ /**
434
+ * Re-check every bound reference field inside `host`. Hosts call this when the
435
+ * DECLARATIONS change, since a reference's validity depends on a list it does not
436
+ * own: rename a property and the driver pointing at it is wrong from that moment,
437
+ * with nothing typed into it.
438
+ */
439
+ declare function revalidatePropertyRefs(host: ParentNode): void;
440
+
382
441
  interface GameIdEditorOptions {
383
442
  anchor: HTMLElement;
384
443
  /** The pinned address, or "" when it is being derived. */
@@ -759,4 +818,4 @@ interface StepperBarOptions {
759
818
  /** Draw (or hide) the bar in `host`. Idempotent: call it again with new state. */
760
819
  declare function renderStepperBar(host: HTMLElement, opts: StepperBarOptions): void;
761
820
 
762
- export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type TooltipOptions, type VcMap, askIdentity, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isCaseOnlyPropertyName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameify, renderStepperBar, saveIndicator, showAbout, staleBar, tagChips, tipAt, tipBold, vcBadgeFor };
821
+ export { type AboutOptions, type AnchoredPanel, type AnchoredPanelOptions, type Child, type Comment, type CommentMessage, type CommentsOptions, type ConfirmOptions, type DocLine, type DupGuard, type ElProps, type GameIdEditorOptions, type IconName, type IconSvgName, type Identity, type IdentityOptions, type JobProgressOptions, type JobProgressView, type Notes, type NotesEditorOptions, PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, type PaneShell, type PaneShellOptions, type PaneShellState, type PaneSide, type PaneSideConfig, type PinButton, type PinButtonOptions, type PropertyRefOptions, RESERVED_PROPERTY_NAMES, type SaveController, type SaveControllerOptions, type SaveIndicator, type SaveStatus, type SettingsDialog, type SettingsDialogOptions, type SettingsSection, type SettingsSectionHandle, type ShardVc, type StaleBarOptions, type StepperBarOptions, type StepperItem, type StepperTone, type TipRect, type TooltipOptions, type VcMap, askIdentity, bindPropertyName, bindPropertyRef, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, firstIllegalPropertyName, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isCaseOnlyPropertyName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, staleBar, tagChips, tipAt, tipBold, vcBadgeFor };
package/dist/index.js CHANGED
@@ -879,6 +879,142 @@ function isCaseOnlyPropertyName(name) {
879
879
  return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());
880
880
  }
881
881
 
882
+ // src/property-name-field.ts
883
+ var refSyncs = /* @__PURE__ */ new WeakMap();
884
+ function propertyNameProblem(name) {
885
+ if (isValidPropertyName(name)) return void 0;
886
+ const suggestion = propertyNameify(name);
887
+ const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
888
+ if (RESERVED_PROPERTY_NAMES.includes(name.toLowerCase())) {
889
+ return `Cannot be used: "${name.toLowerCase()}" is a keyword in expressions.${tail}`;
890
+ }
891
+ if (name.includes("-")) return `Cannot be used: a hyphen reads as subtraction in an expression.${tail}`;
892
+ if (/^[0-9]/.test(name)) return `Cannot be used: a name cannot start with a digit.${tail}`;
893
+ if (name !== name.toLowerCase()) {
894
+ return `Cannot be used: expressions fold names, so this would look for "${name.toLowerCase()}".${tail}`;
895
+ }
896
+ return `Cannot be used: only lower case letters, digits and underscores.${tail}`;
897
+ }
898
+ function bindPropertyName(input, onCommit, opts = {}) {
899
+ const hint = opts.hint;
900
+ const sync = () => {
901
+ const v = input.value.trim();
902
+ const problem = v === "" ? void 0 : propertyNameProblem(v);
903
+ input.classList.toggle("illegal", !!problem);
904
+ if (problem) input.title = problem;
905
+ else if (input.title.startsWith("Cannot be used")) {
906
+ if (hint) input.title = hint;
907
+ else input.removeAttribute("title");
908
+ }
909
+ };
910
+ input.addEventListener("input", () => {
911
+ onCommit(input.value);
912
+ sync();
913
+ });
914
+ input.addEventListener("keydown", (e) => {
915
+ if (e.key !== "Tab" || e.shiftKey) return;
916
+ const typed = input.value.trim();
917
+ if (typed === "" || isValidPropertyName(typed)) return;
918
+ const coerced = propertyNameify(typed);
919
+ if (!coerced || coerced === input.value) return;
920
+ e.preventDefault();
921
+ input.value = coerced;
922
+ onCommit(coerced);
923
+ sync();
924
+ });
925
+ sync();
926
+ }
927
+ function firstIllegalPropertyName(host) {
928
+ return host.querySelector("input.illegal");
929
+ }
930
+ function nearest(name, known) {
931
+ const n = name.toLowerCase();
932
+ const byPrefix = known.filter((k) => k.toLowerCase().startsWith(n) || n.startsWith(k.toLowerCase()));
933
+ if (byPrefix.length === 1) return byPrefix[0];
934
+ let best;
935
+ let bestScore = 3;
936
+ for (const k of known) {
937
+ const a = k.toLowerCase();
938
+ if (Math.abs(a.length - n.length) > 2) continue;
939
+ let prev = Array.from({ length: n.length + 1 }, (_, i) => i);
940
+ for (let i = 1; i <= a.length; i++) {
941
+ const row = [i];
942
+ for (let j = 1; j <= n.length; j++) {
943
+ row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === n[j - 1] ? 0 : 1));
944
+ }
945
+ prev = row;
946
+ }
947
+ const d = prev[n.length];
948
+ if (d < bestScore) {
949
+ bestScore = d;
950
+ best = k;
951
+ }
952
+ }
953
+ return best;
954
+ }
955
+ function propertyRefProblem(name, opts) {
956
+ const scope = opts.scope ? `@${opts.scope}` : "that scope";
957
+ const folded = name.toLowerCase();
958
+ if (!isValidPropertyName(folded)) {
959
+ const suggestion = propertyNameify(name);
960
+ const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
961
+ if (RESERVED_PROPERTY_NAMES.includes(folded)) return `Cannot be used: "${folded}" is a keyword in expressions.${tail}`;
962
+ if (name.includes("-")) return `Cannot be used: a hyphen reads as subtraction in an expression.${tail}`;
963
+ if (/^[0-9]/.test(name)) return `Cannot be used: a name cannot start with a digit.${tail}`;
964
+ return `Cannot be used: only lower case letters, digits and underscores.${tail}`;
965
+ }
966
+ const known = opts.known();
967
+ if (known.some((k) => k.toLowerCase() === folded)) return void 0;
968
+ const near = nearest(folded, known);
969
+ if (near) return `No ${scope} property is called "${name}". Did you mean "${near}"?`;
970
+ if (known.length === 0) return `No ${scope} properties are declared yet. Declare one above first.`;
971
+ const shown = known.slice(0, 3).map((k) => `"${k}"`).join(", ");
972
+ const more = known.length > 3 ? `, and ${known.length - 3} more` : "";
973
+ return `No ${scope} property is called "${name}". Declared: ${shown}${more}.`;
974
+ }
975
+ function bindPropertyRef(input, onCommit, opts) {
976
+ const list = document.createElement("datalist");
977
+ list.id = `shell-propref-${Math.random().toString(36).slice(2, 9)}`;
978
+ input.setAttribute("list", list.id);
979
+ input.after(list);
980
+ const sync = () => {
981
+ const known = opts.known();
982
+ list.replaceChildren(...known.map((k) => {
983
+ const o = document.createElement("option");
984
+ o.value = k;
985
+ return o;
986
+ }));
987
+ const v = input.value.trim();
988
+ const problem = v === "" ? void 0 : propertyRefProblem(v, opts);
989
+ input.classList.toggle("illegal", !!problem);
990
+ if (problem) input.title = problem;
991
+ else if (input.title.startsWith("Cannot be used") || input.title.startsWith("No ")) {
992
+ if (opts.hint) input.title = opts.hint;
993
+ else input.removeAttribute("title");
994
+ }
995
+ };
996
+ refSyncs.set(input, sync);
997
+ input.addEventListener("input", () => {
998
+ onCommit(input.value);
999
+ sync();
1000
+ });
1001
+ input.addEventListener("keydown", (e) => {
1002
+ if (e.key !== "Tab" || e.shiftKey) return;
1003
+ const typed = input.value.trim();
1004
+ if (typed === "" || isValidPropertyName(typed.toLowerCase())) return;
1005
+ const coerced = propertyNameify(typed);
1006
+ if (!coerced || coerced === input.value) return;
1007
+ e.preventDefault();
1008
+ input.value = coerced;
1009
+ onCommit(coerced);
1010
+ sync();
1011
+ });
1012
+ sync();
1013
+ }
1014
+ function revalidatePropertyRefs(host) {
1015
+ for (const input of host.querySelectorAll("input")) refSyncs.get(input)?.();
1016
+ }
1017
+
882
1018
  // src/id-editor.ts
883
1019
  function openGameIdEditor(opts) {
884
1020
  const slugify = opts.slugify ?? gameIdify;
@@ -1539,6 +1675,6 @@ function renderStepperBar(host, opts) {
1539
1675
  );
1540
1676
  }
1541
1677
 
1542
- export { PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, RESERVED_PROPERTY_NAMES, askIdentity, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isCaseOnlyPropertyName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameify, renderStepperBar, saveIndicator, showAbout, staleBar, tagChips, tipAt, tipBold, vcBadgeFor };
1678
+ export { PALETTE, PALETTE_SIZE, PROPERTY_NAME_HINT, RESERVED_PROPERTY_NAMES, askIdentity, bindPropertyName, bindPropertyRef, closeAnchoredPanel, closeWithExit, colourFor, colourIndex, confirmDialog, createSaveController, dupGuard, el, expandableRow, firstIllegalPropertyName, focusNewRow, foldVc, gameIdify, hideTip, icon, iconBtn, iconSvg, initTooltips, isCaseOnlyPropertyName, isValidGameId, isValidPropertyName, labelled, lockControls, lockNotice, mountJobProgress, mountPaneShell, mountSettingsDialog, moveItem, openAnchoredPanel, openComments, openGameIdEditor, openNotesEditor, paintVcBadges, pinButton, placeAnchored, propertyNameProblem, propertyNameify, propertyRefProblem, renderStepperBar, revalidatePropertyRefs, saveIndicator, showAbout, staleBar, tagChips, tipAt, tipBold, vcBadgeFor };
1543
1679
  //# sourceMappingURL=index.js.map
1544
1680
  //# sourceMappingURL=index.js.map