@wildwinter/expr-editor 0.13.1 → 0.14.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.d.cts CHANGED
@@ -169,6 +169,16 @@ declare const choicesOf: (e: {
169
169
  enumValues?: string[];
170
170
  stages?: string[];
171
171
  } | null | undefined) => string[] | undefined;
172
+ /** What a property PILL says on rollover: the purpose, and for a quality its
173
+ * ladder (the one thing about a property whose order matters and which the
174
+ * use site cannot otherwise show). The picker already displays purposes; this
175
+ * is for the place an author actually meets a property, mid-expression.
176
+ * Pure, so hosts and tests share one answer; the pill wires it as its title. */
177
+ declare const propertyTip: (e: {
178
+ type: PropertyType;
179
+ purpose?: string;
180
+ stages?: string[];
181
+ } | null | undefined) => string | undefined;
172
182
  interface Filter {
173
183
  acceptTypes?: PropertyType[];
174
184
  acceptScopes?: string[];
@@ -426,6 +436,16 @@ interface EffectsEditorOptions {
426
436
  * inside a focus-trapping dialog so popovers do not dismiss it. Threaded to the
427
437
  * target/event pickers here and to every inline value editor. */
428
438
  popoverContainer?: HTMLElement;
439
+ /** Host actions for a property pill, shown on right-click (e.g. go to
440
+ * definition). Threaded to every inline value editor, and honoured by the
441
+ * set rows' own target pills (resolved via the catalogue). */
442
+ propertyActions?(ref: {
443
+ scope: string;
444
+ name: string;
445
+ }): Array<{
446
+ label: string;
447
+ run: () => void;
448
+ }>;
429
449
  /** Emitted on every structural / value edit with the whole new list. */
430
450
  onChange: (effects: EditorEffect[]) => void;
431
451
  }
@@ -517,4 +537,4 @@ interface ValueWizardOptions {
517
537
  /** Build the wizard UI into a fresh element (drive it via the host's popover). */
518
538
  declare function valueWizard(opts: ValueWizardOptions): HTMLElement;
519
539
 
520
- 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, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
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 };
package/dist/index.d.ts CHANGED
@@ -169,6 +169,16 @@ declare const choicesOf: (e: {
169
169
  enumValues?: string[];
170
170
  stages?: string[];
171
171
  } | null | undefined) => string[] | undefined;
172
+ /** What a property PILL says on rollover: the purpose, and for a quality its
173
+ * ladder (the one thing about a property whose order matters and which the
174
+ * use site cannot otherwise show). The picker already displays purposes; this
175
+ * is for the place an author actually meets a property, mid-expression.
176
+ * Pure, so hosts and tests share one answer; the pill wires it as its title. */
177
+ declare const propertyTip: (e: {
178
+ type: PropertyType;
179
+ purpose?: string;
180
+ stages?: string[];
181
+ } | null | undefined) => string | undefined;
172
182
  interface Filter {
173
183
  acceptTypes?: PropertyType[];
174
184
  acceptScopes?: string[];
@@ -426,6 +436,16 @@ interface EffectsEditorOptions {
426
436
  * inside a focus-trapping dialog so popovers do not dismiss it. Threaded to the
427
437
  * target/event pickers here and to every inline value editor. */
428
438
  popoverContainer?: HTMLElement;
439
+ /** Host actions for a property pill, shown on right-click (e.g. go to
440
+ * definition). Threaded to every inline value editor, and honoured by the
441
+ * set rows' own target pills (resolved via the catalogue). */
442
+ propertyActions?(ref: {
443
+ scope: string;
444
+ name: string;
445
+ }): Array<{
446
+ label: string;
447
+ run: () => void;
448
+ }>;
429
449
  /** Emitted on every structural / value edit with the whole new list. */
430
450
  onChange: (effects: EditorEffect[]) => void;
431
451
  }
@@ -517,4 +537,4 @@ interface ValueWizardOptions {
517
537
  /** Build the wizard UI into a fresh element (drive it via the host's popover). */
518
538
  declare function valueWizard(opts: ValueWizardOptions): HTMLElement;
519
539
 
520
- 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, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
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 };
package/dist/index.js CHANGED
@@ -286,6 +286,13 @@ var choicesOf = (e) => {
286
286
  if (e.type === "enum") return e.enumValues;
287
287
  return void 0;
288
288
  };
289
+ var propertyTip = (e) => {
290
+ if (!e) return void 0;
291
+ const lines = [];
292
+ if (e.purpose) lines.push(e.purpose);
293
+ if (e.type === "quality" && e.stages?.length) lines.push(`Stages: ${e.stages.join(" \u2192 ")}`);
294
+ return lines.length ? lines.join("\n") : void 0;
295
+ };
289
296
  var refOf = (e, defaultScope) => e.scope === defaultScope ? `@${e.name}` : `@${e.scope}.${e.name}`;
290
297
  var displayName = (e, defaultScope) => e.scope === defaultScope ? e.name : `${e.scope}.${e.name}`;
291
298
  function filterCatalogue(entries, filter = {}) {
@@ -420,6 +427,14 @@ function textField(opts) {
420
427
  setTimeout(() => input.focus(), 0);
421
428
  return wrap;
422
429
  }
430
+ function propertyMenuBody(actions, close) {
431
+ const wrap = el("div", "exed-menu");
432
+ for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
433
+ a.run();
434
+ close();
435
+ }));
436
+ return wrap;
437
+ }
423
438
 
424
439
  // src/flat.ts
425
440
  var FLAG_FNS = /* @__PURE__ */ new Set(["check_flags", "set_flags"]);
@@ -457,7 +472,12 @@ function renderNode(node, path, ctx, parentOp, side) {
457
472
  case "scopedvar": {
458
473
  const entry = lookup(ctx.catalogue, node.scope, node.name);
459
474
  const label = displayName({ scope: node.scope, name: node.name }, ctx.defaultScope);
460
- const varPill = pill("var", label, (b) => variableEditor(ctx, path, node, b), { issue: issue ?? (entry ? void 0 : `Unknown property ${label}`) });
475
+ const varPill = pill(
476
+ "var",
477
+ label,
478
+ (b) => variableEditor(ctx, path, node, b),
479
+ { issue: issue ?? (entry ? void 0 : `Unknown property ${label}`), title: propertyTip(entry) }
480
+ );
461
481
  attachPropertyMenu(ctx, node, varPill);
462
482
  return varPill;
463
483
  }
@@ -533,14 +553,7 @@ function attachPropertyMenu(ctx, node, pillEl) {
533
553
  const open = ctx.openMenu ?? ctx.openPopover;
534
554
  pillEl.addEventListener("contextmenu", (e) => {
535
555
  e.preventDefault();
536
- open(pillEl, (close) => {
537
- const wrap = el("div", "exed-menu");
538
- for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
539
- a.run();
540
- close();
541
- }));
542
- return wrap;
543
- });
556
+ open(pillEl, (close) => propertyMenuBody(actions, close));
544
557
  });
545
558
  }
546
559
  function isCompoundComparison(node, ctx) {
@@ -1550,6 +1563,7 @@ function mountEffectsEditor(host, opts) {
1550
1563
  valueField: true,
1551
1564
  ...addTerm ? { addTerm } : {},
1552
1565
  ...selfAdvanceRef ? { selfAdvanceRef } : {},
1566
+ ...opts.propertyActions ? { propertyActions: opts.propertyActions } : {},
1553
1567
  ...opts.popoverContainer ? { popoverContainer: opts.popoverContainer } : {},
1554
1568
  text: textMode,
1555
1569
  onChange
@@ -1600,6 +1614,17 @@ function mountEffectsEditor(host, opts) {
1600
1614
  commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value: initialValueFor(entry, defaultScope).src }));
1601
1615
  });
1602
1616
  }, "change the target property");
1617
+ const targetEntry = opts.catalogue.find((e) => refOf(e, defaultScope) === eff.target);
1618
+ const tip = propertyTip(targetEntry);
1619
+ if (tip) targetBtn.title = tip;
1620
+ if (targetEntry && opts.propertyActions) {
1621
+ const actions = opts.propertyActions({ scope: targetEntry.scope, name: targetEntry.name });
1622
+ if (actions.length) targetBtn.addEventListener("contextmenu", (e) => {
1623
+ e.preventDefault();
1624
+ closePopover();
1625
+ popover = openPopover(targetBtn, (close) => propertyMenuBody(actions, close), { container: opts.popoverContainer });
1626
+ });
1627
+ }
1603
1628
  const advances = isSelfAdvance(eff, defaultScope, opts.dialect);
1604
1629
  row.append(targetBtn);
1605
1630
  if (!advances) row.append(el("span", "exed-effect-eq", ["="]));
@@ -1763,13 +1788,26 @@ function renderConditionPreview(src, o) {
1763
1788
  const cls = o.propertyActions ? "exed-preview exed-preview-interactive" : "exed-preview";
1764
1789
  return el("div", cls, [exprPills(src, o)]);
1765
1790
  }
1791
+ function targetPill(ref, o) {
1792
+ const pill2 = el("span", "exed-pill exed-pill-prop", [ref || "(property)"]);
1793
+ const entry = o.catalogue.find((e) => refOf(e, o.dialect.defaultScope) === ref);
1794
+ if (!entry) return pill2;
1795
+ const tip = propertyTip(entry);
1796
+ if (tip) pill2.title = tip;
1797
+ const actions = o.propertyActions?.({ scope: entry.scope, name: entry.name }) ?? [];
1798
+ if (actions.length) pill2.addEventListener("contextmenu", (e) => {
1799
+ e.preventDefault();
1800
+ openPopover(pill2, (close) => propertyMenuBody(actions, close), { container: o.popoverContainer });
1801
+ });
1802
+ return pill2;
1803
+ }
1766
1804
  function renderEffectsPreview(effects, o) {
1767
- const wrap = el("div", "exed-preview exed-preview-effects");
1805
+ const wrap = el("div", `exed-preview exed-preview-effects${o.propertyActions ? " exed-preview-interactive" : ""}`);
1768
1806
  for (const eff of effects) {
1769
1807
  const row = el("div", "exed-preview-eff");
1770
1808
  if (eff.kind === "set") {
1771
1809
  const advances = isSelfAdvance(eff, o.dialect.defaultScope, o.dialect);
1772
- row.append(el("span", "exed-pill exed-pill-prop", [eff.target || "(property)"]));
1810
+ row.append(targetPill(eff.target, o));
1773
1811
  if (!advances) row.append(el("span", "exed-effect-eq", ["="]));
1774
1812
  row.append(exprPills(eff.value, o, advances ? eff.target : void 0));
1775
1813
  } else {
@@ -1787,6 +1825,6 @@ function renderEffectsPreview(effects, o) {
1787
1825
  return wrap;
1788
1826
  }
1789
1827
 
1790
- 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, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
1828
+ 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 };
1791
1829
  //# sourceMappingURL=index.js.map
1792
1830
  //# sourceMappingURL=index.js.map