@wildwinter/expr-editor 0.6.0 → 0.8.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 +8 -0
- package/dist/index.cjs +85 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +85 -37
- package/dist/index.js.map +1 -1
- package/dist/styles.css +26 -0
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,14 @@ 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.
|
|
58
66
|
- `setText(on)` — the host's raw-text toggle (`</>`); unparseable input falls
|
|
59
67
|
back to raw text automatically.
|
|
60
68
|
|
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"));
|
|
@@ -458,10 +476,47 @@ function renderNode(node, path, ctx, parentOp, side) {
|
|
|
458
476
|
}
|
|
459
477
|
}
|
|
460
478
|
}
|
|
479
|
+
function signToggle(current, onPick) {
|
|
480
|
+
const row = el("div", "exed-sign");
|
|
481
|
+
["+", "-"].forEach((s) => {
|
|
482
|
+
const cls = `exed-signbtn${s === current ? s === "+" ? " sel-pos" : " sel-neg" : ""}`;
|
|
483
|
+
row.append(button(cls, s === "+" ? "+ set" : "\u2212 unset", () => onPick(s)));
|
|
484
|
+
});
|
|
485
|
+
return row;
|
|
486
|
+
}
|
|
461
487
|
var deleteOrUnwrapNot = (ctx, path, node) => (
|
|
462
488
|
// clicking the NOT pill strips it (keeps the operand)
|
|
463
489
|
setNodeAt(ctx.getAst(), path, node.operand)
|
|
464
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
|
+
pillEl.addEventListener("contextmenu", (e) => {
|
|
496
|
+
e.preventDefault();
|
|
497
|
+
ctx.openPopover(pillEl, (close) => {
|
|
498
|
+
const wrap = el("div", "exed-menu");
|
|
499
|
+
for (const a of actions) wrap.append(button("exed-opt", a.label, () => {
|
|
500
|
+
a.run();
|
|
501
|
+
close();
|
|
502
|
+
}));
|
|
503
|
+
return wrap;
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
function isCompoundComparison(node, ctx) {
|
|
508
|
+
if (node.kind !== "binary" || !isComparisonOp(node.op)) return false;
|
|
509
|
+
const numCall = (n) => n.kind === "call" && ctx.dialect.functions[n.name]?.returnType === "number";
|
|
510
|
+
const isNum = (n) => n.kind === "number";
|
|
511
|
+
return numCall(node.left) && isNum(node.right) || numCall(node.right) && isNum(node.left);
|
|
512
|
+
}
|
|
513
|
+
function effectiveDeletePath(ctx, path) {
|
|
514
|
+
const last = path[path.length - 1];
|
|
515
|
+
if (last !== "left" && last !== "right") return path;
|
|
516
|
+
const parentPath = path.slice(0, -1);
|
|
517
|
+
const parent = getNodeAt(ctx.getAst(), parentPath);
|
|
518
|
+
return parent && isCompoundComparison(parent, ctx) ? parentPath : path;
|
|
519
|
+
}
|
|
465
520
|
function boolEditor(ctx, path, value, anchor) {
|
|
466
521
|
ctx.openPopover(anchor, (close) => {
|
|
467
522
|
const wrap = el("div", "exed-menu");
|
|
@@ -547,14 +602,10 @@ function flagEditor(ctx, path, node, anchor) {
|
|
|
547
602
|
ctx.openPopover(anchor, (close) => {
|
|
548
603
|
const wrap = el("div", "exed-menu");
|
|
549
604
|
wrap.append(el("div", "exed-menu-head", ["Flag set?"]));
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
close();
|
|
555
|
-
}));
|
|
556
|
-
}
|
|
557
|
-
wrap.append(signRow);
|
|
605
|
+
wrap.append(signToggle(node.sign, (s) => {
|
|
606
|
+
replace(ctx, path, flagDelta(s, node.name));
|
|
607
|
+
close();
|
|
608
|
+
}));
|
|
558
609
|
const names = (entry?.enumValues ?? []).filter((n) => !used.includes(n));
|
|
559
610
|
if (names.length) {
|
|
560
611
|
wrap.append(el("div", "exed-menu-head", ["Flag"]));
|
|
@@ -577,7 +628,7 @@ function flagEditor(ctx, path, node, anchor) {
|
|
|
577
628
|
return wrap;
|
|
578
629
|
});
|
|
579
630
|
}
|
|
580
|
-
var canDelete = (ctx, path) => !(ctx.requireNonEmpty && path.length === 0);
|
|
631
|
+
var canDelete = (ctx, path) => !(ctx.requireNonEmpty && effectiveDeletePath(ctx, path).length === 0);
|
|
581
632
|
function callEditor(ctx, path, node, anchor) {
|
|
582
633
|
ctx.openPopover(anchor, (close) => {
|
|
583
634
|
const wrap = el("div", "exed-menu");
|
|
@@ -591,7 +642,7 @@ function callEditor(ctx, path, node, anchor) {
|
|
|
591
642
|
}
|
|
592
643
|
if (canDelete(ctx, path)) {
|
|
593
644
|
wrap.append(button("exed-opt danger", "Delete", () => {
|
|
594
|
-
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
645
|
+
ctx.apply(deleteAt(ctx.getAst(), effectiveDeletePath(ctx, path)));
|
|
595
646
|
close();
|
|
596
647
|
}));
|
|
597
648
|
}
|
|
@@ -606,7 +657,7 @@ function variableEditor(ctx, path, node, anchor) {
|
|
|
606
657
|
close();
|
|
607
658
|
},
|
|
608
659
|
...canDelete(ctx, path) ? { footer: button("exed-opt danger", "Delete", () => {
|
|
609
|
-
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
660
|
+
ctx.apply(deleteAt(ctx.getAst(), effectiveDeletePath(ctx, path)));
|
|
610
661
|
close();
|
|
611
662
|
}) } : {}
|
|
612
663
|
}));
|
|
@@ -876,15 +927,17 @@ function renderRow(ctx, row, env) {
|
|
|
876
927
|
if (row.kind === "container") return renderContainer(ctx, row, env);
|
|
877
928
|
return renderLeaf(ctx, row, env);
|
|
878
929
|
}
|
|
879
|
-
function notToggle(ctx, path) {
|
|
880
|
-
|
|
930
|
+
function notToggle(ctx, path, pressed) {
|
|
931
|
+
const b = button("exed-rowbtn", "NOT", () => ctx.apply(toggleContainerNot(ctx.getAst(), path)), "toggle NOT", pressed ? "remove NOT" : "apply NOT");
|
|
932
|
+
b.setAttribute("aria-pressed", String(pressed));
|
|
933
|
+
return b;
|
|
881
934
|
}
|
|
882
935
|
function rowActions(ctx, row, env) {
|
|
883
936
|
const acts = el("div", "exed-rowacts");
|
|
884
|
-
acts.append(notToggle(ctx, row.path));
|
|
937
|
+
acts.append(notToggle(ctx, row.path, row.negated));
|
|
885
938
|
if (env.chainPath && env.count != null && env.index != null) {
|
|
886
|
-
if (env.index > 0) acts.append(button("exed-rowbtn", "\u2191", () => ctx.apply(moveChildInContainer(ctx.getAst(), env.chainPath, env.index, env.index - 1)), "move up"));
|
|
887
|
-
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"));
|
|
939
|
+
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"));
|
|
940
|
+
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"));
|
|
888
941
|
}
|
|
889
942
|
acts.append(button("exed-rowbtn danger", "\u2715", () => {
|
|
890
943
|
if (env.root) {
|
|
@@ -893,7 +946,7 @@ function rowActions(ctx, row, env) {
|
|
|
893
946
|
}
|
|
894
947
|
const target = redirectDeleteForPlaceholderSibling(ctx.getAst(), row.path);
|
|
895
948
|
ctx.apply(deleteAt(ctx.getAst(), target));
|
|
896
|
-
}, "delete"));
|
|
949
|
+
}, "delete", "delete this condition"));
|
|
897
950
|
return acts;
|
|
898
951
|
}
|
|
899
952
|
function renderLeaf(ctx, row, env) {
|
|
@@ -921,8 +974,8 @@ function renderContainer(ctx, row, env) {
|
|
|
921
974
|
head.append(el("span", "exed-grouplabel", [label]));
|
|
922
975
|
}
|
|
923
976
|
const headActs = el("div", "exed-rowacts");
|
|
924
|
-
headActs.append(notToggle(ctx, row.path));
|
|
925
|
-
if (!env.root) headActs.append(button("exed-rowbtn danger", "\u2715", () => ctx.apply(deleteAt(ctx.getAst(), row.path)), "delete group"));
|
|
977
|
+
headActs.append(notToggle(ctx, row.path, row.negated));
|
|
978
|
+
if (!env.root) headActs.append(button("exed-rowbtn danger", "\u2715", () => ctx.apply(deleteAt(ctx.getAst(), row.path)), "delete group", "delete this group"));
|
|
926
979
|
head.append(headActs);
|
|
927
980
|
box.append(head);
|
|
928
981
|
const body = el("div", "exed-groupbody");
|
|
@@ -1075,6 +1128,7 @@ function mountExpressionEditor(host, opts) {
|
|
|
1075
1128
|
},
|
|
1076
1129
|
...opts.requireNonEmpty ? { requireNonEmpty: true } : {},
|
|
1077
1130
|
...opts.valueEnumValues ? { valueEnumValues: opts.valueEnumValues } : {},
|
|
1131
|
+
...opts.propertyActions ? { propertyActions: opts.propertyActions } : {},
|
|
1078
1132
|
...opts.pickNode ? { pickNode: opts.pickNode } : {},
|
|
1079
1133
|
...opts.nodeLabel ? { nodeLabel: opts.nodeLabel } : {}
|
|
1080
1134
|
};
|
|
@@ -1111,7 +1165,7 @@ function mountExpressionEditor(host, opts) {
|
|
|
1111
1165
|
function rawArea() {
|
|
1112
1166
|
const ta = el("textarea", "exed-raw");
|
|
1113
1167
|
ta.value = src;
|
|
1114
|
-
ta.placeholder = "@gold > 0 and @met_anna";
|
|
1168
|
+
ta.placeholder = opts.rawPlaceholder ?? "@gold > 0 and @met_anna";
|
|
1115
1169
|
ta.rows = 2;
|
|
1116
1170
|
ta.addEventListener("input", () => {
|
|
1117
1171
|
src = ta.value;
|
|
@@ -1164,21 +1218,15 @@ function mountExpressionEditor(host, opts) {
|
|
|
1164
1218
|
let sign = "+";
|
|
1165
1219
|
const wrap = el("div", "exed-menu");
|
|
1166
1220
|
wrap.append(el("div", "exed-menu-head", ["Add flag"]));
|
|
1167
|
-
const
|
|
1221
|
+
const signHost = el("div");
|
|
1168
1222
|
const drawSign = () => {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}),
|
|
1174
|
-
button(`exed-opt${sign === "-" ? " sel" : ""}`, "\u2212 unset", () => {
|
|
1175
|
-
sign = "-";
|
|
1176
|
-
drawSign();
|
|
1177
|
-
})
|
|
1178
|
-
);
|
|
1223
|
+
signHost.replaceChildren(signToggle(sign, (s) => {
|
|
1224
|
+
sign = s;
|
|
1225
|
+
drawSign();
|
|
1226
|
+
}));
|
|
1179
1227
|
};
|
|
1180
1228
|
drawSign();
|
|
1181
|
-
wrap.append(
|
|
1229
|
+
wrap.append(signHost);
|
|
1182
1230
|
const commit = (name) => {
|
|
1183
1231
|
const delta = flagDelta(sign, name);
|
|
1184
1232
|
emit(toSrc(call ? { ...call, args: [...call.args, delta] } : callNode("set_flags", [parseRef(opts.flagValue.target), delta])));
|