@wildwinter/expr-editor 0.2.0 → 0.4.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 +11 -0
- package/dist/index.cjs +44 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +44 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,6 +39,17 @@ Notable options beyond the basics:
|
|
|
39
39
|
host can suppress its own validation display mid-edit.
|
|
40
40
|
- `messages: false` — hide the editor's internal validation list when the host
|
|
41
41
|
renders its own.
|
|
42
|
+
- `popoverContainer` — where the popover micro-editors mount (default
|
|
43
|
+
`document.body`). Pass a container inside a focus-trapping dialog (Radix,
|
|
44
|
+
etc.) so opening a pill's popover counts as "inside" that layer and does not
|
|
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.
|
|
42
53
|
- `setText(on)` — the host's raw-text toggle (`</>`); unparseable input falls
|
|
43
54
|
back to raw text automatically.
|
|
44
55
|
|
package/dist/index.cjs
CHANGED
|
@@ -312,7 +312,8 @@ function button(cls, label, onClick, title) {
|
|
|
312
312
|
b.addEventListener("click", onClick);
|
|
313
313
|
return b;
|
|
314
314
|
}
|
|
315
|
-
function openPopover(anchor, render,
|
|
315
|
+
function openPopover(anchor, render, opts = {}) {
|
|
316
|
+
const container = opts.container ?? document.body;
|
|
316
317
|
const pop = el("div", "exed-pop");
|
|
317
318
|
let closed = false;
|
|
318
319
|
const close = () => {
|
|
@@ -321,7 +322,7 @@ function openPopover(anchor, render, onClose) {
|
|
|
321
322
|
document.removeEventListener("pointerdown", onDown, true);
|
|
322
323
|
document.removeEventListener("keydown", onKey, true);
|
|
323
324
|
pop.remove();
|
|
324
|
-
onClose?.();
|
|
325
|
+
opts.onClose?.();
|
|
325
326
|
};
|
|
326
327
|
const onDown = (e) => {
|
|
327
328
|
const t = e.target;
|
|
@@ -334,15 +335,21 @@ function openPopover(anchor, render, onClose) {
|
|
|
334
335
|
}
|
|
335
336
|
};
|
|
336
337
|
pop.append(render(close));
|
|
337
|
-
|
|
338
|
+
container.append(pop);
|
|
338
339
|
const a = anchor.getBoundingClientRect();
|
|
339
340
|
const ph = pop.getBoundingClientRect();
|
|
340
341
|
let top = a.bottom + 4;
|
|
341
342
|
if (top + ph.height > window.innerHeight - 8 && a.top - ph.height - 4 > 8) top = a.top - ph.height - 4;
|
|
342
343
|
let left = a.left;
|
|
343
344
|
if (left + ph.width > window.innerWidth - 8) left = Math.max(8, window.innerWidth - 8 - ph.width);
|
|
344
|
-
|
|
345
|
-
|
|
345
|
+
if (container === document.body) {
|
|
346
|
+
pop.style.top = `${Math.round(top + window.scrollY)}px`;
|
|
347
|
+
pop.style.left = `${Math.round(left + window.scrollX)}px`;
|
|
348
|
+
} else {
|
|
349
|
+
const cr = container.getBoundingClientRect();
|
|
350
|
+
pop.style.top = `${Math.round(top - cr.top + container.scrollTop)}px`;
|
|
351
|
+
pop.style.left = `${Math.round(left - cr.left + container.scrollLeft)}px`;
|
|
352
|
+
}
|
|
346
353
|
setTimeout(() => {
|
|
347
354
|
document.addEventListener("pointerdown", onDown, true);
|
|
348
355
|
document.addEventListener("keydown", onKey, true);
|
|
@@ -483,11 +490,14 @@ function numberEditor(ctx, path, value, anchor) {
|
|
|
483
490
|
function stringEditor(ctx, path, node, anchor) {
|
|
484
491
|
const peer = findEnumPeer(ctx.getAst(), path);
|
|
485
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";
|
|
486
496
|
ctx.openPopover(anchor, (close) => {
|
|
487
|
-
if (
|
|
497
|
+
if (enumValues?.length) {
|
|
488
498
|
const wrap = el("div", "exed-menu");
|
|
489
|
-
wrap.append(el("div", "exed-menu-head", [
|
|
490
|
-
for (const v of
|
|
499
|
+
wrap.append(el("div", "exed-menu-head", [enumHeadLabel]));
|
|
500
|
+
for (const v of enumValues) {
|
|
491
501
|
wrap.append(button(`exed-opt${v === node.value ? " sel" : ""}`, v, () => {
|
|
492
502
|
replace(ctx, path, strLit(v));
|
|
493
503
|
close();
|
|
@@ -559,6 +569,7 @@ function flagEditor(ctx, path, node, anchor) {
|
|
|
559
569
|
return wrap;
|
|
560
570
|
});
|
|
561
571
|
}
|
|
572
|
+
var canDelete = (ctx, path) => !(ctx.requireNonEmpty && path.length === 0);
|
|
562
573
|
function callEditor(ctx, path, node, anchor) {
|
|
563
574
|
ctx.openPopover(anchor, (close) => {
|
|
564
575
|
const wrap = el("div", "exed-menu");
|
|
@@ -570,10 +581,12 @@ function callEditor(ctx, path, node, anchor) {
|
|
|
570
581
|
close();
|
|
571
582
|
}));
|
|
572
583
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
584
|
+
if (canDelete(ctx, path)) {
|
|
585
|
+
wrap.append(button("exed-opt danger", "Delete", () => {
|
|
586
|
+
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
587
|
+
close();
|
|
588
|
+
}));
|
|
589
|
+
}
|
|
577
590
|
return wrap;
|
|
578
591
|
});
|
|
579
592
|
}
|
|
@@ -584,10 +597,10 @@ function variableEditor(ctx, path, node, anchor) {
|
|
|
584
597
|
replace(ctx, path, scopedVar(entry.scope, entry.name));
|
|
585
598
|
close();
|
|
586
599
|
},
|
|
587
|
-
footer: button("exed-opt danger", "Delete", () => {
|
|
600
|
+
...canDelete(ctx, path) ? { footer: button("exed-opt danger", "Delete", () => {
|
|
588
601
|
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
589
602
|
close();
|
|
590
|
-
})
|
|
603
|
+
}) } : {}
|
|
591
604
|
}));
|
|
592
605
|
}
|
|
593
606
|
function propertyPicker(ctx, opts) {
|
|
@@ -1039,9 +1052,12 @@ function mountExpressionEditor(host, opts) {
|
|
|
1039
1052
|
apply: (next) => emit(toSrc(next)),
|
|
1040
1053
|
openPopover: (anchor, r) => {
|
|
1041
1054
|
closePopover();
|
|
1042
|
-
const pop = openPopover(anchor, r,
|
|
1043
|
-
|
|
1044
|
-
|
|
1055
|
+
const pop = openPopover(anchor, r, {
|
|
1056
|
+
container: opts.popoverContainer,
|
|
1057
|
+
onClose: () => {
|
|
1058
|
+
if (activePopover === pop) activePopover = null;
|
|
1059
|
+
setEditing(false);
|
|
1060
|
+
}
|
|
1045
1061
|
});
|
|
1046
1062
|
activePopover = pop;
|
|
1047
1063
|
setEditing(true);
|
|
@@ -1049,6 +1065,8 @@ function mountExpressionEditor(host, opts) {
|
|
|
1049
1065
|
requestFocus: (p) => {
|
|
1050
1066
|
pendingFocus = p;
|
|
1051
1067
|
},
|
|
1068
|
+
...opts.requireNonEmpty ? { requireNonEmpty: true } : {},
|
|
1069
|
+
...opts.valueEnumValues ? { valueEnumValues: opts.valueEnumValues } : {},
|
|
1052
1070
|
...opts.pickNode ? { pickNode: opts.pickNode } : {},
|
|
1053
1071
|
...opts.nodeLabel ? { nodeLabel: opts.nodeLabel } : {}
|
|
1054
1072
|
};
|
|
@@ -1060,7 +1078,7 @@ function mountExpressionEditor(host, opts) {
|
|
|
1060
1078
|
if (raw || v.unparseable) {
|
|
1061
1079
|
host.append(rawArea());
|
|
1062
1080
|
} else if (!src.trim() || !v.ast) {
|
|
1063
|
-
host.append(emptyState());
|
|
1081
|
+
host.append(opts.valueField ? valueEmptyState() : emptyState());
|
|
1064
1082
|
} else {
|
|
1065
1083
|
const ctx = buildCtx(v.ast);
|
|
1066
1084
|
const body = el("div", "exed-body");
|
|
@@ -1104,6 +1122,10 @@ function mountExpressionEditor(host, opts) {
|
|
|
1104
1122
|
}));
|
|
1105
1123
|
return wrap;
|
|
1106
1124
|
}
|
|
1125
|
+
function valueEmptyState() {
|
|
1126
|
+
const ctx = buildCtx(strLit(""));
|
|
1127
|
+
return el("div", "exed-flat", [renderNode(strLit(""), [], ctx)]);
|
|
1128
|
+
}
|
|
1107
1129
|
function addTermControl(ctx) {
|
|
1108
1130
|
const boolean = opts.addTerm === "boolean";
|
|
1109
1131
|
const ops = boolean ? ["and", "or"] : ARITHMETIC_OPS;
|
|
@@ -1333,7 +1355,7 @@ function mountEffectsEditor(host, opts) {
|
|
|
1333
1355
|
close();
|
|
1334
1356
|
},
|
|
1335
1357
|
onCancel: close
|
|
1336
|
-
}));
|
|
1358
|
+
}), { container: opts.popoverContainer });
|
|
1337
1359
|
};
|
|
1338
1360
|
const targetType = (target) => opts.catalogue.find((e) => refOf(e, defaultScope) === target)?.type;
|
|
1339
1361
|
const valueDisplay = (value, onChange, type) => {
|
|
@@ -1348,6 +1370,7 @@ function mountEffectsEditor(host, opts) {
|
|
|
1348
1370
|
functions: opts.functions,
|
|
1349
1371
|
mode: "flat",
|
|
1350
1372
|
...addTerm ? { addTerm } : {},
|
|
1373
|
+
...opts.popoverContainer ? { popoverContainer: opts.popoverContainer } : {},
|
|
1351
1374
|
text: textMode,
|
|
1352
1375
|
onChange
|
|
1353
1376
|
}));
|
|
@@ -1387,7 +1410,7 @@ function mountEffectsEditor(host, opts) {
|
|
|
1387
1410
|
wrap.append(search, list);
|
|
1388
1411
|
setTimeout(() => search.focus(), 0);
|
|
1389
1412
|
return wrap;
|
|
1390
|
-
});
|
|
1413
|
+
}, { container: opts.popoverContainer });
|
|
1391
1414
|
};
|
|
1392
1415
|
const iconBtn = (glyph, title, onClick, danger = false) => button(`exed-eff-icon${danger ? " danger" : ""}`, glyph, onClick, title);
|
|
1393
1416
|
function setRow(eff, i) {
|
|
@@ -1460,7 +1483,7 @@ function mountEffectsEditor(host, opts) {
|
|
|
1460
1483
|
wrap.append(button("exed-btn primary", "Apply", apply));
|
|
1461
1484
|
setTimeout(() => input.focus(), 0);
|
|
1462
1485
|
return wrap;
|
|
1463
|
-
});
|
|
1486
|
+
}, { container: opts.popoverContainer });
|
|
1464
1487
|
};
|
|
1465
1488
|
const rowActions2 = (i) => {
|
|
1466
1489
|
const acts = el("div", "exed-effect-acts");
|