@wildwinter/expr-editor 0.7.0 → 0.9.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 +13 -0
- package/dist/index.cjs +77 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +77 -19
- package/dist/index.js.map +1 -1
- package/dist/styles.css +4 -0
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,19 @@ Notable options beyond the basics:
|
|
|
55
55
|
the flag-delta pills plus an "+ flag" chip (which picks sign + name from
|
|
56
56
|
`flags`), hiding the function name and target arg, and seeds the call on the
|
|
57
57
|
first flag add. Pair with `requireNonEmpty` so the last flag can't be removed.
|
|
58
|
+
- `propertyActions(ref)` — host actions shown when a property pill is
|
|
59
|
+
right-clicked (e.g. "Go to definition"); the host resolves what each does.
|
|
60
|
+
- `rawPlaceholder` — placeholder for the raw-text textarea (value cells pass a
|
|
61
|
+
value-flavoured hint).
|
|
62
|
+
|
|
63
|
+
Pills carry `aria-haspopup`/labels and the tree controls carry aria-labels for
|
|
64
|
+
assistive tech. A comparison of a numeric dialect function against a number
|
|
65
|
+
(e.g. `turns_since_tag("x") > 3`) deletes as a unit.
|
|
66
|
+
|
|
67
|
+
`renderConditionPreview(src, opts)` / `renderEffectsPreview(effects, opts)`
|
|
68
|
+
return read-only pill strips (same pills as the editor, non-interactive). Pass
|
|
69
|
+
`propertyActions` to make property pills right-click interactive (e.g. "Go to
|
|
70
|
+
definition") while left-click still falls through to the host.
|
|
58
71
|
- `setText(on)` — the host's raw-text toggle (`</>`); unparseable input falls
|
|
59
72
|
back to raw text automatically.
|
|
60
73
|
|
package/dist/index.cjs
CHANGED
|
@@ -305,10 +305,11 @@ function el(tag, cls, children) {
|
|
|
305
305
|
}
|
|
306
306
|
return node;
|
|
307
307
|
}
|
|
308
|
-
function button(cls, label, onClick, title) {
|
|
308
|
+
function button(cls, label, onClick, title, aria) {
|
|
309
309
|
const b = el("button", cls, [label]);
|
|
310
310
|
b.type = "button";
|
|
311
311
|
if (title) b.title = title;
|
|
312
|
+
if (aria) b.setAttribute("aria-label", aria);
|
|
312
313
|
b.addEventListener("click", onClick);
|
|
313
314
|
return b;
|
|
314
315
|
}
|
|
@@ -389,10 +390,25 @@ function textField(opts) {
|
|
|
389
390
|
var FLAG_FNS = /* @__PURE__ */ new Set(["check_flags", "set_flags"]);
|
|
390
391
|
var TAG_ARG0_FNS = /* @__PURE__ */ new Set(["seen", "patter_seen", "visits", "patter_visits"]);
|
|
391
392
|
function pill(kind, label, onClick, opts = {}) {
|
|
392
|
-
const b = button(`exed-pill exed-pill-${kind}${opts.issue ? " exed-pill-err" : ""}`, label, () => onClick(b), opts.issue ?? opts.title);
|
|
393
|
+
const b = button(`exed-pill exed-pill-${kind}${opts.issue ? " exed-pill-err" : ""}`, label, () => onClick(b), opts.issue ?? opts.title, opts.aria);
|
|
394
|
+
b.setAttribute("aria-haspopup", "dialog");
|
|
393
395
|
if (opts.path) b.dataset["exedPath"] = opts.path.join("/");
|
|
394
396
|
return b;
|
|
395
397
|
}
|
|
398
|
+
var OP_ARIA = {
|
|
399
|
+
"==": "equals",
|
|
400
|
+
"!=": "not equal to",
|
|
401
|
+
">": "greater than",
|
|
402
|
+
">=": "at least",
|
|
403
|
+
"<": "less than",
|
|
404
|
+
"<=": "at most",
|
|
405
|
+
"and": "and",
|
|
406
|
+
"or": "or",
|
|
407
|
+
"+": "plus",
|
|
408
|
+
"-": "minus",
|
|
409
|
+
"*": "times",
|
|
410
|
+
"/": "divided by"
|
|
411
|
+
};
|
|
396
412
|
var issueText = (ctx, path) => {
|
|
397
413
|
const list = issuesAt(ctx.byPath, path);
|
|
398
414
|
return list.length ? list.map((i) => i.message).join("; ") : void 0;
|
|
@@ -420,7 +436,9 @@ function renderNode(node, path, ctx, parentOp, side) {
|
|
|
420
436
|
case "scopedvar": {
|
|
421
437
|
const entry = lookup(ctx.catalogue, node.scope, node.name);
|
|
422
438
|
const label = displayName({ scope: node.scope, name: node.name }, ctx.defaultScope);
|
|
423
|
-
|
|
439
|
+
const varPill = pill("var", label, (b) => variableEditor(ctx, path, node, b), { issue: issue ?? (entry ? void 0 : `Unknown property ${label}`) });
|
|
440
|
+
attachPropertyMenu(ctx, node, varPill);
|
|
441
|
+
return varPill;
|
|
424
442
|
}
|
|
425
443
|
case "flagdelta":
|
|
426
444
|
return pill(node.sign === "+" ? "flagpos" : "flagneg", `${node.sign}${node.name || "flag"}`, (b) => flagEditor(ctx, path, node, b), { issue, path });
|
|
@@ -437,7 +455,7 @@ function renderNode(node, path, ctx, parentOp, side) {
|
|
|
437
455
|
}
|
|
438
456
|
case "unary": {
|
|
439
457
|
const row = el("span", "exed-unary");
|
|
440
|
-
row.append(pill("op", UNARY_LABEL[node.op], () => ctx.apply(deleteOrUnwrapNot(ctx, path, node)), { title: "remove" }));
|
|
458
|
+
row.append(pill("op", UNARY_LABEL[node.op], () => ctx.apply(deleteOrUnwrapNot(ctx, path, node)), { title: "remove", aria: `${OP_ARIA[node.op] ?? node.op} (click to remove)` }));
|
|
441
459
|
row.append(renderNode(node.operand, [...path, "operand"], ctx));
|
|
442
460
|
return row;
|
|
443
461
|
}
|
|
@@ -449,7 +467,7 @@ function renderNode(node, path, ctx, parentOp, side) {
|
|
|
449
467
|
const swap = opSwapGroup(node.op);
|
|
450
468
|
const opPill = pill("op", BINARY_LABEL[node.op], (b) => {
|
|
451
469
|
if (swap) operatorEditor(ctx, path, node.op, swap, b);
|
|
452
|
-
}, { title: swap ? "swap operator" : void 0 });
|
|
470
|
+
}, { title: swap ? "swap operator" : void 0, aria: OP_ARIA[node.op] ?? node.op });
|
|
453
471
|
if (!swap) opPill.classList.add("exed-op-structural");
|
|
454
472
|
row.append(opPill);
|
|
455
473
|
row.append(renderNode(node.right, [...path, "right"], ctx, node.op, "right"));
|
|
@@ -470,6 +488,36 @@ var deleteOrUnwrapNot = (ctx, path, node) => (
|
|
|
470
488
|
// clicking the NOT pill strips it (keeps the operand)
|
|
471
489
|
setNodeAt(ctx.getAst(), path, node.operand)
|
|
472
490
|
);
|
|
491
|
+
function attachPropertyMenu(ctx, node, pillEl) {
|
|
492
|
+
if (!ctx.propertyActions) return;
|
|
493
|
+
const actions = ctx.propertyActions({ scope: node.scope, name: node.name });
|
|
494
|
+
if (!actions.length) return;
|
|
495
|
+
const open = ctx.openMenu ?? ctx.openPopover;
|
|
496
|
+
pillEl.addEventListener("contextmenu", (e) => {
|
|
497
|
+
e.preventDefault();
|
|
498
|
+
open(pillEl, (close) => {
|
|
499
|
+
const wrap = el("div", "exed-menu");
|
|
500
|
+
for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
|
|
501
|
+
a.run();
|
|
502
|
+
close();
|
|
503
|
+
}));
|
|
504
|
+
return wrap;
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function isCompoundComparison(node, ctx) {
|
|
509
|
+
if (node.kind !== "binary" || !isComparisonOp(node.op)) return false;
|
|
510
|
+
const numCall = (n) => n.kind === "call" && ctx.dialect.functions[n.name]?.returnType === "number";
|
|
511
|
+
const isNum = (n) => n.kind === "number";
|
|
512
|
+
return numCall(node.left) && isNum(node.right) || numCall(node.right) && isNum(node.left);
|
|
513
|
+
}
|
|
514
|
+
function effectiveDeletePath(ctx, path) {
|
|
515
|
+
const last = path[path.length - 1];
|
|
516
|
+
if (last !== "left" && last !== "right") return path;
|
|
517
|
+
const parentPath = path.slice(0, -1);
|
|
518
|
+
const parent = getNodeAt(ctx.getAst(), parentPath);
|
|
519
|
+
return parent && isCompoundComparison(parent, ctx) ? parentPath : path;
|
|
520
|
+
}
|
|
473
521
|
function boolEditor(ctx, path, value, anchor) {
|
|
474
522
|
ctx.openPopover(anchor, (close) => {
|
|
475
523
|
const wrap = el("div", "exed-menu");
|
|
@@ -581,7 +629,7 @@ function flagEditor(ctx, path, node, anchor) {
|
|
|
581
629
|
return wrap;
|
|
582
630
|
});
|
|
583
631
|
}
|
|
584
|
-
var canDelete = (ctx, path) => !(ctx.requireNonEmpty && path.length === 0);
|
|
632
|
+
var canDelete = (ctx, path) => !(ctx.requireNonEmpty && effectiveDeletePath(ctx, path).length === 0);
|
|
585
633
|
function callEditor(ctx, path, node, anchor) {
|
|
586
634
|
ctx.openPopover(anchor, (close) => {
|
|
587
635
|
const wrap = el("div", "exed-menu");
|
|
@@ -595,7 +643,7 @@ function callEditor(ctx, path, node, anchor) {
|
|
|
595
643
|
}
|
|
596
644
|
if (canDelete(ctx, path)) {
|
|
597
645
|
wrap.append(button("exed-opt danger", "Delete", () => {
|
|
598
|
-
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
646
|
+
ctx.apply(deleteAt(ctx.getAst(), effectiveDeletePath(ctx, path)));
|
|
599
647
|
close();
|
|
600
648
|
}));
|
|
601
649
|
}
|
|
@@ -610,7 +658,7 @@ function variableEditor(ctx, path, node, anchor) {
|
|
|
610
658
|
close();
|
|
611
659
|
},
|
|
612
660
|
...canDelete(ctx, path) ? { footer: button("exed-opt danger", "Delete", () => {
|
|
613
|
-
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
661
|
+
ctx.apply(deleteAt(ctx.getAst(), effectiveDeletePath(ctx, path)));
|
|
614
662
|
close();
|
|
615
663
|
}) } : {}
|
|
616
664
|
}));
|
|
@@ -880,15 +928,17 @@ function renderRow(ctx, row, env) {
|
|
|
880
928
|
if (row.kind === "container") return renderContainer(ctx, row, env);
|
|
881
929
|
return renderLeaf(ctx, row, env);
|
|
882
930
|
}
|
|
883
|
-
function notToggle(ctx, path) {
|
|
884
|
-
|
|
931
|
+
function notToggle(ctx, path, pressed) {
|
|
932
|
+
const b = button("exed-rowbtn", "NOT", () => ctx.apply(toggleContainerNot(ctx.getAst(), path)), "toggle NOT", pressed ? "remove NOT" : "apply NOT");
|
|
933
|
+
b.setAttribute("aria-pressed", String(pressed));
|
|
934
|
+
return b;
|
|
885
935
|
}
|
|
886
936
|
function rowActions(ctx, row, env) {
|
|
887
937
|
const acts = el("div", "exed-rowacts");
|
|
888
|
-
acts.append(notToggle(ctx, row.path));
|
|
938
|
+
acts.append(notToggle(ctx, row.path, row.negated));
|
|
889
939
|
if (env.chainPath && env.count != null && env.index != null) {
|
|
890
|
-
if (env.index > 0) acts.append(button("exed-rowbtn", "\u2191", () => ctx.apply(moveChildInContainer(ctx.getAst(), env.chainPath, env.index, env.index - 1)), "move up"));
|
|
891
|
-
if (env.index < env.count - 1) acts.append(button("exed-rowbtn", "\u2193", () => ctx.apply(moveChildInContainer(ctx.getAst(), env.chainPath, env.index, env.index + 1)), "move down"));
|
|
940
|
+
if (env.index > 0) acts.append(button("exed-rowbtn", "\u2191", () => ctx.apply(moveChildInContainer(ctx.getAst(), env.chainPath, env.index, env.index - 1)), "move up", "move up"));
|
|
941
|
+
if (env.index < env.count - 1) acts.append(button("exed-rowbtn", "\u2193", () => ctx.apply(moveChildInContainer(ctx.getAst(), env.chainPath, env.index, env.index + 1)), "move down", "move down"));
|
|
892
942
|
}
|
|
893
943
|
acts.append(button("exed-rowbtn danger", "\u2715", () => {
|
|
894
944
|
if (env.root) {
|
|
@@ -897,7 +947,7 @@ function rowActions(ctx, row, env) {
|
|
|
897
947
|
}
|
|
898
948
|
const target = redirectDeleteForPlaceholderSibling(ctx.getAst(), row.path);
|
|
899
949
|
ctx.apply(deleteAt(ctx.getAst(), target));
|
|
900
|
-
}, "delete"));
|
|
950
|
+
}, "delete", "delete this condition"));
|
|
901
951
|
return acts;
|
|
902
952
|
}
|
|
903
953
|
function renderLeaf(ctx, row, env) {
|
|
@@ -925,8 +975,8 @@ function renderContainer(ctx, row, env) {
|
|
|
925
975
|
head.append(el("span", "exed-grouplabel", [label]));
|
|
926
976
|
}
|
|
927
977
|
const headActs = el("div", "exed-rowacts");
|
|
928
|
-
headActs.append(notToggle(ctx, row.path));
|
|
929
|
-
if (!env.root) headActs.append(button("exed-rowbtn danger", "\u2715", () => ctx.apply(deleteAt(ctx.getAst(), row.path)), "delete group"));
|
|
978
|
+
headActs.append(notToggle(ctx, row.path, row.negated));
|
|
979
|
+
if (!env.root) headActs.append(button("exed-rowbtn danger", "\u2715", () => ctx.apply(deleteAt(ctx.getAst(), row.path)), "delete group", "delete this group"));
|
|
930
980
|
head.append(headActs);
|
|
931
981
|
box.append(head);
|
|
932
982
|
const body = el("div", "exed-groupbody");
|
|
@@ -1079,6 +1129,7 @@ function mountExpressionEditor(host, opts) {
|
|
|
1079
1129
|
},
|
|
1080
1130
|
...opts.requireNonEmpty ? { requireNonEmpty: true } : {},
|
|
1081
1131
|
...opts.valueEnumValues ? { valueEnumValues: opts.valueEnumValues } : {},
|
|
1132
|
+
...opts.propertyActions ? { propertyActions: opts.propertyActions } : {},
|
|
1082
1133
|
...opts.pickNode ? { pickNode: opts.pickNode } : {},
|
|
1083
1134
|
...opts.nodeLabel ? { nodeLabel: opts.nodeLabel } : {}
|
|
1084
1135
|
};
|
|
@@ -1115,7 +1166,7 @@ function mountExpressionEditor(host, opts) {
|
|
|
1115
1166
|
function rawArea() {
|
|
1116
1167
|
const ta = el("textarea", "exed-raw");
|
|
1117
1168
|
ta.value = src;
|
|
1118
|
-
ta.placeholder = "@gold > 0 and @met_anna";
|
|
1169
|
+
ta.placeholder = opts.rawPlaceholder ?? "@gold > 0 and @met_anna";
|
|
1119
1170
|
ta.rows = 2;
|
|
1120
1171
|
ta.addEventListener("input", () => {
|
|
1121
1172
|
src = ta.value;
|
|
@@ -1614,7 +1665,13 @@ function frozenCtx(src, o) {
|
|
|
1614
1665
|
pickNode: () => {
|
|
1615
1666
|
},
|
|
1616
1667
|
// enables labelled node pills; never fires (preview is read-only)
|
|
1617
|
-
...o.nodeLabel ? { nodeLabel: o.nodeLabel } : {}
|
|
1668
|
+
...o.nodeLabel ? { nodeLabel: o.nodeLabel } : {},
|
|
1669
|
+
...o.propertyActions ? {
|
|
1670
|
+
propertyActions: o.propertyActions,
|
|
1671
|
+
openMenu: (anchor, render) => {
|
|
1672
|
+
openPopover(anchor, render, { container: o.popoverContainer });
|
|
1673
|
+
}
|
|
1674
|
+
} : {}
|
|
1618
1675
|
};
|
|
1619
1676
|
return { ctx, ast: v.ast };
|
|
1620
1677
|
}
|
|
@@ -1623,7 +1680,8 @@ function exprPills(src, o) {
|
|
|
1623
1680
|
return ast ? renderNode(ast, [], ctx) : el("span", "exed-preview-raw", [src]);
|
|
1624
1681
|
}
|
|
1625
1682
|
function renderConditionPreview(src, o) {
|
|
1626
|
-
|
|
1683
|
+
const cls = o.propertyActions ? "exed-preview exed-preview-interactive" : "exed-preview";
|
|
1684
|
+
return el("div", cls, [exprPills(src, o)]);
|
|
1627
1685
|
}
|
|
1628
1686
|
function renderEffectsPreview(effects, o) {
|
|
1629
1687
|
const wrap = el("div", "exed-preview exed-preview-effects");
|