@wildwinter/expr-editor 0.3.0 → 0.5.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 +12 -0
- package/dist/index.cjs +65 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +65 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,18 @@ Notable options beyond the basics:
|
|
|
43
43
|
`document.body`). Pass a container inside a focus-trapping dialog (Radix,
|
|
44
44
|
etc.) so opening a pill's popover counts as "inside" that layer and does not
|
|
45
45
|
dismiss the dialog; the popover is then positioned relative to that container.
|
|
46
|
+
- `requireNonEmpty` — block a delete that would empty the whole expression
|
|
47
|
+
(single-value fields that must always hold a term). Conditions leave it off
|
|
48
|
+
(an empty condition is the valid "always" state).
|
|
49
|
+
- `valueEnumValues` + `valueField` — flat single-value editing for an
|
|
50
|
+
outcome-style value cell: `valueEnumValues` offers a target's enum values on
|
|
51
|
+
the root literal, and `valueField` makes an empty value render one editable
|
|
52
|
+
"set a value…" pill instead of the condition clause-menu empty state.
|
|
53
|
+
- `flagValue: { target }` — flat compact editor for a `set_flags(@target, …)`
|
|
54
|
+
value whose target is implied by context: renders only the flag-delta pills
|
|
55
|
+
plus an "+ flag" chip, hiding the function name and target arg, and seeds the
|
|
56
|
+
call on the first flag add. Pair with `requireNonEmpty` so the last flag
|
|
57
|
+
can't be removed.
|
|
46
58
|
- `setText(on)` — the host's raw-text toggle (`</>`); unparseable input falls
|
|
47
59
|
back to raw text automatically.
|
|
48
60
|
|
package/dist/index.cjs
CHANGED
|
@@ -490,11 +490,14 @@ function numberEditor(ctx, path, value, anchor) {
|
|
|
490
490
|
function stringEditor(ctx, path, node, anchor) {
|
|
491
491
|
const peer = findEnumPeer(ctx.getAst(), path);
|
|
492
492
|
const enumEntry = peer ? lookup(ctx.catalogue, peer.scope, peer.name) : null;
|
|
493
|
+
const rootValueEnums = path.length === 0 && ctx.valueEnumValues?.length ? ctx.valueEnumValues : null;
|
|
494
|
+
const enumValues = enumEntry?.enumValues?.length ? enumEntry.enumValues : rootValueEnums;
|
|
495
|
+
const enumHeadLabel = enumEntry ? `${displayName(enumEntry, ctx.defaultScope)} value` : "Value";
|
|
493
496
|
ctx.openPopover(anchor, (close) => {
|
|
494
|
-
if (
|
|
497
|
+
if (enumValues?.length) {
|
|
495
498
|
const wrap = el("div", "exed-menu");
|
|
496
|
-
wrap.append(el("div", "exed-menu-head", [
|
|
497
|
-
for (const v of
|
|
499
|
+
wrap.append(el("div", "exed-menu-head", [enumHeadLabel]));
|
|
500
|
+
for (const v of enumValues) {
|
|
498
501
|
wrap.append(button(`exed-opt${v === node.value ? " sel" : ""}`, v, () => {
|
|
499
502
|
replace(ctx, path, strLit(v));
|
|
500
503
|
close();
|
|
@@ -538,7 +541,9 @@ function flagEditor(ctx, path, node, anchor) {
|
|
|
538
541
|
const call = getNodeAt(ctx.getAst(), callPath);
|
|
539
542
|
const flagsVar = call?.kind === "call" ? call.args[0] : void 0;
|
|
540
543
|
const entry = flagsVar?.kind === "scopedvar" ? lookup(ctx.catalogue, flagsVar.scope, flagsVar.name) : null;
|
|
544
|
+
const flagCount = call?.kind === "call" ? call.args.filter((a) => a.kind === "flagdelta").length : 0;
|
|
541
545
|
const used = call?.kind === "call" ? call.args.filter((a, i) => a.kind === "flagdelta" && i !== path[path.length - 1]).map((a) => a.name) : [];
|
|
546
|
+
const canRemove = !(ctx.requireNonEmpty && flagCount <= 1);
|
|
542
547
|
ctx.openPopover(anchor, (close) => {
|
|
543
548
|
const wrap = el("div", "exed-menu");
|
|
544
549
|
wrap.append(el("div", "exed-menu-head", ["Flag set?"]));
|
|
@@ -563,9 +568,16 @@ function flagEditor(ctx, path, node, anchor) {
|
|
|
563
568
|
close();
|
|
564
569
|
} }));
|
|
565
570
|
}
|
|
571
|
+
if (canRemove) {
|
|
572
|
+
wrap.append(button("exed-opt danger", "Remove flag", () => {
|
|
573
|
+
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
574
|
+
close();
|
|
575
|
+
}));
|
|
576
|
+
}
|
|
566
577
|
return wrap;
|
|
567
578
|
});
|
|
568
579
|
}
|
|
580
|
+
var canDelete = (ctx, path) => !(ctx.requireNonEmpty && path.length === 0);
|
|
569
581
|
function callEditor(ctx, path, node, anchor) {
|
|
570
582
|
ctx.openPopover(anchor, (close) => {
|
|
571
583
|
const wrap = el("div", "exed-menu");
|
|
@@ -577,10 +589,12 @@ function callEditor(ctx, path, node, anchor) {
|
|
|
577
589
|
close();
|
|
578
590
|
}));
|
|
579
591
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
592
|
+
if (canDelete(ctx, path)) {
|
|
593
|
+
wrap.append(button("exed-opt danger", "Delete", () => {
|
|
594
|
+
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
595
|
+
close();
|
|
596
|
+
}));
|
|
597
|
+
}
|
|
584
598
|
return wrap;
|
|
585
599
|
});
|
|
586
600
|
}
|
|
@@ -591,10 +605,10 @@ function variableEditor(ctx, path, node, anchor) {
|
|
|
591
605
|
replace(ctx, path, scopedVar(entry.scope, entry.name));
|
|
592
606
|
close();
|
|
593
607
|
},
|
|
594
|
-
footer: button("exed-opt danger", "Delete", () => {
|
|
608
|
+
...canDelete(ctx, path) ? { footer: button("exed-opt danger", "Delete", () => {
|
|
595
609
|
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
596
610
|
close();
|
|
597
|
-
})
|
|
611
|
+
}) } : {}
|
|
598
612
|
}));
|
|
599
613
|
}
|
|
600
614
|
function propertyPicker(ctx, opts) {
|
|
@@ -1059,6 +1073,8 @@ function mountExpressionEditor(host, opts) {
|
|
|
1059
1073
|
requestFocus: (p) => {
|
|
1060
1074
|
pendingFocus = p;
|
|
1061
1075
|
},
|
|
1076
|
+
...opts.requireNonEmpty ? { requireNonEmpty: true } : {},
|
|
1077
|
+
...opts.valueEnumValues ? { valueEnumValues: opts.valueEnumValues } : {},
|
|
1062
1078
|
...opts.pickNode ? { pickNode: opts.pickNode } : {},
|
|
1063
1079
|
...opts.nodeLabel ? { nodeLabel: opts.nodeLabel } : {}
|
|
1064
1080
|
};
|
|
@@ -1067,10 +1083,14 @@ function mountExpressionEditor(host, opts) {
|
|
|
1067
1083
|
closePopover();
|
|
1068
1084
|
host.replaceChildren();
|
|
1069
1085
|
const v = validateSource(src, opts.schema, opts.dialect);
|
|
1070
|
-
|
|
1086
|
+
const flagCompact = !!opts.flagValue && (opts.mode ?? "tree") === "flat";
|
|
1087
|
+
const astIsFlagCall = v.ast?.kind === "call" && (v.ast.name === "set_flags" || v.ast.name === "check_flags");
|
|
1088
|
+
if (raw || v.unparseable || flagCompact && !!v.ast && !astIsFlagCall) {
|
|
1071
1089
|
host.append(rawArea());
|
|
1090
|
+
} else if (flagCompact) {
|
|
1091
|
+
host.append(flagValueBody(astIsFlagCall ? v.ast : null));
|
|
1072
1092
|
} else if (!src.trim() || !v.ast) {
|
|
1073
|
-
host.append(emptyState());
|
|
1093
|
+
host.append(opts.valueField ? valueEmptyState() : emptyState());
|
|
1074
1094
|
} else {
|
|
1075
1095
|
const ctx = buildCtx(v.ast);
|
|
1076
1096
|
const body = el("div", "exed-body");
|
|
@@ -1114,6 +1134,40 @@ function mountExpressionEditor(host, opts) {
|
|
|
1114
1134
|
}));
|
|
1115
1135
|
return wrap;
|
|
1116
1136
|
}
|
|
1137
|
+
function valueEmptyState() {
|
|
1138
|
+
const ctx = buildCtx(strLit(""));
|
|
1139
|
+
return el("div", "exed-flat", [renderNode(strLit(""), [], ctx)]);
|
|
1140
|
+
}
|
|
1141
|
+
function parseRef(ref) {
|
|
1142
|
+
const bare = ref.replace(/^@/, "");
|
|
1143
|
+
const dot = bare.indexOf(".");
|
|
1144
|
+
return dot >= 0 ? scopedVar(bare.slice(0, dot), bare.slice(dot + 1)) : scopedVar(defaultScope, bare);
|
|
1145
|
+
}
|
|
1146
|
+
function flagValueBody(call) {
|
|
1147
|
+
const wrap = el("div", "exed-flat exed-flagvalue");
|
|
1148
|
+
if (call) {
|
|
1149
|
+
const ctx = buildCtx(call);
|
|
1150
|
+
call.args.forEach((arg, i) => {
|
|
1151
|
+
if (arg.kind === "flagdelta") wrap.append(renderNode(arg, ["args", i], ctx));
|
|
1152
|
+
});
|
|
1153
|
+
wrap.append(addFlagChip(call));
|
|
1154
|
+
} else {
|
|
1155
|
+
wrap.append(addFlagChip(null));
|
|
1156
|
+
}
|
|
1157
|
+
return wrap;
|
|
1158
|
+
}
|
|
1159
|
+
function addFlagChip(call) {
|
|
1160
|
+
return button("exed-add exed-addflag", "+ flag", () => {
|
|
1161
|
+
if (call) {
|
|
1162
|
+
pendingFocus = ["args", call.args.length];
|
|
1163
|
+
emit(toSrc({ ...call, args: [...call.args, flagDelta("+", "")] }));
|
|
1164
|
+
} else {
|
|
1165
|
+
const seeded = callNode("set_flags", [parseRef(opts.flagValue.target), flagDelta("+", "")]);
|
|
1166
|
+
pendingFocus = ["args", 1];
|
|
1167
|
+
emit(toSrc(seeded));
|
|
1168
|
+
}
|
|
1169
|
+
}, "add a flag");
|
|
1170
|
+
}
|
|
1117
1171
|
function addTermControl(ctx) {
|
|
1118
1172
|
const boolean = opts.addTerm === "boolean";
|
|
1119
1173
|
const ops = boolean ? ["and", "or"] : ARITHMETIC_OPS;
|