@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.d.cts CHANGED
@@ -56,6 +56,12 @@ declare const findEnumPeer: typeof findChoicePeer;
56
56
  /** A name-form ref in ONE form, so two spellings of the same property compare equal: the default
57
57
  * scope may be written or left off (`@patter.debt` and `@debt`), and refs are case-insensitive. */
58
58
  declare const normaliseRef: (ref: string, defaultScope: string) => string;
59
+ /** The set/clear-a-flag value: `set_flags(@scope.name, +flag)` - adjust one
60
+ * flag, keep the rest, which is what outcomes on a flags property
61
+ * overwhelmingly do (and the form a whole-value change cannot express).
62
+ * Lives here with the other pure ref helpers so the value wizard can build
63
+ * it without importing the effects mount (which imports the wizard). */
64
+ declare const flagChangeSrc: (targetRef: string, sign: "+" | "-", flag: string) => string;
59
65
  /** The name-form ref a call ADVANCES, when the call is the plain `advance(@x)` shape: one scopedvar
60
66
  * argument and nothing else. Null for any other call, and for an argument that is not a property
61
67
  * (`advance(f(@x))` keeps its explicit form, since collapsing it would hide the middle).
@@ -183,11 +189,21 @@ interface Filter {
183
189
  acceptTypes?: PropertyType[];
184
190
  acceptScopes?: string[];
185
191
  }
186
- /** The reference string for an entry: `@name` for the default scope, else `@scope.name`. */
192
+ /** The reference string for an entry: `@name` for the default scope, else `@scope.name`.
193
+ * CONDITION-side sugar only: a change TARGET must use {@link targetRefOf} - the storylets
194
+ * compiler requires `@scope.name` there, and emitting this shorthand for a default-scope
195
+ * target made every picker-authored change invalid (the Storyletter antagonist audit's
196
+ * find, 2026-08-29). Patter's grammar takes both forms, so the split costs it nothing. */
187
197
  declare const refOf: (e: {
188
198
  scope: string;
189
199
  name: string;
190
200
  }, defaultScope: string) => string;
201
+ /** The reference a change TARGET is written as: always fully qualified. A target is a
202
+ * reference, not an expression, and the shorthand is condition-side sugar (see refOf). */
203
+ declare const targetRefOf: (e: {
204
+ scope: string;
205
+ name: string;
206
+ }) => string;
191
207
  /** The label shown in the picker / on a property pill. */
192
208
  declare const displayName: (e: {
193
209
  scope: string;
@@ -529,6 +545,13 @@ interface ValueWizardOptions {
529
545
  expectedChoices?: string[];
530
546
  /** @deprecated Use `expectedChoices`. Kept so a 0.11.x caller keeps working. */
531
547
  expectedEnumValues?: string[];
548
+ /** A FLAGS target's qualified ref and declared flags: together they unlock the
549
+ * set/clear-a-flag step, which is what outcomes on a flags property
550
+ * overwhelmingly want and which a whole-value change cannot express (the
551
+ * Storyletter antagonist audit's find, 2026-08-29). Absent, the generic
552
+ * kinds stand alone as before. */
553
+ expectedRef?: string;
554
+ expectedFlags?: string[];
532
555
  /** Receives the chosen value as name-form source. */
533
556
  onCommit: (src: string) => void;
534
557
  /** Optional cancel (the ✕ on the step). */
@@ -537,4 +560,4 @@ interface ValueWizardOptions {
537
560
  /** Build the wizard UI into a fresh element (drive it via the host's popover). */
538
561
  declare function valueWizard(opts: ValueWizardOptions): HTMLElement;
539
562
 
540
- export { ARITHMETIC_OPS, BINARY_LABEL, COMPARABLE_TYPES, COMPARISON_OPS, type CatalogueEntry, EQUALITY_OPS, type EditCtx, type EditorEffect, type EffectsEditorHandle, type EffectsEditorOptions, type ExpressionEditorHandle, type ExpressionEditorOptions, type Filter, type FunctionTemplateSpec, OP_WORD, type PreviewOptions, type TreeRow, UNARY_LABEL, type Validation, type ValueWizardOptions, type WizardSpec, type WizardStepSpec, type WizardValue, addArg, addChildToContainer, addEmit, addSet, advancedRef, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findChoicePeer, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, initialValueFor, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, normaliseRef, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, propertyTip, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
563
+ export { ARITHMETIC_OPS, BINARY_LABEL, COMPARABLE_TYPES, COMPARISON_OPS, type CatalogueEntry, EQUALITY_OPS, type EditCtx, type EditorEffect, type EffectsEditorHandle, type EffectsEditorOptions, type ExpressionEditorHandle, type ExpressionEditorOptions, type Filter, type FunctionTemplateSpec, OP_WORD, type PreviewOptions, type TreeRow, UNARY_LABEL, type Validation, type ValueWizardOptions, type WizardSpec, type WizardStepSpec, type WizardValue, addArg, addChildToContainer, addEmit, addSet, advancedRef, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findChoicePeer, findEnumPeer, firstEmptyLeafPath, flagChangeSrc, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, initialValueFor, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, normaliseRef, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, propertyTip, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, targetRefOf, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
package/dist/index.d.ts CHANGED
@@ -56,6 +56,12 @@ declare const findEnumPeer: typeof findChoicePeer;
56
56
  /** A name-form ref in ONE form, so two spellings of the same property compare equal: the default
57
57
  * scope may be written or left off (`@patter.debt` and `@debt`), and refs are case-insensitive. */
58
58
  declare const normaliseRef: (ref: string, defaultScope: string) => string;
59
+ /** The set/clear-a-flag value: `set_flags(@scope.name, +flag)` - adjust one
60
+ * flag, keep the rest, which is what outcomes on a flags property
61
+ * overwhelmingly do (and the form a whole-value change cannot express).
62
+ * Lives here with the other pure ref helpers so the value wizard can build
63
+ * it without importing the effects mount (which imports the wizard). */
64
+ declare const flagChangeSrc: (targetRef: string, sign: "+" | "-", flag: string) => string;
59
65
  /** The name-form ref a call ADVANCES, when the call is the plain `advance(@x)` shape: one scopedvar
60
66
  * argument and nothing else. Null for any other call, and for an argument that is not a property
61
67
  * (`advance(f(@x))` keeps its explicit form, since collapsing it would hide the middle).
@@ -183,11 +189,21 @@ interface Filter {
183
189
  acceptTypes?: PropertyType[];
184
190
  acceptScopes?: string[];
185
191
  }
186
- /** The reference string for an entry: `@name` for the default scope, else `@scope.name`. */
192
+ /** The reference string for an entry: `@name` for the default scope, else `@scope.name`.
193
+ * CONDITION-side sugar only: a change TARGET must use {@link targetRefOf} - the storylets
194
+ * compiler requires `@scope.name` there, and emitting this shorthand for a default-scope
195
+ * target made every picker-authored change invalid (the Storyletter antagonist audit's
196
+ * find, 2026-08-29). Patter's grammar takes both forms, so the split costs it nothing. */
187
197
  declare const refOf: (e: {
188
198
  scope: string;
189
199
  name: string;
190
200
  }, defaultScope: string) => string;
201
+ /** The reference a change TARGET is written as: always fully qualified. A target is a
202
+ * reference, not an expression, and the shorthand is condition-side sugar (see refOf). */
203
+ declare const targetRefOf: (e: {
204
+ scope: string;
205
+ name: string;
206
+ }) => string;
191
207
  /** The label shown in the picker / on a property pill. */
192
208
  declare const displayName: (e: {
193
209
  scope: string;
@@ -529,6 +545,13 @@ interface ValueWizardOptions {
529
545
  expectedChoices?: string[];
530
546
  /** @deprecated Use `expectedChoices`. Kept so a 0.11.x caller keeps working. */
531
547
  expectedEnumValues?: string[];
548
+ /** A FLAGS target's qualified ref and declared flags: together they unlock the
549
+ * set/clear-a-flag step, which is what outcomes on a flags property
550
+ * overwhelmingly want and which a whole-value change cannot express (the
551
+ * Storyletter antagonist audit's find, 2026-08-29). Absent, the generic
552
+ * kinds stand alone as before. */
553
+ expectedRef?: string;
554
+ expectedFlags?: string[];
532
555
  /** Receives the chosen value as name-form source. */
533
556
  onCommit: (src: string) => void;
534
557
  /** Optional cancel (the ✕ on the step). */
@@ -537,4 +560,4 @@ interface ValueWizardOptions {
537
560
  /** Build the wizard UI into a fresh element (drive it via the host's popover). */
538
561
  declare function valueWizard(opts: ValueWizardOptions): HTMLElement;
539
562
 
540
- export { ARITHMETIC_OPS, BINARY_LABEL, COMPARABLE_TYPES, COMPARISON_OPS, type CatalogueEntry, EQUALITY_OPS, type EditCtx, type EditorEffect, type EffectsEditorHandle, type EffectsEditorOptions, type ExpressionEditorHandle, type ExpressionEditorOptions, type Filter, type FunctionTemplateSpec, OP_WORD, type PreviewOptions, type TreeRow, UNARY_LABEL, type Validation, type ValueWizardOptions, type WizardSpec, type WizardStepSpec, type WizardValue, addArg, addChildToContainer, addEmit, addSet, advancedRef, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findChoicePeer, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, initialValueFor, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, normaliseRef, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, propertyTip, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
563
+ export { ARITHMETIC_OPS, BINARY_LABEL, COMPARABLE_TYPES, COMPARISON_OPS, type CatalogueEntry, EQUALITY_OPS, type EditCtx, type EditorEffect, type EffectsEditorHandle, type EffectsEditorOptions, type ExpressionEditorHandle, type ExpressionEditorOptions, type Filter, type FunctionTemplateSpec, OP_WORD, type PreviewOptions, type TreeRow, UNARY_LABEL, type Validation, type ValueWizardOptions, type WizardSpec, type WizardStepSpec, type WizardValue, addArg, addChildToContainer, addEmit, addSet, advancedRef, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findChoicePeer, findEnumPeer, firstEmptyLeafPath, flagChangeSrc, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, initialValueFor, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, normaliseRef, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, propertyTip, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, targetRefOf, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
package/dist/index.js CHANGED
@@ -96,6 +96,7 @@ var normaliseRef = (ref, defaultScope) => {
96
96
  const prefix = `@${defaultScope.toLowerCase()}.`;
97
97
  return low.startsWith(prefix) ? `@${low.slice(prefix.length)}` : low;
98
98
  };
99
+ var flagChangeSrc = (targetRef, sign, flag) => `set_flags(${targetRef}, ${sign}${flag})`;
99
100
  function advancedRef(node, defaultScope) {
100
101
  if (node.kind !== "call" || node.name !== "advance" || node.args.length !== 1) return null;
101
102
  const arg = node.args[0];
@@ -294,6 +295,7 @@ var propertyTip = (e) => {
294
295
  return lines.length ? lines.join("\n") : void 0;
295
296
  };
296
297
  var refOf = (e, defaultScope) => e.scope === defaultScope ? `@${e.name}` : `@${e.scope}.${e.name}`;
298
+ var targetRefOf = (e) => `@${e.scope}.${e.name}`;
297
299
  var displayName = (e, defaultScope) => e.scope === defaultScope ? e.name : `${e.scope}.${e.name}`;
298
300
  function filterCatalogue(entries, filter = {}) {
299
301
  return entries.filter(
@@ -427,6 +429,14 @@ function textField(opts) {
427
429
  setTimeout(() => input.focus(), 0);
428
430
  return wrap;
429
431
  }
432
+ function propertyMenuBody(actions, close) {
433
+ const wrap = el("div", "exed-menu");
434
+ for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
435
+ a.run();
436
+ close();
437
+ }));
438
+ return wrap;
439
+ }
430
440
 
431
441
  // src/flat.ts
432
442
  var FLAG_FNS = /* @__PURE__ */ new Set(["check_flags", "set_flags"]);
@@ -545,14 +555,7 @@ function attachPropertyMenu(ctx, node, pillEl) {
545
555
  const open = ctx.openMenu ?? ctx.openPopover;
546
556
  pillEl.addEventListener("contextmenu", (e) => {
547
557
  e.preventDefault();
548
- open(pillEl, (close) => {
549
- const wrap = el("div", "exed-menu");
550
- for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
551
- a.run();
552
- close();
553
- }));
554
- return wrap;
555
- });
558
+ open(pillEl, (close) => propertyMenuBody(actions, close));
556
559
  });
557
560
  }
558
561
  function isCompoundComparison(node, ctx) {
@@ -1081,7 +1084,7 @@ function seedClause(ctx) {
1081
1084
  function clauseMenu(ctx, anchor, onPick) {
1082
1085
  ctx.openPopover(anchor, (close) => {
1083
1086
  const wrap = el("div", "exed-menu");
1084
- wrap.append(el("div", "exed-menu-head", ["Add a clause"]));
1087
+ wrap.append(el("div", "exed-menu-head", ["Add a condition"]));
1085
1088
  const add = (label, hint, make, disabled = false) => {
1086
1089
  const b = button(`exed-opt${disabled ? " disabled" : ""}`, "", () => {
1087
1090
  if (disabled) return;
@@ -1380,7 +1383,8 @@ function valueWizard(opts) {
1380
1383
  host.append(body);
1381
1384
  const choices = opts.expectedChoices?.length ? opts.expectedChoices : opts.expectedEnumValues;
1382
1385
  const isStage = opts.expectedType === "quality";
1383
- let kind = opts.expectedType === "number" ? "number" : opts.expectedType === "string" ? "text" : opts.expectedType === "boolean" ? "bool" : (opts.expectedType === "enum" || opts.expectedType === "quality") && choices?.length ? "enum" : "menu";
1386
+ const flagsReady = opts.expectedType === "flags" && opts.expectedRef !== void 0;
1387
+ 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";
1384
1388
  const draw = () => {
1385
1389
  body.replaceChildren();
1386
1390
  const other = () => button("exed-vwiz-other", "\u21A9 a different kind", () => {
@@ -1389,6 +1393,10 @@ function valueWizard(opts) {
1389
1393
  });
1390
1394
  switch (kind) {
1391
1395
  case "menu":
1396
+ if (flagsReady) body.append(optBtn("Set or clear a flag\u2026", () => {
1397
+ kind = "flags";
1398
+ draw();
1399
+ }));
1392
1400
  body.append(optBtn("A property\u2026", () => {
1393
1401
  kind = "property";
1394
1402
  draw();
@@ -1410,6 +1418,22 @@ function valueWizard(opts) {
1410
1418
  draw();
1411
1419
  }));
1412
1420
  break;
1421
+ case "flags": {
1422
+ const flags = opts.expectedFlags ?? [];
1423
+ if (!flags.length) body.append(el("div", "exed-hint", ["This property declares no flag values yet."]));
1424
+ for (const sign of ["+", "-"]) {
1425
+ if (!flags.length) break;
1426
+ body.append(el("div", "exed-vwiz-note", [sign === "+" ? "Set a flag:" : "Clear a flag:"]));
1427
+ const row = el("div", "exed-field-row");
1428
+ for (const f of flags) row.append(optBtn(`${sign} ${f}`, () => opts.onCommit(flagChangeSrc(opts.expectedRef, sign, f))));
1429
+ body.append(row);
1430
+ }
1431
+ body.append(button("exed-vwiz-other", "\u21A9 a different kind (replaces every flag)", () => {
1432
+ kind = "menu";
1433
+ draw();
1434
+ }));
1435
+ break;
1436
+ }
1413
1437
  case "property":
1414
1438
  body.append(propertyPicker(pickCtx, { onPick: (e) => opts.onCommit(refOf(e, opts.defaultScope)) }));
1415
1439
  body.append(other());
@@ -1448,6 +1472,10 @@ function isSelfAdvance(eff, defaultScope, dialect) {
1448
1472
  return false;
1449
1473
  }
1450
1474
  }
1475
+ function entryForTarget(catalogue, target, defaultScope) {
1476
+ const want = normaliseRef(target, defaultScope);
1477
+ return catalogue.find((e) => normaliseRef(`@${e.scope}.${e.name}`, defaultScope) === want);
1478
+ }
1451
1479
  var clone = (list) => list.map((e) => e.kind === "set" ? { ...e } : { ...e, args: [...e.args] });
1452
1480
  var addSet = (list, target, value) => [...clone(list), { kind: "set", target, value }];
1453
1481
  var addEmit = (list, event) => [...clone(list), { kind: "emit", event, args: [] }];
@@ -1536,6 +1564,11 @@ function mountEffectsEditor(host, opts) {
1536
1564
  // choicesOf, never a raw field: a quality's ladder lives in `stages`, and reading `enumValues`
1537
1565
  // here told the wizard the type and withheld the values.
1538
1566
  ...choicesOf(expected) ? { expectedChoices: choicesOf(expected) } : {},
1567
+ // The flags step needs the target itself: its declared flags, and the
1568
+ // qualified ref its set_flags(...) is written against. choicesOf stays
1569
+ // out of it deliberately - flags are not a pick-one list, and a pinned
1570
+ // test holds it to that.
1571
+ ...expected?.type === "flags" ? { expectedRef: targetRefOf(expected), expectedFlags: expected.enumValues ?? [] } : {},
1539
1572
  onCommit: (src) => {
1540
1573
  onCommit(src);
1541
1574
  close();
@@ -1543,7 +1576,7 @@ function mountEffectsEditor(host, opts) {
1543
1576
  onCancel: close
1544
1577
  }), { container: opts.popoverContainer });
1545
1578
  };
1546
- const targetType = (target) => opts.catalogue.find((e) => refOf(e, defaultScope) === target)?.type;
1579
+ const targetType = (target) => entryForTarget(opts.catalogue, target, defaultScope)?.type;
1547
1580
  const valueDisplay = (value, onChange, type, selfAdvanceRef) => {
1548
1581
  const sub = el("div", "exed-effect-value");
1549
1582
  const addTerm = type === "number" ? "arithmetic" : type === "boolean" ? "boolean" : void 0;
@@ -1610,10 +1643,10 @@ function mountEffectsEditor(host, opts) {
1610
1643
  const row = el("div", "exed-effect exed-effect-set");
1611
1644
  const targetBtn = button("exed-pill exed-pill-prop", eff.target || "(pick property)", (e) => {
1612
1645
  pickTarget(e.currentTarget, void 0, (entry) => {
1613
- commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value: initialValueFor(entry, defaultScope).src }));
1646
+ commit(updateAt(effects, i, { target: targetRefOf(entry), value: initialValueFor(entry, defaultScope).src }));
1614
1647
  });
1615
1648
  }, "change the target property");
1616
- const targetEntry = opts.catalogue.find((e) => refOf(e, defaultScope) === eff.target);
1649
+ const targetEntry = entryForTarget(opts.catalogue, eff.target, defaultScope);
1617
1650
  const tip = propertyTip(targetEntry);
1618
1651
  if (tip) targetBtn.title = tip;
1619
1652
  if (targetEntry && opts.propertyActions) {
@@ -1621,14 +1654,7 @@ function mountEffectsEditor(host, opts) {
1621
1654
  if (actions.length) targetBtn.addEventListener("contextmenu", (e) => {
1622
1655
  e.preventDefault();
1623
1656
  closePopover();
1624
- popover = openPopover(targetBtn, (close) => {
1625
- const wrap = el("div", "exed-menu");
1626
- for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
1627
- a.run();
1628
- close();
1629
- }));
1630
- return wrap;
1631
- }, { container: opts.popoverContainer });
1657
+ popover = openPopover(targetBtn, (close) => propertyMenuBody(actions, close), { container: opts.popoverContainer });
1632
1658
  });
1633
1659
  }
1634
1660
  const advances = isSelfAdvance(eff, defaultScope, opts.dialect);
@@ -1724,10 +1750,10 @@ function mountEffectsEditor(host, opts) {
1724
1750
  pickTarget(anchor, void 0, (entry) => {
1725
1751
  const { src, ask } = initialValueFor(entry, defaultScope);
1726
1752
  if (!ask) {
1727
- commit(addSet(effects, refOf(entry, defaultScope), src));
1753
+ commit(addSet(effects, targetRefOf(entry), src));
1728
1754
  return;
1729
1755
  }
1730
- openValueWizard(anchor, entry, (chosen) => commit(addSet(effects, refOf(entry, defaultScope), chosen)));
1756
+ openValueWizard(anchor, entry, (chosen) => commit(addSet(effects, targetRefOf(entry), chosen)));
1731
1757
  });
1732
1758
  }));
1733
1759
  if (opts.allowEmit !== false) {
@@ -1794,13 +1820,26 @@ function renderConditionPreview(src, o) {
1794
1820
  const cls = o.propertyActions ? "exed-preview exed-preview-interactive" : "exed-preview";
1795
1821
  return el("div", cls, [exprPills(src, o)]);
1796
1822
  }
1823
+ function targetPill(ref, o) {
1824
+ const pill2 = el("span", "exed-pill exed-pill-prop", [ref || "(property)"]);
1825
+ const entry = o.catalogue.find((e) => refOf(e, o.dialect.defaultScope) === ref);
1826
+ if (!entry) return pill2;
1827
+ const tip = propertyTip(entry);
1828
+ if (tip) pill2.title = tip;
1829
+ const actions = o.propertyActions?.({ scope: entry.scope, name: entry.name }) ?? [];
1830
+ if (actions.length) pill2.addEventListener("contextmenu", (e) => {
1831
+ e.preventDefault();
1832
+ openPopover(pill2, (close) => propertyMenuBody(actions, close), { container: o.popoverContainer });
1833
+ });
1834
+ return pill2;
1835
+ }
1797
1836
  function renderEffectsPreview(effects, o) {
1798
- const wrap = el("div", "exed-preview exed-preview-effects");
1837
+ const wrap = el("div", `exed-preview exed-preview-effects${o.propertyActions ? " exed-preview-interactive" : ""}`);
1799
1838
  for (const eff of effects) {
1800
1839
  const row = el("div", "exed-preview-eff");
1801
1840
  if (eff.kind === "set") {
1802
1841
  const advances = isSelfAdvance(eff, o.dialect.defaultScope, o.dialect);
1803
- row.append(el("span", "exed-pill exed-pill-prop", [eff.target || "(property)"]));
1842
+ row.append(targetPill(eff.target, o));
1804
1843
  if (!advances) row.append(el("span", "exed-effect-eq", ["="]));
1805
1844
  row.append(exprPills(eff.value, o, advances ? eff.target : void 0));
1806
1845
  } else {
@@ -1818,6 +1857,6 @@ function renderEffectsPreview(effects, o) {
1818
1857
  return wrap;
1819
1858
  }
1820
1859
 
1821
- export { ARITHMETIC_OPS, BINARY_LABEL, COMPARABLE_TYPES, COMPARISON_OPS, EQUALITY_OPS, OP_WORD, UNARY_LABEL, addArg, addChildToContainer, addEmit, addSet, advancedRef, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findChoicePeer, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, initialValueFor, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, normaliseRef, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, propertyTip, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
1860
+ export { ARITHMETIC_OPS, BINARY_LABEL, COMPARABLE_TYPES, COMPARISON_OPS, EQUALITY_OPS, OP_WORD, UNARY_LABEL, addArg, addChildToContainer, addEmit, addSet, advancedRef, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findChoicePeer, findEnumPeer, firstEmptyLeafPath, flagChangeSrc, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, initialValueFor, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, normaliseRef, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, propertyTip, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, targetRefOf, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
1822
1861
  //# sourceMappingURL=index.js.map
1823
1862
  //# sourceMappingURL=index.js.map