@wildwinter/expr-editor 0.14.0 → 0.15.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
@@ -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(
@@ -429,6 +431,14 @@ function textField(opts) {
429
431
  setTimeout(() => input.focus(), 0);
430
432
  return wrap;
431
433
  }
434
+ function propertyMenuBody(actions, close) {
435
+ const wrap = el("div", "exed-menu");
436
+ for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
437
+ a.run();
438
+ close();
439
+ }));
440
+ return wrap;
441
+ }
432
442
 
433
443
  // src/flat.ts
434
444
  var FLAG_FNS = /* @__PURE__ */ new Set(["check_flags", "set_flags"]);
@@ -547,14 +557,7 @@ function attachPropertyMenu(ctx, node, pillEl) {
547
557
  const open = ctx.openMenu ?? ctx.openPopover;
548
558
  pillEl.addEventListener("contextmenu", (e) => {
549
559
  e.preventDefault();
550
- open(pillEl, (close) => {
551
- const wrap = el("div", "exed-menu");
552
- for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
553
- a.run();
554
- close();
555
- }));
556
- return wrap;
557
- });
560
+ open(pillEl, (close) => propertyMenuBody(actions, close));
558
561
  });
559
562
  }
560
563
  function isCompoundComparison(node, ctx) {
@@ -1083,7 +1086,7 @@ function seedClause(ctx) {
1083
1086
  function clauseMenu(ctx, anchor, onPick) {
1084
1087
  ctx.openPopover(anchor, (close) => {
1085
1088
  const wrap = el("div", "exed-menu");
1086
- wrap.append(el("div", "exed-menu-head", ["Add a clause"]));
1089
+ wrap.append(el("div", "exed-menu-head", ["Add a condition"]));
1087
1090
  const add = (label, hint, make, disabled = false) => {
1088
1091
  const b = button(`exed-opt${disabled ? " disabled" : ""}`, "", () => {
1089
1092
  if (disabled) return;
@@ -1382,7 +1385,8 @@ function valueWizard(opts) {
1382
1385
  host.append(body);
1383
1386
  const choices = opts.expectedChoices?.length ? opts.expectedChoices : opts.expectedEnumValues;
1384
1387
  const isStage = opts.expectedType === "quality";
1385
- let kind = opts.expectedType === "number" ? "number" : opts.expectedType === "string" ? "text" : opts.expectedType === "boolean" ? "bool" : (opts.expectedType === "enum" || opts.expectedType === "quality") && choices?.length ? "enum" : "menu";
1388
+ const flagsReady = opts.expectedType === "flags" && opts.expectedRef !== void 0;
1389
+ 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";
1386
1390
  const draw = () => {
1387
1391
  body.replaceChildren();
1388
1392
  const other = () => button("exed-vwiz-other", "\u21A9 a different kind", () => {
@@ -1391,6 +1395,10 @@ function valueWizard(opts) {
1391
1395
  });
1392
1396
  switch (kind) {
1393
1397
  case "menu":
1398
+ if (flagsReady) body.append(optBtn("Set or clear a flag\u2026", () => {
1399
+ kind = "flags";
1400
+ draw();
1401
+ }));
1394
1402
  body.append(optBtn("A property\u2026", () => {
1395
1403
  kind = "property";
1396
1404
  draw();
@@ -1412,6 +1420,22 @@ function valueWizard(opts) {
1412
1420
  draw();
1413
1421
  }));
1414
1422
  break;
1423
+ case "flags": {
1424
+ const flags = opts.expectedFlags ?? [];
1425
+ if (!flags.length) body.append(el("div", "exed-hint", ["This property declares no flag values yet."]));
1426
+ for (const sign of ["+", "-"]) {
1427
+ if (!flags.length) break;
1428
+ body.append(el("div", "exed-vwiz-note", [sign === "+" ? "Set a flag:" : "Clear a flag:"]));
1429
+ const row = el("div", "exed-field-row");
1430
+ for (const f of flags) row.append(optBtn(`${sign} ${f}`, () => opts.onCommit(flagChangeSrc(opts.expectedRef, sign, f))));
1431
+ body.append(row);
1432
+ }
1433
+ body.append(button("exed-vwiz-other", "\u21A9 a different kind (replaces every flag)", () => {
1434
+ kind = "menu";
1435
+ draw();
1436
+ }));
1437
+ break;
1438
+ }
1415
1439
  case "property":
1416
1440
  body.append(propertyPicker(pickCtx, { onPick: (e) => opts.onCommit(refOf(e, opts.defaultScope)) }));
1417
1441
  body.append(other());
@@ -1450,6 +1474,10 @@ function isSelfAdvance(eff, defaultScope, dialect) {
1450
1474
  return false;
1451
1475
  }
1452
1476
  }
1477
+ function entryForTarget(catalogue, target, defaultScope) {
1478
+ const want = normaliseRef(target, defaultScope);
1479
+ return catalogue.find((e) => normaliseRef(`@${e.scope}.${e.name}`, defaultScope) === want);
1480
+ }
1453
1481
  var clone = (list) => list.map((e) => e.kind === "set" ? { ...e } : { ...e, args: [...e.args] });
1454
1482
  var addSet = (list, target, value) => [...clone(list), { kind: "set", target, value }];
1455
1483
  var addEmit = (list, event) => [...clone(list), { kind: "emit", event, args: [] }];
@@ -1538,6 +1566,11 @@ function mountEffectsEditor(host, opts) {
1538
1566
  // choicesOf, never a raw field: a quality's ladder lives in `stages`, and reading `enumValues`
1539
1567
  // here told the wizard the type and withheld the values.
1540
1568
  ...choicesOf(expected) ? { expectedChoices: choicesOf(expected) } : {},
1569
+ // The flags step needs the target itself: its declared flags, and the
1570
+ // qualified ref its set_flags(...) is written against. choicesOf stays
1571
+ // out of it deliberately - flags are not a pick-one list, and a pinned
1572
+ // test holds it to that.
1573
+ ...expected?.type === "flags" ? { expectedRef: targetRefOf(expected), expectedFlags: expected.enumValues ?? [] } : {},
1541
1574
  onCommit: (src) => {
1542
1575
  onCommit(src);
1543
1576
  close();
@@ -1545,7 +1578,7 @@ function mountEffectsEditor(host, opts) {
1545
1578
  onCancel: close
1546
1579
  }), { container: opts.popoverContainer });
1547
1580
  };
1548
- const targetType = (target) => opts.catalogue.find((e) => refOf(e, defaultScope) === target)?.type;
1581
+ const targetType = (target) => entryForTarget(opts.catalogue, target, defaultScope)?.type;
1549
1582
  const valueDisplay = (value, onChange, type, selfAdvanceRef) => {
1550
1583
  const sub = el("div", "exed-effect-value");
1551
1584
  const addTerm = type === "number" ? "arithmetic" : type === "boolean" ? "boolean" : void 0;
@@ -1612,10 +1645,10 @@ function mountEffectsEditor(host, opts) {
1612
1645
  const row = el("div", "exed-effect exed-effect-set");
1613
1646
  const targetBtn = button("exed-pill exed-pill-prop", eff.target || "(pick property)", (e) => {
1614
1647
  pickTarget(e.currentTarget, void 0, (entry) => {
1615
- commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value: initialValueFor(entry, defaultScope).src }));
1648
+ commit(updateAt(effects, i, { target: targetRefOf(entry), value: initialValueFor(entry, defaultScope).src }));
1616
1649
  });
1617
1650
  }, "change the target property");
1618
- const targetEntry = opts.catalogue.find((e) => refOf(e, defaultScope) === eff.target);
1651
+ const targetEntry = entryForTarget(opts.catalogue, eff.target, defaultScope);
1619
1652
  const tip = propertyTip(targetEntry);
1620
1653
  if (tip) targetBtn.title = tip;
1621
1654
  if (targetEntry && opts.propertyActions) {
@@ -1623,14 +1656,7 @@ function mountEffectsEditor(host, opts) {
1623
1656
  if (actions.length) targetBtn.addEventListener("contextmenu", (e) => {
1624
1657
  e.preventDefault();
1625
1658
  closePopover();
1626
- popover = openPopover(targetBtn, (close) => {
1627
- const wrap = el("div", "exed-menu");
1628
- for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
1629
- a.run();
1630
- close();
1631
- }));
1632
- return wrap;
1633
- }, { container: opts.popoverContainer });
1659
+ popover = openPopover(targetBtn, (close) => propertyMenuBody(actions, close), { container: opts.popoverContainer });
1634
1660
  });
1635
1661
  }
1636
1662
  const advances = isSelfAdvance(eff, defaultScope, opts.dialect);
@@ -1726,10 +1752,10 @@ function mountEffectsEditor(host, opts) {
1726
1752
  pickTarget(anchor, void 0, (entry) => {
1727
1753
  const { src, ask } = initialValueFor(entry, defaultScope);
1728
1754
  if (!ask) {
1729
- commit(addSet(effects, refOf(entry, defaultScope), src));
1755
+ commit(addSet(effects, targetRefOf(entry), src));
1730
1756
  return;
1731
1757
  }
1732
- openValueWizard(anchor, entry, (chosen) => commit(addSet(effects, refOf(entry, defaultScope), chosen)));
1758
+ openValueWizard(anchor, entry, (chosen) => commit(addSet(effects, targetRefOf(entry), chosen)));
1733
1759
  });
1734
1760
  }));
1735
1761
  if (opts.allowEmit !== false) {
@@ -1796,13 +1822,26 @@ function renderConditionPreview(src, o) {
1796
1822
  const cls = o.propertyActions ? "exed-preview exed-preview-interactive" : "exed-preview";
1797
1823
  return el("div", cls, [exprPills(src, o)]);
1798
1824
  }
1825
+ function targetPill(ref, o) {
1826
+ const pill2 = el("span", "exed-pill exed-pill-prop", [ref || "(property)"]);
1827
+ const entry = o.catalogue.find((e) => refOf(e, o.dialect.defaultScope) === ref);
1828
+ if (!entry) return pill2;
1829
+ const tip = propertyTip(entry);
1830
+ if (tip) pill2.title = tip;
1831
+ const actions = o.propertyActions?.({ scope: entry.scope, name: entry.name }) ?? [];
1832
+ if (actions.length) pill2.addEventListener("contextmenu", (e) => {
1833
+ e.preventDefault();
1834
+ openPopover(pill2, (close) => propertyMenuBody(actions, close), { container: o.popoverContainer });
1835
+ });
1836
+ return pill2;
1837
+ }
1799
1838
  function renderEffectsPreview(effects, o) {
1800
- const wrap = el("div", "exed-preview exed-preview-effects");
1839
+ const wrap = el("div", `exed-preview exed-preview-effects${o.propertyActions ? " exed-preview-interactive" : ""}`);
1801
1840
  for (const eff of effects) {
1802
1841
  const row = el("div", "exed-preview-eff");
1803
1842
  if (eff.kind === "set") {
1804
1843
  const advances = isSelfAdvance(eff, o.dialect.defaultScope, o.dialect);
1805
- row.append(el("span", "exed-pill exed-pill-prop", [eff.target || "(property)"]));
1844
+ row.append(targetPill(eff.target, o));
1806
1845
  if (!advances) row.append(el("span", "exed-effect-eq", ["="]));
1807
1846
  row.append(exprPills(eff.value, o, advances ? eff.target : void 0));
1808
1847
  } else {
@@ -1845,6 +1884,7 @@ exports.filterCatalogue = filterCatalogue;
1845
1884
  exports.findChoicePeer = findChoicePeer;
1846
1885
  exports.findEnumPeer = findEnumPeer;
1847
1886
  exports.firstEmptyLeafPath = firstEmptyLeafPath;
1887
+ exports.flagChangeSrc = flagChangeSrc;
1848
1888
  exports.flagDelta = flagDelta;
1849
1889
  exports.flipContainerOp = flipContainerOp;
1850
1890
  exports.formatNumber = formatNumber;
@@ -1882,6 +1922,7 @@ exports.seedValueSrc = seedValueSrc;
1882
1922
  exports.setArgAt = setArgAt;
1883
1923
  exports.setNodeAt = setNodeAt;
1884
1924
  exports.strLit = strLit;
1925
+ exports.targetRefOf = targetRefOf;
1885
1926
  exports.toggleContainerNot = toggleContainerNot;
1886
1927
  exports.toggleNotAt = toggleNotAt;
1887
1928
  exports.updateAt = updateAt;