@wildwinter/expr-editor 0.14.1 → 0.15.1

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
@@ -98,6 +98,7 @@ var normaliseRef = (ref, defaultScope) => {
98
98
  const prefix = `@${defaultScope.toLowerCase()}.`;
99
99
  return low.startsWith(prefix) ? `@${low.slice(prefix.length)}` : low;
100
100
  };
101
+ var flagChangeSrc = (targetRef, sign, flag) => `set_flags(${targetRef}, ${sign}${flag})`;
101
102
  function advancedRef(node, defaultScope) {
102
103
  if (node.kind !== "call" || node.name !== "advance" || node.args.length !== 1) return null;
103
104
  const arg = node.args[0];
@@ -296,6 +297,7 @@ var propertyTip = (e) => {
296
297
  return lines.length ? lines.join("\n") : void 0;
297
298
  };
298
299
  var refOf = (e, defaultScope) => e.scope === defaultScope ? `@${e.name}` : `@${e.scope}.${e.name}`;
300
+ var targetRefOf = (e) => `@${e.scope}.${e.name}`;
299
301
  var displayName = (e, defaultScope) => e.scope === defaultScope ? e.name : `${e.scope}.${e.name}`;
300
302
  function filterCatalogue(entries, filter = {}) {
301
303
  return entries.filter(
@@ -383,8 +385,14 @@ function openPopover(anchor, render, opts = {}) {
383
385
  container.append(pop);
384
386
  const a = anchor.getBoundingClientRect();
385
387
  const ph = pop.getBoundingClientRect();
386
- let top = a.bottom + 4;
387
- if (top + ph.height > window.innerHeight - 8 && a.top - ph.height - 4 > 8) top = a.top - ph.height - 4;
388
+ const GAP = 4;
389
+ const EDGE = 8;
390
+ const below = window.innerHeight - EDGE - (a.bottom + GAP);
391
+ const above = a.top - GAP - EDGE;
392
+ const flip = ph.height > below && above > below;
393
+ const room = flip ? above : below;
394
+ const top = flip ? a.top - Math.min(ph.height, above) - GAP : a.bottom + GAP;
395
+ if (ph.height > room) pop.style.maxHeight = `${Math.max(96, Math.round(room))}px`;
388
396
  let left = a.left;
389
397
  if (left + ph.width > window.innerWidth - 8) left = Math.max(8, window.innerWidth - 8 - ph.width);
390
398
  if (container === document.body) {
@@ -1084,7 +1092,7 @@ function seedClause(ctx) {
1084
1092
  function clauseMenu(ctx, anchor, onPick) {
1085
1093
  ctx.openPopover(anchor, (close) => {
1086
1094
  const wrap = el("div", "exed-menu");
1087
- wrap.append(el("div", "exed-menu-head", ["Add a clause"]));
1095
+ wrap.append(el("div", "exed-menu-head", ["Add a condition"]));
1088
1096
  const add = (label, hint, make, disabled = false) => {
1089
1097
  const b = button(`exed-opt${disabled ? " disabled" : ""}`, "", () => {
1090
1098
  if (disabled) return;
@@ -1383,7 +1391,9 @@ function valueWizard(opts) {
1383
1391
  host.append(body);
1384
1392
  const choices = opts.expectedChoices?.length ? opts.expectedChoices : opts.expectedEnumValues;
1385
1393
  const isStage = opts.expectedType === "quality";
1386
- let kind = opts.expectedType === "number" ? "number" : opts.expectedType === "string" ? "text" : opts.expectedType === "boolean" ? "bool" : (opts.expectedType === "enum" || opts.expectedType === "quality") && choices?.length ? "enum" : "menu";
1394
+ const flagsReady = opts.expectedType === "flags" && opts.expectedRef !== void 0;
1395
+ let kind = opts.expectedType === "number" ? "number" : opts.expectedType === "string" ? "text" : opts.expectedType === "boolean" ? "bool" : (opts.expectedType === "enum" || opts.expectedType === "quality") && choices?.length ? "enum" : flagsReady ? "flags" : "menu";
1396
+ let flagSign = "+";
1387
1397
  const draw = () => {
1388
1398
  body.replaceChildren();
1389
1399
  const other = () => button("exed-vwiz-other", "\u21A9 a different kind", () => {
@@ -1392,6 +1402,10 @@ function valueWizard(opts) {
1392
1402
  });
1393
1403
  switch (kind) {
1394
1404
  case "menu":
1405
+ if (flagsReady) body.append(optBtn("Set or clear a flag\u2026", () => {
1406
+ kind = "flags";
1407
+ draw();
1408
+ }));
1395
1409
  body.append(optBtn("A property\u2026", () => {
1396
1410
  kind = "property";
1397
1411
  draw();
@@ -1413,6 +1427,33 @@ function valueWizard(opts) {
1413
1427
  draw();
1414
1428
  }));
1415
1429
  break;
1430
+ case "flags": {
1431
+ const flags = opts.expectedFlags ?? [];
1432
+ if (!flags.length) body.append(el("div", "exed-hint", ["This property declares no flag values yet."]));
1433
+ else {
1434
+ const signRow = el("div", "exed-field-row");
1435
+ signRow.append(
1436
+ button(`exed-opt${flagSign === "+" ? " sel" : ""}`, "\uFF0B set", () => {
1437
+ flagSign = "+";
1438
+ draw();
1439
+ }),
1440
+ button(`exed-opt${flagSign === "-" ? " sel" : ""}`, "\uFF0D clear", () => {
1441
+ flagSign = "-";
1442
+ draw();
1443
+ })
1444
+ );
1445
+ body.append(signRow);
1446
+ body.append(el("div", "exed-vwiz-note", [flagSign === "+" ? "Set a flag:" : "Clear a flag:"]));
1447
+ const list = el("div", "exed-picker-list");
1448
+ for (const f of flags) list.append(optBtn(`${flagSign} ${f}`, () => opts.onCommit(flagChangeSrc(opts.expectedRef, flagSign, f))));
1449
+ body.append(list);
1450
+ }
1451
+ body.append(button("exed-vwiz-other", "\u21A9 a different kind (replaces every flag)", () => {
1452
+ kind = "menu";
1453
+ draw();
1454
+ }));
1455
+ break;
1456
+ }
1416
1457
  case "property":
1417
1458
  body.append(propertyPicker(pickCtx, { onPick: (e) => opts.onCommit(refOf(e, opts.defaultScope)) }));
1418
1459
  body.append(other());
@@ -1451,6 +1492,10 @@ function isSelfAdvance(eff, defaultScope, dialect) {
1451
1492
  return false;
1452
1493
  }
1453
1494
  }
1495
+ function entryForTarget(catalogue, target, defaultScope) {
1496
+ const want = normaliseRef(target, defaultScope);
1497
+ return catalogue.find((e) => normaliseRef(`@${e.scope}.${e.name}`, defaultScope) === want);
1498
+ }
1454
1499
  var clone = (list) => list.map((e) => e.kind === "set" ? { ...e } : { ...e, args: [...e.args] });
1455
1500
  var addSet = (list, target, value) => [...clone(list), { kind: "set", target, value }];
1456
1501
  var addEmit = (list, event) => [...clone(list), { kind: "emit", event, args: [] }];
@@ -1539,6 +1584,11 @@ function mountEffectsEditor(host, opts) {
1539
1584
  // choicesOf, never a raw field: a quality's ladder lives in `stages`, and reading `enumValues`
1540
1585
  // here told the wizard the type and withheld the values.
1541
1586
  ...choicesOf(expected) ? { expectedChoices: choicesOf(expected) } : {},
1587
+ // The flags step needs the target itself: its declared flags, and the
1588
+ // qualified ref its set_flags(...) is written against. choicesOf stays
1589
+ // out of it deliberately - flags are not a pick-one list, and a pinned
1590
+ // test holds it to that.
1591
+ ...expected?.type === "flags" ? { expectedRef: targetRefOf(expected), expectedFlags: expected.enumValues ?? [] } : {},
1542
1592
  onCommit: (src) => {
1543
1593
  onCommit(src);
1544
1594
  close();
@@ -1546,7 +1596,7 @@ function mountEffectsEditor(host, opts) {
1546
1596
  onCancel: close
1547
1597
  }), { container: opts.popoverContainer });
1548
1598
  };
1549
- const targetType = (target) => opts.catalogue.find((e) => refOf(e, defaultScope) === target)?.type;
1599
+ const targetType = (target) => entryForTarget(opts.catalogue, target, defaultScope)?.type;
1550
1600
  const valueDisplay = (value, onChange, type, selfAdvanceRef) => {
1551
1601
  const sub = el("div", "exed-effect-value");
1552
1602
  const addTerm = type === "number" ? "arithmetic" : type === "boolean" ? "boolean" : void 0;
@@ -1613,10 +1663,10 @@ function mountEffectsEditor(host, opts) {
1613
1663
  const row = el("div", "exed-effect exed-effect-set");
1614
1664
  const targetBtn = button("exed-pill exed-pill-prop", eff.target || "(pick property)", (e) => {
1615
1665
  pickTarget(e.currentTarget, void 0, (entry) => {
1616
- commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value: initialValueFor(entry, defaultScope).src }));
1666
+ commit(updateAt(effects, i, { target: targetRefOf(entry), value: initialValueFor(entry, defaultScope).src }));
1617
1667
  });
1618
1668
  }, "change the target property");
1619
- const targetEntry = opts.catalogue.find((e) => refOf(e, defaultScope) === eff.target);
1669
+ const targetEntry = entryForTarget(opts.catalogue, eff.target, defaultScope);
1620
1670
  const tip = propertyTip(targetEntry);
1621
1671
  if (tip) targetBtn.title = tip;
1622
1672
  if (targetEntry && opts.propertyActions) {
@@ -1720,10 +1770,10 @@ function mountEffectsEditor(host, opts) {
1720
1770
  pickTarget(anchor, void 0, (entry) => {
1721
1771
  const { src, ask } = initialValueFor(entry, defaultScope);
1722
1772
  if (!ask) {
1723
- commit(addSet(effects, refOf(entry, defaultScope), src));
1773
+ commit(addSet(effects, targetRefOf(entry), src));
1724
1774
  return;
1725
1775
  }
1726
- openValueWizard(anchor, entry, (chosen) => commit(addSet(effects, refOf(entry, defaultScope), chosen)));
1776
+ openValueWizard(anchor, entry, (chosen) => commit(addSet(effects, targetRefOf(entry), chosen)));
1727
1777
  });
1728
1778
  }));
1729
1779
  if (opts.allowEmit !== false) {
@@ -1852,6 +1902,7 @@ exports.filterCatalogue = filterCatalogue;
1852
1902
  exports.findChoicePeer = findChoicePeer;
1853
1903
  exports.findEnumPeer = findEnumPeer;
1854
1904
  exports.firstEmptyLeafPath = firstEmptyLeafPath;
1905
+ exports.flagChangeSrc = flagChangeSrc;
1855
1906
  exports.flagDelta = flagDelta;
1856
1907
  exports.flipContainerOp = flipContainerOp;
1857
1908
  exports.formatNumber = formatNumber;
@@ -1889,6 +1940,7 @@ exports.seedValueSrc = seedValueSrc;
1889
1940
  exports.setArgAt = setArgAt;
1890
1941
  exports.setNodeAt = setNodeAt;
1891
1942
  exports.strLit = strLit;
1943
+ exports.targetRefOf = targetRefOf;
1892
1944
  exports.toggleContainerNot = toggleContainerNot;
1893
1945
  exports.toggleNotAt = toggleNotAt;
1894
1946
  exports.updateAt = updateAt;