@wildwinter/expr-editor 0.12.3 → 0.13.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
@@ -93,6 +93,16 @@ function findChoicePeer(ast, path) {
93
93
  return other.kind === "scopedvar" ? { scope: other.scope, name: other.name } : null;
94
94
  }
95
95
  var findEnumPeer = findChoicePeer;
96
+ var normaliseRef = (ref, defaultScope) => {
97
+ const low = ref.toLowerCase();
98
+ const prefix = `@${defaultScope.toLowerCase()}.`;
99
+ return low.startsWith(prefix) ? `@${low.slice(prefix.length)}` : low;
100
+ };
101
+ function advancedRef(node, defaultScope) {
102
+ if (node.kind !== "call" || node.name !== "advance" || node.args.length !== 1) return null;
103
+ const arg = node.args[0];
104
+ return arg.kind === "scopedvar" ? normaliseRef(`@${arg.scope}.${arg.name}`, defaultScope) : null;
105
+ }
96
106
  function firstEmptyLeafPath(node, base = []) {
97
107
  switch (node.kind) {
98
108
  case "string":
@@ -463,6 +473,16 @@ function renderNode(node, path, ctx, parentOp, side) {
463
473
  for (let i = 1; i < node.args.length; i++) row2.append(renderNode(node.args[i], [...path, "args", i], ctx));
464
474
  return row2;
465
475
  }
476
+ if (ctx.selfAdvanceRef && advancedRef(node, ctx.defaultScope) === normaliseRef(ctx.selfAdvanceRef, ctx.defaultScope)) {
477
+ const row2 = el("span", "exed-call exed-call-advance");
478
+ row2.append(pill(
479
+ "advance",
480
+ "advances",
481
+ (b) => callEditor(ctx, path, node, b),
482
+ { issue, title: "advance one stage", aria: "advances one stage" }
483
+ ));
484
+ return row2;
485
+ }
466
486
  const row = el("span", "exed-call");
467
487
  row.append(pill("func", node.name, (b) => callEditor(ctx, path, node, b), { issue }));
468
488
  row.append(el("span", "exed-paren", ["("]));
@@ -1146,6 +1166,7 @@ function mountExpressionEditor(host, opts) {
1146
1166
  },
1147
1167
  ...opts.requireNonEmpty ? { requireNonEmpty: true } : {},
1148
1168
  ...opts.valueEnumValues ? { valueEnumValues: opts.valueEnumValues } : {},
1169
+ ...opts.selfAdvanceRef ? { selfAdvanceRef: opts.selfAdvanceRef } : {},
1149
1170
  ...opts.propertyActions ? { propertyActions: opts.propertyActions } : {},
1150
1171
  ...opts.pickNode ? { pickNode: opts.pickNode } : {},
1151
1172
  ...opts.nodeLabel ? { nodeLabel: opts.nodeLabel } : {}
@@ -1409,6 +1430,14 @@ function valueWizard(opts) {
1409
1430
  }
1410
1431
 
1411
1432
  // src/effects.ts
1433
+ function isSelfAdvance(eff, defaultScope, dialect) {
1434
+ if (eff.kind !== "set" || !eff.target) return false;
1435
+ try {
1436
+ return advancedRef(expr.parse(eff.value, dialect), defaultScope) === normaliseRef(eff.target, defaultScope);
1437
+ } catch {
1438
+ return false;
1439
+ }
1440
+ }
1412
1441
  var clone = (list) => list.map((e) => e.kind === "set" ? { ...e } : { ...e, args: [...e.args] });
1413
1442
  var addSet = (list, target, value) => [...clone(list), { kind: "set", target, value }];
1414
1443
  var addEmit = (list, event) => [...clone(list), { kind: "emit", event, args: [] }];
@@ -1505,7 +1534,7 @@ function mountEffectsEditor(host, opts) {
1505
1534
  }), { container: opts.popoverContainer });
1506
1535
  };
1507
1536
  const targetType = (target) => opts.catalogue.find((e) => refOf(e, defaultScope) === target)?.type;
1508
- const valueDisplay = (value, onChange, type) => {
1537
+ const valueDisplay = (value, onChange, type, selfAdvanceRef) => {
1509
1538
  const sub = el("div", "exed-effect-value");
1510
1539
  const addTerm = type === "number" ? "arithmetic" : type === "boolean" ? "boolean" : void 0;
1511
1540
  inner.push(mountExpressionEditor(sub, {
@@ -1516,7 +1545,13 @@ function mountEffectsEditor(host, opts) {
1516
1545
  scopeOrder: opts.scopeOrder,
1517
1546
  functions: opts.functions,
1518
1547
  mode: "flat",
1548
+ // Every editor here holds a VALUE (a set's value, an emit's argument), never a condition. Say
1549
+ // so, or emptying one falls back to the condition empty state and an outcome row reads
1550
+ // "always / + Add your first condition" - condition vocabulary, offering clauses where a value
1551
+ // belongs, and no way back to a value. `valueField` renders an editable empty literal instead.
1552
+ valueField: true,
1519
1553
  ...addTerm ? { addTerm } : {},
1554
+ ...selfAdvanceRef ? { selfAdvanceRef } : {},
1520
1555
  ...opts.popoverContainer ? { popoverContainer: opts.popoverContainer } : {},
1521
1556
  text: textMode,
1522
1557
  onChange
@@ -1567,11 +1602,18 @@ function mountEffectsEditor(host, opts) {
1567
1602
  commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value: initialValueFor(entry, defaultScope).src }));
1568
1603
  });
1569
1604
  }, "change the target property");
1570
- row.append(targetBtn, el("span", "exed-effect-eq", ["="]));
1605
+ const advances = isSelfAdvance(eff, defaultScope, opts.dialect);
1606
+ row.append(targetBtn);
1607
+ if (!advances) row.append(el("span", "exed-effect-eq", ["="]));
1571
1608
  row.append(valueDisplay(eff.value, (src) => {
1572
- effects = updateAt(effects, i, { value: src });
1609
+ const next = updateAt(effects, i, { value: src });
1610
+ if (isSelfAdvance(next[i], defaultScope, opts.dialect) !== advances) {
1611
+ commit(next);
1612
+ return;
1613
+ }
1614
+ effects = next;
1573
1615
  opts.onChange(effects);
1574
- }, targetType(eff.target)));
1616
+ }, targetType(eff.target), advances ? eff.target : void 0));
1575
1617
  row.append(rowActions2(i));
1576
1618
  return row;
1577
1619
  }
@@ -1685,7 +1727,7 @@ function mountEffectsEditor(host, opts) {
1685
1727
  }
1686
1728
 
1687
1729
  // src/preview.ts
1688
- function frozenCtx(src, o) {
1730
+ function frozenCtx(src, o, selfAdvanceRef) {
1689
1731
  const v = validateSource(src, o.schema, o.dialect);
1690
1732
  const ctx = {
1691
1733
  schema: o.schema,
@@ -1704,6 +1746,7 @@ function frozenCtx(src, o) {
1704
1746
  pickNode: () => {
1705
1747
  },
1706
1748
  // enables labelled node pills; never fires (preview is read-only)
1749
+ ...selfAdvanceRef ? { selfAdvanceRef } : {},
1707
1750
  ...o.nodeLabel ? { nodeLabel: o.nodeLabel } : {},
1708
1751
  ...o.propertyActions ? {
1709
1752
  propertyActions: o.propertyActions,
@@ -1714,8 +1757,8 @@ function frozenCtx(src, o) {
1714
1757
  };
1715
1758
  return { ctx, ast: v.ast };
1716
1759
  }
1717
- function exprPills(src, o) {
1718
- const { ctx, ast } = frozenCtx(src, o);
1760
+ function exprPills(src, o, selfAdvanceRef) {
1761
+ const { ctx, ast } = frozenCtx(src, o, selfAdvanceRef);
1719
1762
  return ast ? renderNode(ast, [], ctx) : el("span", "exed-preview-raw", [src]);
1720
1763
  }
1721
1764
  function renderConditionPreview(src, o) {
@@ -1727,9 +1770,10 @@ function renderEffectsPreview(effects, o) {
1727
1770
  for (const eff of effects) {
1728
1771
  const row = el("div", "exed-preview-eff");
1729
1772
  if (eff.kind === "set") {
1773
+ const advances = isSelfAdvance(eff, o.dialect.defaultScope, o.dialect);
1730
1774
  row.append(el("span", "exed-pill exed-pill-prop", [eff.target || "(property)"]));
1731
- row.append(el("span", "exed-effect-eq", ["="]));
1732
- row.append(exprPills(eff.value, o));
1775
+ if (!advances) row.append(el("span", "exed-effect-eq", ["="]));
1776
+ row.append(exprPills(eff.value, o, advances ? eff.target : void 0));
1733
1777
  } else {
1734
1778
  row.append(el("span", "exed-effect-kw", ["emit"]));
1735
1779
  row.append(el("span", "exed-pill exed-pill-event", [eff.event ? `"${eff.event}"` : "(name)"]));
@@ -1756,6 +1800,7 @@ exports.addArg = addArg;
1756
1800
  exports.addChildToContainer = addChildToContainer;
1757
1801
  exports.addEmit = addEmit;
1758
1802
  exports.addSet = addSet;
1803
+ exports.advancedRef = advancedRef;
1759
1804
  exports.astToTree = astToTree;
1760
1805
  exports.binary = binary;
1761
1806
  exports.boolLit = boolLit;
@@ -1786,6 +1831,7 @@ exports.mountExpressionEditor = mountExpressionEditor;
1786
1831
  exports.moveAt = moveAt;
1787
1832
  exports.moveChildInContainer = moveChildInContainer;
1788
1833
  exports.needsParens = needsParens;
1834
+ exports.normaliseRef = normaliseRef;
1789
1835
  exports.notNode = notNode;
1790
1836
  exports.numLit = numLit;
1791
1837
  exports.opSwapGroup = opSwapGroup;