@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/README.md +14 -0
- package/dist/index.cjs +57 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -4
- package/dist/index.d.ts +41 -4
- package/dist/index.js +53 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,20 @@ Notable options beyond the basics:
|
|
|
35
35
|
multi-step flow (e.g. tag → operator → threshold) with no editor changes.
|
|
36
36
|
Templates without a wizard insert-then-refine: the editor auto-opens the
|
|
37
37
|
first unfilled slot of the inserted clause.
|
|
38
|
+
- **Qualities** (the `quality` property type) need nothing from the host beyond a
|
|
39
|
+
catalogue entry carrying `stages` — the ladder, in order. The editor then offers
|
|
40
|
+
the property in a comparison clause, gives it the **ordering** operators (a
|
|
41
|
+
ladder exists for "at or past a stage"), lists its stages wherever a value is
|
|
42
|
+
picked, labels that list "stage", and seeds an outcome on it with
|
|
43
|
+
`advance(@ref)` — the form that keeps a story insertion-safe, because it names
|
|
44
|
+
no destination. That seed is the same on both paths (adding an effect and
|
|
45
|
+
re-pointing one), via `initialValueFor`, and a quality skips the value wizard
|
|
46
|
+
entirely: there is no question left worth asking. A host still passing a
|
|
47
|
+
ladder through `enumValues` keeps its value picker, but not the ordering
|
|
48
|
+
affordances.
|
|
49
|
+
- `valueWizard`'s `expectedChoices` supersedes `expectedEnumValues` — one
|
|
50
|
+
channel for "the target's closed set of values", filled with `choicesOf(entry)`
|
|
51
|
+
so a quality's ladder arrives with its type. The old name still works.
|
|
38
52
|
- `onEditingChange(editing)` — fires as popover micro-editors open/close, so the
|
|
39
53
|
host can suppress its own validation display mid-edit.
|
|
40
54
|
- `messages: false` — hide the editor's internal validation list when the host
|
package/dist/index.cjs
CHANGED
|
@@ -222,6 +222,10 @@ var BINARY_LABEL = {
|
|
|
222
222
|
var UNARY_LABEL = { not: "NOT", neg: "\u2212" };
|
|
223
223
|
var COMPARISON_OPS = ["==", "!=", ">", ">=", "<", "<="];
|
|
224
224
|
var ARITHMETIC_OPS = ["+", "-", "*", "/"];
|
|
225
|
+
var COMPARABLE_TYPES = ["boolean", "number", "string", "enum", "quality"];
|
|
226
|
+
var comparisonOpsFor = (t) => t === "number" || t === "quality" ? COMPARISON_OPS : EQUALITY_OPS;
|
|
227
|
+
var EQUALITY_OPS = ["==", "!="];
|
|
228
|
+
var rhsTypesFor = (t) => t === "number" ? ["number"] : t === "boolean" ? ["boolean"] : t === "quality" ? ["quality"] : t === "enum" ? ["enum", "string"] : ["string", "enum"];
|
|
225
229
|
function opSwapGroup(op) {
|
|
226
230
|
if (COMPARISON_OPS.includes(op)) return COMPARISON_OPS;
|
|
227
231
|
if (ARITHMETIC_OPS.includes(op)) return ARITHMETIC_OPS;
|
|
@@ -251,6 +255,12 @@ function formatNumber(n) {
|
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
// src/schema.ts
|
|
258
|
+
var choicesOf = (e) => {
|
|
259
|
+
if (!e) return void 0;
|
|
260
|
+
if (e.type === "quality") return e.stages?.length ? e.stages : e.enumValues;
|
|
261
|
+
if (e.type === "enum") return e.enumValues;
|
|
262
|
+
return void 0;
|
|
263
|
+
};
|
|
254
264
|
var refOf = (e, defaultScope) => e.scope === defaultScope ? `@${e.name}` : `@${e.scope}.${e.name}`;
|
|
255
265
|
var displayName = (e, defaultScope) => e.scope === defaultScope ? e.name : `${e.scope}.${e.name}`;
|
|
256
266
|
function filterCatalogue(entries, filter = {}) {
|
|
@@ -554,8 +564,9 @@ function stringEditor(ctx, path, node, anchor) {
|
|
|
554
564
|
const peer = findEnumPeer(ctx.getAst(), path);
|
|
555
565
|
const enumEntry = peer ? lookup(ctx.catalogue, peer.scope, peer.name) : null;
|
|
556
566
|
const rootValueEnums = path.length === 0 && ctx.valueEnumValues?.length ? ctx.valueEnumValues : null;
|
|
557
|
-
const
|
|
558
|
-
const
|
|
567
|
+
const peerChoices = choicesOf(enumEntry);
|
|
568
|
+
const enumValues = peerChoices?.length ? peerChoices : rootValueEnums;
|
|
569
|
+
const enumHeadLabel = enumEntry ? `${displayName(enumEntry, ctx.defaultScope)} ${enumEntry.type === "quality" ? "stage" : "value"}` : "Value";
|
|
559
570
|
ctx.openPopover(anchor, (close) => {
|
|
560
571
|
if (enumValues?.length) {
|
|
561
572
|
const wrap = el("div", "exed-menu");
|
|
@@ -705,7 +716,6 @@ function propertyPicker(ctx, opts) {
|
|
|
705
716
|
}
|
|
706
717
|
|
|
707
718
|
// src/clausewizard.ts
|
|
708
|
-
var EQUALITY = ["==", "!="];
|
|
709
719
|
var OP_WORD = {
|
|
710
720
|
"==": "equals",
|
|
711
721
|
"!=": "not equal to",
|
|
@@ -719,8 +729,6 @@ var opButton = (o, onClick) => {
|
|
|
719
729
|
b.append(el("span", "exed-opt-name", [BINARY_LABEL[o]]), el("span", "exed-opt-purpose", [OP_WORD[o] ?? ""]));
|
|
720
730
|
return b;
|
|
721
731
|
};
|
|
722
|
-
var opsForType = (t) => t === "number" ? COMPARISON_OPS : EQUALITY;
|
|
723
|
-
var rhsTypesFor = (t) => t === "number" ? ["number"] : t === "boolean" ? ["boolean"] : t === "enum" ? ["enum", "string"] : ["string", "enum"];
|
|
724
732
|
function header(host, title, back, cancel) {
|
|
725
733
|
const h = el("div", "exed-vwiz-head");
|
|
726
734
|
if (back) h.append(button("exed-vwiz-back", "\u2190", back, "Back"));
|
|
@@ -751,8 +759,8 @@ function valueStep(host, w, lhs, op, back, cancel, commit) {
|
|
|
751
759
|
const row = el("div", "exed-field-row");
|
|
752
760
|
row.append(button("exed-opt", "true", () => done(boolLit(true))), button("exed-opt", "false", () => done(boolLit(false))));
|
|
753
761
|
body.append(row);
|
|
754
|
-
} else if (lhs
|
|
755
|
-
for (const v of lhs
|
|
762
|
+
} else if (choicesOf(lhs)?.length) {
|
|
763
|
+
for (const v of choicesOf(lhs)) body.append(button("exed-opt", v, () => done(strLit(v))));
|
|
756
764
|
} else if (lhs.type === "number") {
|
|
757
765
|
body.append(textField({ caption: "Number", placeholder: "e.g. 3", validate: (v) => v.trim() !== "" && Number.isFinite(Number(v)), onCommit: (v) => done(numLit(Number(v))) }));
|
|
758
766
|
} else {
|
|
@@ -774,8 +782,13 @@ function comparisonWizard(host, w, commit, cancel) {
|
|
|
774
782
|
const body = el("div", "exed-vwiz-body");
|
|
775
783
|
host.append(body);
|
|
776
784
|
body.append(propertyPicker(pickCtxOf(w), {
|
|
777
|
-
accept:
|
|
778
|
-
onPick: (e) => pickOp({
|
|
785
|
+
accept: COMPARABLE_TYPES,
|
|
786
|
+
onPick: (e) => pickOp({
|
|
787
|
+
ref: refDisplay(w, e),
|
|
788
|
+
type: e.type,
|
|
789
|
+
...e.enumValues ? { enumValues: e.enumValues } : {},
|
|
790
|
+
...e.stages ? { stages: e.stages } : {}
|
|
791
|
+
})
|
|
779
792
|
}));
|
|
780
793
|
};
|
|
781
794
|
const pickOp = (lhs) => {
|
|
@@ -783,7 +796,7 @@ function comparisonWizard(host, w, commit, cancel) {
|
|
|
783
796
|
header(host, el("span", void 0, ["Operator for ", mono(lhs.ref)]), pickLhs, cancel);
|
|
784
797
|
const body = el("div", "exed-vwiz-body");
|
|
785
798
|
host.append(body);
|
|
786
|
-
for (const o of
|
|
799
|
+
for (const o of comparisonOpsFor(lhs.type)) {
|
|
787
800
|
body.append(opButton(o, () => valueStep(host, w, lhs, o, () => pickOp(lhs), cancel, commit)));
|
|
788
801
|
}
|
|
789
802
|
};
|
|
@@ -1337,7 +1350,9 @@ function valueWizard(opts) {
|
|
|
1337
1350
|
host.append(head("Pick a value"));
|
|
1338
1351
|
const body = el("div", "exed-vwiz-body");
|
|
1339
1352
|
host.append(body);
|
|
1340
|
-
|
|
1353
|
+
const choices = opts.expectedChoices?.length ? opts.expectedChoices : opts.expectedEnumValues;
|
|
1354
|
+
const isStage = opts.expectedType === "quality";
|
|
1355
|
+
let kind = opts.expectedType === "number" ? "number" : opts.expectedType === "string" ? "text" : opts.expectedType === "boolean" ? "bool" : (opts.expectedType === "enum" || opts.expectedType === "quality") && choices?.length ? "enum" : "menu";
|
|
1341
1356
|
const draw = () => {
|
|
1342
1357
|
body.replaceChildren();
|
|
1343
1358
|
const other = () => button("exed-vwiz-other", "\u21A9 a different kind", () => {
|
|
@@ -1362,7 +1377,7 @@ function valueWizard(opts) {
|
|
|
1362
1377
|
kind = "bool";
|
|
1363
1378
|
draw();
|
|
1364
1379
|
}));
|
|
1365
|
-
if (
|
|
1380
|
+
if (choices?.length) body.append(optBtn(isStage ? "A stage" : "A listed value", () => {
|
|
1366
1381
|
kind = "enum";
|
|
1367
1382
|
draw();
|
|
1368
1383
|
}));
|
|
@@ -1386,7 +1401,8 @@ function valueWizard(opts) {
|
|
|
1386
1401
|
break;
|
|
1387
1402
|
}
|
|
1388
1403
|
case "enum":
|
|
1389
|
-
|
|
1404
|
+
body.append(el("div", "exed-vwiz-note", [isStage ? "Stages, in ladder order:" : ""]));
|
|
1405
|
+
for (const v of choices ?? []) body.append(optBtn(v, () => opts.onCommit(enumSrc(v))));
|
|
1390
1406
|
body.append(other());
|
|
1391
1407
|
break;
|
|
1392
1408
|
}
|
|
@@ -1435,14 +1451,23 @@ function removeArgAt(list, i, argIdx) {
|
|
|
1435
1451
|
cur.args.splice(argIdx, 1);
|
|
1436
1452
|
return next;
|
|
1437
1453
|
}
|
|
1438
|
-
function
|
|
1454
|
+
function initialValueFor(entry, defaultScope) {
|
|
1455
|
+
const ref = refOf(entry, defaultScope);
|
|
1456
|
+
return {
|
|
1457
|
+
src: seedValueSrc(entry.type, choicesOf(entry), ref),
|
|
1458
|
+
ask: entry.type !== "quality"
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
function seedValueSrc(type, values, ref) {
|
|
1439
1462
|
switch (type) {
|
|
1440
1463
|
case "boolean":
|
|
1441
1464
|
return "true";
|
|
1442
1465
|
case "number":
|
|
1443
1466
|
return "0";
|
|
1444
1467
|
case "enum":
|
|
1445
|
-
return JSON.stringify(
|
|
1468
|
+
return JSON.stringify(values?.[0] ?? "");
|
|
1469
|
+
case "quality":
|
|
1470
|
+
return ref ? `advance(${ref})` : JSON.stringify(values?.[0] ?? "");
|
|
1446
1471
|
case "string":
|
|
1447
1472
|
return '""';
|
|
1448
1473
|
default:
|
|
@@ -1472,7 +1497,9 @@ function mountEffectsEditor(host, opts) {
|
|
|
1472
1497
|
scopeOrder: opts.scopeOrder ?? [],
|
|
1473
1498
|
defaultScope,
|
|
1474
1499
|
...expected?.type ? { expectedType: expected.type } : {},
|
|
1475
|
-
|
|
1500
|
+
// choicesOf, never a raw field: a quality's ladder lives in `stages`, and reading `enumValues`
|
|
1501
|
+
// here told the wizard the type and withheld the values.
|
|
1502
|
+
...choicesOf(expected) ? { expectedChoices: choicesOf(expected) } : {},
|
|
1476
1503
|
onCommit: (src) => {
|
|
1477
1504
|
onCommit(src);
|
|
1478
1505
|
close();
|
|
@@ -1540,7 +1567,7 @@ function mountEffectsEditor(host, opts) {
|
|
|
1540
1567
|
const row = el("div", "exed-effect exed-effect-set");
|
|
1541
1568
|
const targetBtn = button("exed-pill exed-pill-prop", eff.target || "(pick property)", (e) => {
|
|
1542
1569
|
pickTarget(e.currentTarget, void 0, (entry) => {
|
|
1543
|
-
commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value:
|
|
1570
|
+
commit(updateAt(effects, i, { target: refOf(entry, defaultScope), value: initialValueFor(entry, defaultScope).src }));
|
|
1544
1571
|
});
|
|
1545
1572
|
}, "change the target property");
|
|
1546
1573
|
row.append(targetBtn, el("span", "exed-effect-eq", ["="]));
|
|
@@ -1626,7 +1653,14 @@ function mountEffectsEditor(host, opts) {
|
|
|
1626
1653
|
const bar = el("div", "exed-effect-addbar");
|
|
1627
1654
|
bar.append(button("exed-eff-add", "+ set property", (e) => {
|
|
1628
1655
|
const anchor = e.currentTarget;
|
|
1629
|
-
pickTarget(anchor, void 0, (entry) =>
|
|
1656
|
+
pickTarget(anchor, void 0, (entry) => {
|
|
1657
|
+
const { src, ask } = initialValueFor(entry, defaultScope);
|
|
1658
|
+
if (!ask) {
|
|
1659
|
+
commit(addSet(effects, refOf(entry, defaultScope), src));
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
openValueWizard(anchor, entry, (chosen) => commit(addSet(effects, refOf(entry, defaultScope), chosen)));
|
|
1663
|
+
});
|
|
1630
1664
|
}));
|
|
1631
1665
|
if (opts.allowEmit !== false) {
|
|
1632
1666
|
bar.append(button("exed-eff-add", "+ emit event", (e) => editEvent(e.currentTarget, "", (name) => commit(addEmit(effects, name)))));
|
|
@@ -1716,7 +1750,9 @@ function renderEffectsPreview(effects, o) {
|
|
|
1716
1750
|
|
|
1717
1751
|
exports.ARITHMETIC_OPS = ARITHMETIC_OPS;
|
|
1718
1752
|
exports.BINARY_LABEL = BINARY_LABEL;
|
|
1753
|
+
exports.COMPARABLE_TYPES = COMPARABLE_TYPES;
|
|
1719
1754
|
exports.COMPARISON_OPS = COMPARISON_OPS;
|
|
1755
|
+
exports.EQUALITY_OPS = EQUALITY_OPS;
|
|
1720
1756
|
exports.UNARY_LABEL = UNARY_LABEL;
|
|
1721
1757
|
exports.addArg = addArg;
|
|
1722
1758
|
exports.addChildToContainer = addChildToContainer;
|
|
@@ -1727,6 +1763,8 @@ exports.binary = binary;
|
|
|
1727
1763
|
exports.boolLit = boolLit;
|
|
1728
1764
|
exports.buildSubGroupClause = buildSubGroupClause;
|
|
1729
1765
|
exports.callNode = callNode;
|
|
1766
|
+
exports.choicesOf = choicesOf;
|
|
1767
|
+
exports.comparisonOpsFor = comparisonOpsFor;
|
|
1730
1768
|
exports.deleteAt = deleteAt;
|
|
1731
1769
|
exports.displayName = displayName;
|
|
1732
1770
|
exports.filterCatalogue = filterCatalogue;
|
|
@@ -1759,6 +1797,7 @@ exports.removeArgAt = removeArgAt;
|
|
|
1759
1797
|
exports.removeAt = removeAt;
|
|
1760
1798
|
exports.renderConditionPreview = renderConditionPreview;
|
|
1761
1799
|
exports.renderEffectsPreview = renderEffectsPreview;
|
|
1800
|
+
exports.rhsTypesFor = rhsTypesFor;
|
|
1762
1801
|
exports.scopedVar = scopedVar;
|
|
1763
1802
|
exports.searchCatalogue = searchCatalogue;
|
|
1764
1803
|
exports.seedValueSrc = seedValueSrc;
|