@wildwinter/expr-editor 0.10.3 → 0.12.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
@@ -1,4 +1,4 @@
1
- import { BinaryOp, ExprNode, AstPath, UnaryOp, PropertyType, ExpressionValidationResult, ExpressionValidationIssue, ExpressionSchema, Dialect } from '@wildwinter/expr';
1
+ import { BinaryOp, ExprNode, AstPath, PropertyType, UnaryOp, ExpressionValidationResult, ExpressionValidationIssue, ExpressionSchema, Dialect } from '@wildwinter/expr';
2
2
  export { ExpressionValidationIssue, ExpressionValidationResult, PropertyType } from '@wildwinter/expr';
3
3
 
4
4
  declare const boolLit: (value: boolean) => ExprNode;
@@ -99,6 +99,20 @@ declare const BINARY_LABEL: Record<BinaryOp, string>;
99
99
  declare const UNARY_LABEL: Record<UnaryOp, string>;
100
100
  declare const COMPARISON_OPS: BinaryOp[];
101
101
  declare const ARITHMETIC_OPS: BinaryOp[];
102
+ /** Property types a comparison clause can be built on - what the clause wizard offers as a left-hand
103
+ * side. `flags` is absent because its clause is the flag-check wizard, not a comparison. */
104
+ declare const COMPARABLE_TYPES: PropertyType[];
105
+ /** The operators offered for a comparison on a property of this type.
106
+ *
107
+ * Ordered types get the full set; the rest get equality only. A QUALITY is ordered: its stages are a
108
+ * ladder, and "at or past a stage" is the gate the type exists for, so offering it `==` alone (as the
109
+ * editor did before 0.11.0) hides the feature behind hand-written source. */
110
+ declare const comparisonOpsFor: (t: PropertyType) => BinaryOp[];
111
+ declare const EQUALITY_OPS: BinaryOp[];
112
+ /** The property types a comparison's RIGHT-hand side may be picked from, given the left's type.
113
+ * A quality accepts only another quality: a plain string would name a stage nothing validates, and
114
+ * comparing across two ladders is an error the engine raises by design. */
115
+ declare const rhsTypesFor: (t: PropertyType) => PropertyType[];
102
116
  /** The set of operators a given operator can be swapped to inline, or null if structural (and/or/not). */
103
117
  declare function opSwapGroup(op: BinaryOp): BinaryOp[] | null;
104
118
  /** Whether a child binary needs parentheses inside a parent binary on the given side. */
@@ -112,9 +126,22 @@ interface CatalogueEntry {
112
126
  name: string;
113
127
  type: PropertyType;
114
128
  enumValues?: string[];
129
+ /** A quality's ORDERED stage ladder. Separate from `enumValues` because the order carries meaning
130
+ * here (comparisons are positional, `advance()` walks it) - a host that smuggled stages through
131
+ * `enumValues` got a value picker but no ordering affordances. */
132
+ stages?: string[];
115
133
  /** Free-text description; the picker search matches it alongside the name. */
116
134
  purpose?: string;
117
135
  }
136
+ /** The CLOSED set of values a property can hold, when it has one: a quality's stages (in ladder
137
+ * order) or an enum's values. Everything that offers "pick a value" for a property goes through
138
+ * this, so a quality is never mistaken for free text. `stages` falls back to `enumValues` for a
139
+ * host still passing a quality's ladder the old way. */
140
+ declare const choicesOf: (e: {
141
+ type: PropertyType;
142
+ enumValues?: string[];
143
+ stages?: string[];
144
+ } | null | undefined) => string[] | undefined;
118
145
  interface Filter {
119
146
  acceptTypes?: PropertyType[];
120
147
  acceptScopes?: string[];
@@ -381,8 +408,13 @@ declare function setArgAt(list: EditorEffect[], i: number, argIdx: number, value
381
408
  declare function addArg(list: EditorEffect[], i: number, value?: string): EditorEffect[];
382
409
  declare function removeArgAt(list: EditorEffect[], i: number, argIdx: number): EditorEffect[];
383
410
  /** A sensible starting value-expression for a freshly targeted property. Literals
384
- * are seeded so the value editor opens on an editable pill, never the empty state. */
385
- declare function seedValueSrc(type: PropertyType, enumValues?: string[]): string;
411
+ * are seeded so the value editor opens on an editable pill, never the empty state.
412
+ *
413
+ * A QUALITY seeds to `advance(<ref>)` when the target's ref is known: moving to the next stage is
414
+ * what outcomes on a ladder overwhelmingly do, and it is the form that keeps a story insertion-safe
415
+ * (it names no destination, so a stage added later routes through it). Pass no ref and it falls back
416
+ * to the first stage as a literal, which the author can still swap to `advance(...)`. */
417
+ declare function seedValueSrc(type: PropertyType, values?: string[], ref?: string): string;
386
418
  declare function mountEffectsEditor(host: HTMLElement, opts: EffectsEditorOptions): EffectsEditorHandle;
387
419
 
388
420
  interface PreviewOptions {
@@ -418,6 +450,11 @@ interface ValueWizardOptions {
418
450
  defaultScope: string;
419
451
  /** When known (a `set` target's declared type), the picker leads straight to that input. */
420
452
  expectedType?: PropertyType;
453
+ /** The target's closed set of values, when it has one: an enum's values, or a QUALITY's stages in
454
+ * ladder order. Callers should fill this with `choicesOf(entry)` rather than reading a single
455
+ * field, so a quality is never handed a wizard that knows its type but not its ladder. */
456
+ expectedChoices?: string[];
457
+ /** @deprecated Use `expectedChoices`. Kept so a 0.11.x caller keeps working. */
421
458
  expectedEnumValues?: string[];
422
459
  /** Receives the chosen value as name-form source. */
423
460
  onCommit: (src: string) => void;
@@ -427,4 +464,4 @@ interface ValueWizardOptions {
427
464
  /** Build the wizard UI into a fresh element (drive it via the host's popover). */
428
465
  declare function valueWizard(opts: ValueWizardOptions): HTMLElement;
429
466
 
430
- export { ARITHMETIC_OPS, BINARY_LABEL, COMPARISON_OPS, type CatalogueEntry, type EditCtx, type EditorEffect, type EffectsEditorHandle, type EffectsEditorOptions, type ExpressionEditorHandle, type ExpressionEditorOptions, type Filter, type FunctionTemplateSpec, type PreviewOptions, type TreeRow, UNARY_LABEL, type Validation, type ValueWizardOptions, type WizardSpec, type WizardStepSpec, type WizardValue, addArg, addChildToContainer, addEmit, addSet, astToTree, binary, boolLit, buildSubGroupClause, callNode, deleteAt, displayName, filterCatalogue, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
467
+ 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, type PreviewOptions, type TreeRow, UNARY_LABEL, type Validation, type ValueWizardOptions, type WizardSpec, type WizardStepSpec, type WizardValue, addArg, addChildToContainer, addEmit, addSet, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, 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
@@ -1,4 +1,4 @@
1
- import { BinaryOp, ExprNode, AstPath, UnaryOp, PropertyType, ExpressionValidationResult, ExpressionValidationIssue, ExpressionSchema, Dialect } from '@wildwinter/expr';
1
+ import { BinaryOp, ExprNode, AstPath, PropertyType, UnaryOp, ExpressionValidationResult, ExpressionValidationIssue, ExpressionSchema, Dialect } from '@wildwinter/expr';
2
2
  export { ExpressionValidationIssue, ExpressionValidationResult, PropertyType } from '@wildwinter/expr';
3
3
 
4
4
  declare const boolLit: (value: boolean) => ExprNode;
@@ -99,6 +99,20 @@ declare const BINARY_LABEL: Record<BinaryOp, string>;
99
99
  declare const UNARY_LABEL: Record<UnaryOp, string>;
100
100
  declare const COMPARISON_OPS: BinaryOp[];
101
101
  declare const ARITHMETIC_OPS: BinaryOp[];
102
+ /** Property types a comparison clause can be built on - what the clause wizard offers as a left-hand
103
+ * side. `flags` is absent because its clause is the flag-check wizard, not a comparison. */
104
+ declare const COMPARABLE_TYPES: PropertyType[];
105
+ /** The operators offered for a comparison on a property of this type.
106
+ *
107
+ * Ordered types get the full set; the rest get equality only. A QUALITY is ordered: its stages are a
108
+ * ladder, and "at or past a stage" is the gate the type exists for, so offering it `==` alone (as the
109
+ * editor did before 0.11.0) hides the feature behind hand-written source. */
110
+ declare const comparisonOpsFor: (t: PropertyType) => BinaryOp[];
111
+ declare const EQUALITY_OPS: BinaryOp[];
112
+ /** The property types a comparison's RIGHT-hand side may be picked from, given the left's type.
113
+ * A quality accepts only another quality: a plain string would name a stage nothing validates, and
114
+ * comparing across two ladders is an error the engine raises by design. */
115
+ declare const rhsTypesFor: (t: PropertyType) => PropertyType[];
102
116
  /** The set of operators a given operator can be swapped to inline, or null if structural (and/or/not). */
103
117
  declare function opSwapGroup(op: BinaryOp): BinaryOp[] | null;
104
118
  /** Whether a child binary needs parentheses inside a parent binary on the given side. */
@@ -112,9 +126,22 @@ interface CatalogueEntry {
112
126
  name: string;
113
127
  type: PropertyType;
114
128
  enumValues?: string[];
129
+ /** A quality's ORDERED stage ladder. Separate from `enumValues` because the order carries meaning
130
+ * here (comparisons are positional, `advance()` walks it) - a host that smuggled stages through
131
+ * `enumValues` got a value picker but no ordering affordances. */
132
+ stages?: string[];
115
133
  /** Free-text description; the picker search matches it alongside the name. */
116
134
  purpose?: string;
117
135
  }
136
+ /** The CLOSED set of values a property can hold, when it has one: a quality's stages (in ladder
137
+ * order) or an enum's values. Everything that offers "pick a value" for a property goes through
138
+ * this, so a quality is never mistaken for free text. `stages` falls back to `enumValues` for a
139
+ * host still passing a quality's ladder the old way. */
140
+ declare const choicesOf: (e: {
141
+ type: PropertyType;
142
+ enumValues?: string[];
143
+ stages?: string[];
144
+ } | null | undefined) => string[] | undefined;
118
145
  interface Filter {
119
146
  acceptTypes?: PropertyType[];
120
147
  acceptScopes?: string[];
@@ -381,8 +408,13 @@ declare function setArgAt(list: EditorEffect[], i: number, argIdx: number, value
381
408
  declare function addArg(list: EditorEffect[], i: number, value?: string): EditorEffect[];
382
409
  declare function removeArgAt(list: EditorEffect[], i: number, argIdx: number): EditorEffect[];
383
410
  /** A sensible starting value-expression for a freshly targeted property. Literals
384
- * are seeded so the value editor opens on an editable pill, never the empty state. */
385
- declare function seedValueSrc(type: PropertyType, enumValues?: string[]): string;
411
+ * are seeded so the value editor opens on an editable pill, never the empty state.
412
+ *
413
+ * A QUALITY seeds to `advance(<ref>)` when the target's ref is known: moving to the next stage is
414
+ * what outcomes on a ladder overwhelmingly do, and it is the form that keeps a story insertion-safe
415
+ * (it names no destination, so a stage added later routes through it). Pass no ref and it falls back
416
+ * to the first stage as a literal, which the author can still swap to `advance(...)`. */
417
+ declare function seedValueSrc(type: PropertyType, values?: string[], ref?: string): string;
386
418
  declare function mountEffectsEditor(host: HTMLElement, opts: EffectsEditorOptions): EffectsEditorHandle;
387
419
 
388
420
  interface PreviewOptions {
@@ -418,6 +450,11 @@ interface ValueWizardOptions {
418
450
  defaultScope: string;
419
451
  /** When known (a `set` target's declared type), the picker leads straight to that input. */
420
452
  expectedType?: PropertyType;
453
+ /** The target's closed set of values, when it has one: an enum's values, or a QUALITY's stages in
454
+ * ladder order. Callers should fill this with `choicesOf(entry)` rather than reading a single
455
+ * field, so a quality is never handed a wizard that knows its type but not its ladder. */
456
+ expectedChoices?: string[];
457
+ /** @deprecated Use `expectedChoices`. Kept so a 0.11.x caller keeps working. */
421
458
  expectedEnumValues?: string[];
422
459
  /** Receives the chosen value as name-form source. */
423
460
  onCommit: (src: string) => void;
@@ -427,4 +464,4 @@ interface ValueWizardOptions {
427
464
  /** Build the wizard UI into a fresh element (drive it via the host's popover). */
428
465
  declare function valueWizard(opts: ValueWizardOptions): HTMLElement;
429
466
 
430
- export { ARITHMETIC_OPS, BINARY_LABEL, COMPARISON_OPS, type CatalogueEntry, type EditCtx, type EditorEffect, type EffectsEditorHandle, type EffectsEditorOptions, type ExpressionEditorHandle, type ExpressionEditorOptions, type Filter, type FunctionTemplateSpec, type PreviewOptions, type TreeRow, UNARY_LABEL, type Validation, type ValueWizardOptions, type WizardSpec, type WizardStepSpec, type WizardValue, addArg, addChildToContainer, addEmit, addSet, astToTree, binary, boolLit, buildSubGroupClause, callNode, deleteAt, displayName, filterCatalogue, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
467
+ 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, type PreviewOptions, type TreeRow, UNARY_LABEL, type Validation, type ValueWizardOptions, type WizardSpec, type WizardStepSpec, type WizardValue, addArg, addChildToContainer, addEmit, addSet, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
package/dist/index.js CHANGED
@@ -220,6 +220,10 @@ var BINARY_LABEL = {
220
220
  var UNARY_LABEL = { not: "NOT", neg: "\u2212" };
221
221
  var COMPARISON_OPS = ["==", "!=", ">", ">=", "<", "<="];
222
222
  var ARITHMETIC_OPS = ["+", "-", "*", "/"];
223
+ var COMPARABLE_TYPES = ["boolean", "number", "string", "enum", "quality"];
224
+ var comparisonOpsFor = (t) => t === "number" || t === "quality" ? COMPARISON_OPS : EQUALITY_OPS;
225
+ var EQUALITY_OPS = ["==", "!="];
226
+ var rhsTypesFor = (t) => t === "number" ? ["number"] : t === "boolean" ? ["boolean"] : t === "quality" ? ["quality"] : t === "enum" ? ["enum", "string"] : ["string", "enum"];
223
227
  function opSwapGroup(op) {
224
228
  if (COMPARISON_OPS.includes(op)) return COMPARISON_OPS;
225
229
  if (ARITHMETIC_OPS.includes(op)) return ARITHMETIC_OPS;
@@ -249,6 +253,12 @@ function formatNumber(n) {
249
253
  }
250
254
 
251
255
  // src/schema.ts
256
+ var choicesOf = (e) => {
257
+ if (!e) return void 0;
258
+ if (e.type === "quality") return e.stages?.length ? e.stages : e.enumValues;
259
+ if (e.type === "enum") return e.enumValues;
260
+ return void 0;
261
+ };
252
262
  var refOf = (e, defaultScope) => e.scope === defaultScope ? `@${e.name}` : `@${e.scope}.${e.name}`;
253
263
  var displayName = (e, defaultScope) => e.scope === defaultScope ? e.name : `${e.scope}.${e.name}`;
254
264
  function filterCatalogue(entries, filter = {}) {
@@ -552,8 +562,9 @@ function stringEditor(ctx, path, node, anchor) {
552
562
  const peer = findEnumPeer(ctx.getAst(), path);
553
563
  const enumEntry = peer ? lookup(ctx.catalogue, peer.scope, peer.name) : null;
554
564
  const rootValueEnums = path.length === 0 && ctx.valueEnumValues?.length ? ctx.valueEnumValues : null;
555
- const enumValues = enumEntry?.enumValues?.length ? enumEntry.enumValues : rootValueEnums;
556
- const enumHeadLabel = enumEntry ? `${displayName(enumEntry, ctx.defaultScope)} value` : "Value";
565
+ const peerChoices = choicesOf(enumEntry);
566
+ const enumValues = peerChoices?.length ? peerChoices : rootValueEnums;
567
+ const enumHeadLabel = enumEntry ? `${displayName(enumEntry, ctx.defaultScope)} ${enumEntry.type === "quality" ? "stage" : "value"}` : "Value";
557
568
  ctx.openPopover(anchor, (close) => {
558
569
  if (enumValues?.length) {
559
570
  const wrap = el("div", "exed-menu");
@@ -703,7 +714,6 @@ function propertyPicker(ctx, opts) {
703
714
  }
704
715
 
705
716
  // src/clausewizard.ts
706
- var EQUALITY = ["==", "!="];
707
717
  var OP_WORD = {
708
718
  "==": "equals",
709
719
  "!=": "not equal to",
@@ -717,8 +727,6 @@ var opButton = (o, onClick) => {
717
727
  b.append(el("span", "exed-opt-name", [BINARY_LABEL[o]]), el("span", "exed-opt-purpose", [OP_WORD[o] ?? ""]));
718
728
  return b;
719
729
  };
720
- var opsForType = (t) => t === "number" ? COMPARISON_OPS : EQUALITY;
721
- var rhsTypesFor = (t) => t === "number" ? ["number"] : t === "boolean" ? ["boolean"] : t === "enum" ? ["enum", "string"] : ["string", "enum"];
722
730
  function header(host, title, back, cancel) {
723
731
  const h = el("div", "exed-vwiz-head");
724
732
  if (back) h.append(button("exed-vwiz-back", "\u2190", back, "Back"));
@@ -749,8 +757,8 @@ function valueStep(host, w, lhs, op, back, cancel, commit) {
749
757
  const row = el("div", "exed-field-row");
750
758
  row.append(button("exed-opt", "true", () => done(boolLit(true))), button("exed-opt", "false", () => done(boolLit(false))));
751
759
  body.append(row);
752
- } else if (lhs.type === "enum" && lhs.enumValues?.length) {
753
- for (const v of lhs.enumValues) body.append(button("exed-opt", v, () => done(strLit(v))));
760
+ } else if (choicesOf(lhs)?.length) {
761
+ for (const v of choicesOf(lhs)) body.append(button("exed-opt", v, () => done(strLit(v))));
754
762
  } else if (lhs.type === "number") {
755
763
  body.append(textField({ caption: "Number", placeholder: "e.g. 3", validate: (v) => v.trim() !== "" && Number.isFinite(Number(v)), onCommit: (v) => done(numLit(Number(v))) }));
756
764
  } else {
@@ -772,8 +780,13 @@ function comparisonWizard(host, w, commit, cancel) {
772
780
  const body = el("div", "exed-vwiz-body");
773
781
  host.append(body);
774
782
  body.append(propertyPicker(pickCtxOf(w), {
775
- accept: ["boolean", "number", "string", "enum"],
776
- onPick: (e) => pickOp({ ref: refDisplay(w, e), type: e.type, ...e.enumValues ? { enumValues: e.enumValues } : {} })
783
+ accept: COMPARABLE_TYPES,
784
+ onPick: (e) => pickOp({
785
+ ref: refDisplay(w, e),
786
+ type: e.type,
787
+ ...e.enumValues ? { enumValues: e.enumValues } : {},
788
+ ...e.stages ? { stages: e.stages } : {}
789
+ })
777
790
  }));
778
791
  };
779
792
  const pickOp = (lhs) => {
@@ -781,7 +794,7 @@ function comparisonWizard(host, w, commit, cancel) {
781
794
  header(host, el("span", void 0, ["Operator for ", mono(lhs.ref)]), pickLhs, cancel);
782
795
  const body = el("div", "exed-vwiz-body");
783
796
  host.append(body);
784
- for (const o of opsForType(lhs.type)) {
797
+ for (const o of comparisonOpsFor(lhs.type)) {
785
798
  body.append(opButton(o, () => valueStep(host, w, lhs, o, () => pickOp(lhs), cancel, commit)));
786
799
  }
787
800
  };
@@ -1335,7 +1348,9 @@ function valueWizard(opts) {
1335
1348
  host.append(head("Pick a value"));
1336
1349
  const body = el("div", "exed-vwiz-body");
1337
1350
  host.append(body);
1338
- let kind = opts.expectedType === "number" ? "number" : opts.expectedType === "string" ? "text" : opts.expectedType === "boolean" ? "bool" : opts.expectedType === "enum" && opts.expectedEnumValues?.length ? "enum" : "menu";
1351
+ const choices = opts.expectedChoices?.length ? opts.expectedChoices : opts.expectedEnumValues;
1352
+ const isStage = opts.expectedType === "quality";
1353
+ let kind = opts.expectedType === "number" ? "number" : opts.expectedType === "string" ? "text" : opts.expectedType === "boolean" ? "bool" : (opts.expectedType === "enum" || opts.expectedType === "quality") && choices?.length ? "enum" : "menu";
1339
1354
  const draw = () => {
1340
1355
  body.replaceChildren();
1341
1356
  const other = () => button("exed-vwiz-other", "\u21A9 a different kind", () => {
@@ -1360,7 +1375,7 @@ function valueWizard(opts) {
1360
1375
  kind = "bool";
1361
1376
  draw();
1362
1377
  }));
1363
- if (opts.expectedEnumValues?.length) body.append(optBtn("A listed value", () => {
1378
+ if (choices?.length) body.append(optBtn(isStage ? "A stage" : "A listed value", () => {
1364
1379
  kind = "enum";
1365
1380
  draw();
1366
1381
  }));
@@ -1384,7 +1399,8 @@ function valueWizard(opts) {
1384
1399
  break;
1385
1400
  }
1386
1401
  case "enum":
1387
- for (const v of opts.expectedEnumValues ?? []) body.append(optBtn(v, () => opts.onCommit(enumSrc(v))));
1402
+ body.append(el("div", "exed-vwiz-note", [isStage ? "Stages, in ladder order:" : ""]));
1403
+ for (const v of choices ?? []) body.append(optBtn(v, () => opts.onCommit(enumSrc(v))));
1388
1404
  body.append(other());
1389
1405
  break;
1390
1406
  }
@@ -1433,14 +1449,23 @@ function removeArgAt(list, i, argIdx) {
1433
1449
  cur.args.splice(argIdx, 1);
1434
1450
  return next;
1435
1451
  }
1436
- function seedValueSrc(type, enumValues) {
1452
+ function initialValueFor(entry, defaultScope) {
1453
+ const ref = refOf(entry, defaultScope);
1454
+ return {
1455
+ src: seedValueSrc(entry.type, choicesOf(entry), ref),
1456
+ ask: entry.type !== "quality"
1457
+ };
1458
+ }
1459
+ function seedValueSrc(type, values, ref) {
1437
1460
  switch (type) {
1438
1461
  case "boolean":
1439
1462
  return "true";
1440
1463
  case "number":
1441
1464
  return "0";
1442
1465
  case "enum":
1443
- return JSON.stringify(enumValues?.[0] ?? "");
1466
+ return JSON.stringify(values?.[0] ?? "");
1467
+ case "quality":
1468
+ return ref ? `advance(${ref})` : JSON.stringify(values?.[0] ?? "");
1444
1469
  case "string":
1445
1470
  return '""';
1446
1471
  default:
@@ -1470,7 +1495,9 @@ function mountEffectsEditor(host, opts) {
1470
1495
  scopeOrder: opts.scopeOrder ?? [],
1471
1496
  defaultScope,
1472
1497
  ...expected?.type ? { expectedType: expected.type } : {},
1473
- ...expected?.enumValues ? { expectedEnumValues: expected.enumValues } : {},
1498
+ // choicesOf, never a raw field: a quality's ladder lives in `stages`, and reading `enumValues`
1499
+ // here told the wizard the type and withheld the values.
1500
+ ...choicesOf(expected) ? { expectedChoices: choicesOf(expected) } : {},
1474
1501
  onCommit: (src) => {
1475
1502
  onCommit(src);
1476
1503
  close();
@@ -1538,7 +1565,7 @@ function mountEffectsEditor(host, opts) {
1538
1565
  const row = el("div", "exed-effect exed-effect-set");
1539
1566
  const targetBtn = button("exed-pill exed-pill-prop", eff.target || "(pick property)", (e) => {
1540
1567
  pickTarget(e.currentTarget, void 0, (entry) => {
1541
- commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value: seedValueSrc(entry.type, entry.enumValues) }));
1568
+ commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value: initialValueFor(entry, defaultScope).src }));
1542
1569
  });
1543
1570
  }, "change the target property");
1544
1571
  row.append(targetBtn, el("span", "exed-effect-eq", ["="]));
@@ -1624,7 +1651,14 @@ function mountEffectsEditor(host, opts) {
1624
1651
  const bar = el("div", "exed-effect-addbar");
1625
1652
  bar.append(button("exed-eff-add", "+ set property", (e) => {
1626
1653
  const anchor = e.currentTarget;
1627
- pickTarget(anchor, void 0, (entry) => openValueWizard(anchor, entry, (src) => commit(addSet(effects, refOf(entry, defaultScope), src))));
1654
+ pickTarget(anchor, void 0, (entry) => {
1655
+ const { src, ask } = initialValueFor(entry, defaultScope);
1656
+ if (!ask) {
1657
+ commit(addSet(effects, refOf(entry, defaultScope), src));
1658
+ return;
1659
+ }
1660
+ openValueWizard(anchor, entry, (chosen) => commit(addSet(effects, refOf(entry, defaultScope), chosen)));
1661
+ });
1628
1662
  }));
1629
1663
  if (opts.allowEmit !== false) {
1630
1664
  bar.append(button("exed-eff-add", "+ emit event", (e) => editEvent(e.currentTarget, "", (name) => commit(addEmit(effects, name)))));
@@ -1712,6 +1746,6 @@ function renderEffectsPreview(effects, o) {
1712
1746
  return wrap;
1713
1747
  }
1714
1748
 
1715
- export { ARITHMETIC_OPS, BINARY_LABEL, COMPARISON_OPS, UNARY_LABEL, addArg, addChildToContainer, addEmit, addSet, astToTree, binary, boolLit, buildSubGroupClause, callNode, deleteAt, displayName, filterCatalogue, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
1749
+ export { ARITHMETIC_OPS, BINARY_LABEL, COMPARABLE_TYPES, COMPARISON_OPS, EQUALITY_OPS, UNARY_LABEL, addArg, addChildToContainer, addEmit, addSet, astToTree, binary, boolLit, buildSubGroupClause, callNode, choicesOf, comparisonOpsFor, deleteAt, displayName, filterCatalogue, findEnumPeer, firstEmptyLeafPath, flagDelta, flipContainerOp, formatNumber, getNodeAt, groupByScope, insertSiblingClauseAt, isComparisonOp, isPlaceholderForOp, isWrappedInNot, issuesAt, lookup, mountEffectsEditor, mountExpressionEditor, moveAt, moveChildInContainer, needsParens, notNode, numLit, opSwapGroup, pathKey, placeholderForOp, redirectDeleteForPlaceholderSibling, refOf, removeArgAt, removeAt, renderConditionPreview, renderEffectsPreview, rhsTypesFor, scopedVar, searchCatalogue, seedValueSrc, setArgAt, setNodeAt, strLit, toggleContainerNot, toggleNotAt, updateAt, validateSource, valueWizard, wrapInNotAt };
1716
1750
  //# sourceMappingURL=index.js.map
1717
1751
  //# sourceMappingURL=index.js.map