@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.cjs CHANGED
@@ -891,6 +891,142 @@ function isCaseOnlyPropertyName(name) {
891
891
  return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());
892
892
  }
893
893
 
894
+ // src/property-name-field.ts
895
+ var refSyncs = /* @__PURE__ */ new WeakMap();
896
+ function propertyNameProblem(name) {
897
+ if (isValidPropertyName(name)) return void 0;
898
+ const suggestion = propertyNameify(name);
899
+ const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
900
+ if (RESERVED_PROPERTY_NAMES.includes(name.toLowerCase())) {
901
+ return `Cannot be used: "${name.toLowerCase()}" is a keyword in expressions.${tail}`;
902
+ }
903
+ if (name.includes("-")) return `Cannot be used: a hyphen reads as subtraction in an expression.${tail}`;
904
+ if (/^[0-9]/.test(name)) return `Cannot be used: a name cannot start with a digit.${tail}`;
905
+ if (name !== name.toLowerCase()) {
906
+ return `Cannot be used: expressions fold names, so this would look for "${name.toLowerCase()}".${tail}`;
907
+ }
908
+ return `Cannot be used: only lower case letters, digits and underscores.${tail}`;
909
+ }
910
+ function bindPropertyName(input, onCommit, opts = {}) {
911
+ const hint = opts.hint;
912
+ const sync = () => {
913
+ const v = input.value.trim();
914
+ const problem = v === "" ? void 0 : propertyNameProblem(v);
915
+ input.classList.toggle("illegal", !!problem);
916
+ if (problem) input.title = problem;
917
+ else if (input.title.startsWith("Cannot be used")) {
918
+ if (hint) input.title = hint;
919
+ else input.removeAttribute("title");
920
+ }
921
+ };
922
+ input.addEventListener("input", () => {
923
+ onCommit(input.value);
924
+ sync();
925
+ });
926
+ input.addEventListener("keydown", (e) => {
927
+ if (e.key !== "Tab" || e.shiftKey) return;
928
+ const typed = input.value.trim();
929
+ if (typed === "" || isValidPropertyName(typed)) return;
930
+ const coerced = propertyNameify(typed);
931
+ if (!coerced || coerced === input.value) return;
932
+ e.preventDefault();
933
+ input.value = coerced;
934
+ onCommit(coerced);
935
+ sync();
936
+ });
937
+ sync();
938
+ }
939
+ function firstIllegalPropertyName(host) {
940
+ return host.querySelector("input.illegal");
941
+ }
942
+ function nearest(name, known) {
943
+ const n = name.toLowerCase();
944
+ const byPrefix = known.filter((k) => k.toLowerCase().startsWith(n) || n.startsWith(k.toLowerCase()));
945
+ if (byPrefix.length === 1) return byPrefix[0];
946
+ let best;
947
+ let bestScore = 3;
948
+ for (const k of known) {
949
+ const a = k.toLowerCase();
950
+ if (Math.abs(a.length - n.length) > 2) continue;
951
+ let prev = Array.from({ length: n.length + 1 }, (_, i) => i);
952
+ for (let i = 1; i <= a.length; i++) {
953
+ const row = [i];
954
+ for (let j = 1; j <= n.length; j++) {
955
+ row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === n[j - 1] ? 0 : 1));
956
+ }
957
+ prev = row;
958
+ }
959
+ const d = prev[n.length];
960
+ if (d < bestScore) {
961
+ bestScore = d;
962
+ best = k;
963
+ }
964
+ }
965
+ return best;
966
+ }
967
+ function propertyRefProblem(name, opts) {
968
+ const scope = opts.scope ? `@${opts.scope}` : "that scope";
969
+ const folded = name.toLowerCase();
970
+ if (!isValidPropertyName(folded)) {
971
+ const suggestion = propertyNameify(name);
972
+ const tail = suggestion ? ` Try "${suggestion}", or press Tab.` : "";
973
+ if (RESERVED_PROPERTY_NAMES.includes(folded)) return `Cannot be used: "${folded}" is a keyword in expressions.${tail}`;
974
+ if (name.includes("-")) return `Cannot be used: a hyphen reads as subtraction in an expression.${tail}`;
975
+ if (/^[0-9]/.test(name)) return `Cannot be used: a name cannot start with a digit.${tail}`;
976
+ return `Cannot be used: only lower case letters, digits and underscores.${tail}`;
977
+ }
978
+ const known = opts.known();
979
+ if (known.some((k) => k.toLowerCase() === folded)) return void 0;
980
+ const near = nearest(folded, known);
981
+ if (near) return `No ${scope} property is called "${name}". Did you mean "${near}"?`;
982
+ if (known.length === 0) return `No ${scope} properties are declared yet. Declare one above first.`;
983
+ const shown = known.slice(0, 3).map((k) => `"${k}"`).join(", ");
984
+ const more = known.length > 3 ? `, and ${known.length - 3} more` : "";
985
+ return `No ${scope} property is called "${name}". Declared: ${shown}${more}.`;
986
+ }
987
+ function bindPropertyRef(input, onCommit, opts) {
988
+ const list = document.createElement("datalist");
989
+ list.id = `shell-propref-${Math.random().toString(36).slice(2, 9)}`;
990
+ input.setAttribute("list", list.id);
991
+ input.after(list);
992
+ const sync = () => {
993
+ const known = opts.known();
994
+ list.replaceChildren(...known.map((k) => {
995
+ const o = document.createElement("option");
996
+ o.value = k;
997
+ return o;
998
+ }));
999
+ const v = input.value.trim();
1000
+ const problem = v === "" ? void 0 : propertyRefProblem(v, opts);
1001
+ input.classList.toggle("illegal", !!problem);
1002
+ if (problem) input.title = problem;
1003
+ else if (input.title.startsWith("Cannot be used") || input.title.startsWith("No ")) {
1004
+ if (opts.hint) input.title = opts.hint;
1005
+ else input.removeAttribute("title");
1006
+ }
1007
+ };
1008
+ refSyncs.set(input, sync);
1009
+ input.addEventListener("input", () => {
1010
+ onCommit(input.value);
1011
+ sync();
1012
+ });
1013
+ input.addEventListener("keydown", (e) => {
1014
+ if (e.key !== "Tab" || e.shiftKey) return;
1015
+ const typed = input.value.trim();
1016
+ if (typed === "" || isValidPropertyName(typed.toLowerCase())) return;
1017
+ const coerced = propertyNameify(typed);
1018
+ if (!coerced || coerced === input.value) return;
1019
+ e.preventDefault();
1020
+ input.value = coerced;
1021
+ onCommit(coerced);
1022
+ sync();
1023
+ });
1024
+ sync();
1025
+ }
1026
+ function revalidatePropertyRefs(host) {
1027
+ for (const input of host.querySelectorAll("input")) refSyncs.get(input)?.();
1028
+ }
1029
+
894
1030
  // src/id-editor.ts
895
1031
  function openGameIdEditor(opts) {
896
1032
  const slugify = opts.slugify ?? gameIdify;
@@ -1562,6 +1698,8 @@ exports.PANE_MENU = PANE_MENU;
1562
1698
  exports.PROPERTY_NAME_HINT = PROPERTY_NAME_HINT;
1563
1699
  exports.RESERVED_PROPERTY_NAMES = RESERVED_PROPERTY_NAMES;
1564
1700
  exports.askIdentity = askIdentity;
1701
+ exports.bindPropertyName = bindPropertyName;
1702
+ exports.bindPropertyRef = bindPropertyRef;
1565
1703
  exports.closeAnchoredPanel = closeAnchoredPanel;
1566
1704
  exports.closeWithExit = closeWithExit;
1567
1705
  exports.colourFor = colourFor;
@@ -1571,6 +1709,7 @@ exports.createSaveController = createSaveController;
1571
1709
  exports.dupGuard = dupGuard;
1572
1710
  exports.el = el;
1573
1711
  exports.expandableRow = expandableRow;
1712
+ exports.firstIllegalPropertyName = firstIllegalPropertyName;
1574
1713
  exports.focusNewRow = focusNewRow;
1575
1714
  exports.foldVc = foldVc;
1576
1715
  exports.gameIdify = gameIdify;
@@ -1596,8 +1735,11 @@ exports.openNotesEditor = openNotesEditor;
1596
1735
  exports.paintVcBadges = paintVcBadges;
1597
1736
  exports.pinButton = pinButton;
1598
1737
  exports.placeAnchored = placeAnchored;
1738
+ exports.propertyNameProblem = propertyNameProblem;
1599
1739
  exports.propertyNameify = propertyNameify;
1740
+ exports.propertyRefProblem = propertyRefProblem;
1600
1741
  exports.renderStepperBar = renderStepperBar;
1742
+ exports.revalidatePropertyRefs = revalidatePropertyRefs;
1601
1743
  exports.saveIndicator = saveIndicator;
1602
1744
  exports.showAbout = showAbout;
1603
1745
  exports.staleBar = staleBar;