@wildwinter/expr-editor 0.3.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 +7 -0
- package/dist/index.cjs +22 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +22 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -224,6 +224,14 @@ interface EditCtx {
|
|
|
224
224
|
* next render — used by insert-then-refine templates so the author lands
|
|
225
225
|
* straight in the first unfilled slot instead of chasing the error ring. */
|
|
226
226
|
requestFocus?(path: AstPath): void;
|
|
227
|
+
/** When true, a delete that would empty the whole expression is blocked (the
|
|
228
|
+
* Delete affordance is withheld). Used by single-value fields that must
|
|
229
|
+
* always hold at least one term; conditions leave it false (empty = always). */
|
|
230
|
+
requireNonEmpty?: boolean;
|
|
231
|
+
/** Enum values offered when editing the single root literal of a value field
|
|
232
|
+
* (an enum-typed outcome target). Lets the string editor show an enum picker
|
|
233
|
+
* even without a comparison peer. No effect on non-root literals. */
|
|
234
|
+
valueEnumValues?: string[];
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
interface ExpressionEditorOptions {
|
|
@@ -258,6 +266,16 @@ interface ExpressionEditorOptions {
|
|
|
258
266
|
* container inside a focus-trapping dialog (Radix, etc.) so opening a pill's
|
|
259
267
|
* popover does not dismiss the surrounding dialog. */
|
|
260
268
|
popoverContainer?: HTMLElement;
|
|
269
|
+
/** Block a delete that would empty the whole expression (single-value fields
|
|
270
|
+
* that must always hold a term). Conditions leave this off (empty = always). */
|
|
271
|
+
requireNonEmpty?: boolean;
|
|
272
|
+
/** Enum values for the single root literal of a value field (an enum-typed
|
|
273
|
+
* target): the literal editor then offers them as a picker. Flat mode only. */
|
|
274
|
+
valueEnumValues?: string[];
|
|
275
|
+
/** Flat single-value field: an empty value renders one editable "set a value…"
|
|
276
|
+
* placeholder pill (a root string literal) rather than the condition
|
|
277
|
+
* clause-menu empty state. Pairs with mode:"flat". */
|
|
278
|
+
valueField?: boolean;
|
|
261
279
|
/** Emitted on every edit (name-form; "" when cleared). */
|
|
262
280
|
onChange: (src: string) => void;
|
|
263
281
|
/** Notified when the author starts (true) / stops (false) editing inside a popover
|
package/dist/index.d.ts
CHANGED
|
@@ -224,6 +224,14 @@ interface EditCtx {
|
|
|
224
224
|
* next render — used by insert-then-refine templates so the author lands
|
|
225
225
|
* straight in the first unfilled slot instead of chasing the error ring. */
|
|
226
226
|
requestFocus?(path: AstPath): void;
|
|
227
|
+
/** When true, a delete that would empty the whole expression is blocked (the
|
|
228
|
+
* Delete affordance is withheld). Used by single-value fields that must
|
|
229
|
+
* always hold at least one term; conditions leave it false (empty = always). */
|
|
230
|
+
requireNonEmpty?: boolean;
|
|
231
|
+
/** Enum values offered when editing the single root literal of a value field
|
|
232
|
+
* (an enum-typed outcome target). Lets the string editor show an enum picker
|
|
233
|
+
* even without a comparison peer. No effect on non-root literals. */
|
|
234
|
+
valueEnumValues?: string[];
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
interface ExpressionEditorOptions {
|
|
@@ -258,6 +266,16 @@ interface ExpressionEditorOptions {
|
|
|
258
266
|
* container inside a focus-trapping dialog (Radix, etc.) so opening a pill's
|
|
259
267
|
* popover does not dismiss the surrounding dialog. */
|
|
260
268
|
popoverContainer?: HTMLElement;
|
|
269
|
+
/** Block a delete that would empty the whole expression (single-value fields
|
|
270
|
+
* that must always hold a term). Conditions leave this off (empty = always). */
|
|
271
|
+
requireNonEmpty?: boolean;
|
|
272
|
+
/** Enum values for the single root literal of a value field (an enum-typed
|
|
273
|
+
* target): the literal editor then offers them as a picker. Flat mode only. */
|
|
274
|
+
valueEnumValues?: string[];
|
|
275
|
+
/** Flat single-value field: an empty value renders one editable "set a value…"
|
|
276
|
+
* placeholder pill (a root string literal) rather than the condition
|
|
277
|
+
* clause-menu empty state. Pairs with mode:"flat". */
|
|
278
|
+
valueField?: boolean;
|
|
261
279
|
/** Emitted on every edit (name-form; "" when cleared). */
|
|
262
280
|
onChange: (src: string) => void;
|
|
263
281
|
/** Notified when the author starts (true) / stops (false) editing inside a popover
|
package/dist/index.js
CHANGED
|
@@ -488,11 +488,14 @@ function numberEditor(ctx, path, value, anchor) {
|
|
|
488
488
|
function stringEditor(ctx, path, node, anchor) {
|
|
489
489
|
const peer = findEnumPeer(ctx.getAst(), path);
|
|
490
490
|
const enumEntry = peer ? lookup(ctx.catalogue, peer.scope, peer.name) : null;
|
|
491
|
+
const rootValueEnums = path.length === 0 && ctx.valueEnumValues?.length ? ctx.valueEnumValues : null;
|
|
492
|
+
const enumValues = enumEntry?.enumValues?.length ? enumEntry.enumValues : rootValueEnums;
|
|
493
|
+
const enumHeadLabel = enumEntry ? `${displayName(enumEntry, ctx.defaultScope)} value` : "Value";
|
|
491
494
|
ctx.openPopover(anchor, (close) => {
|
|
492
|
-
if (
|
|
495
|
+
if (enumValues?.length) {
|
|
493
496
|
const wrap = el("div", "exed-menu");
|
|
494
|
-
wrap.append(el("div", "exed-menu-head", [
|
|
495
|
-
for (const v of
|
|
497
|
+
wrap.append(el("div", "exed-menu-head", [enumHeadLabel]));
|
|
498
|
+
for (const v of enumValues) {
|
|
496
499
|
wrap.append(button(`exed-opt${v === node.value ? " sel" : ""}`, v, () => {
|
|
497
500
|
replace(ctx, path, strLit(v));
|
|
498
501
|
close();
|
|
@@ -564,6 +567,7 @@ function flagEditor(ctx, path, node, anchor) {
|
|
|
564
567
|
return wrap;
|
|
565
568
|
});
|
|
566
569
|
}
|
|
570
|
+
var canDelete = (ctx, path) => !(ctx.requireNonEmpty && path.length === 0);
|
|
567
571
|
function callEditor(ctx, path, node, anchor) {
|
|
568
572
|
ctx.openPopover(anchor, (close) => {
|
|
569
573
|
const wrap = el("div", "exed-menu");
|
|
@@ -575,10 +579,12 @@ function callEditor(ctx, path, node, anchor) {
|
|
|
575
579
|
close();
|
|
576
580
|
}));
|
|
577
581
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
+
if (canDelete(ctx, path)) {
|
|
583
|
+
wrap.append(button("exed-opt danger", "Delete", () => {
|
|
584
|
+
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
585
|
+
close();
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
582
588
|
return wrap;
|
|
583
589
|
});
|
|
584
590
|
}
|
|
@@ -589,10 +595,10 @@ function variableEditor(ctx, path, node, anchor) {
|
|
|
589
595
|
replace(ctx, path, scopedVar(entry.scope, entry.name));
|
|
590
596
|
close();
|
|
591
597
|
},
|
|
592
|
-
footer: button("exed-opt danger", "Delete", () => {
|
|
598
|
+
...canDelete(ctx, path) ? { footer: button("exed-opt danger", "Delete", () => {
|
|
593
599
|
ctx.apply(deleteAt(ctx.getAst(), path));
|
|
594
600
|
close();
|
|
595
|
-
})
|
|
601
|
+
}) } : {}
|
|
596
602
|
}));
|
|
597
603
|
}
|
|
598
604
|
function propertyPicker(ctx, opts) {
|
|
@@ -1057,6 +1063,8 @@ function mountExpressionEditor(host, opts) {
|
|
|
1057
1063
|
requestFocus: (p) => {
|
|
1058
1064
|
pendingFocus = p;
|
|
1059
1065
|
},
|
|
1066
|
+
...opts.requireNonEmpty ? { requireNonEmpty: true } : {},
|
|
1067
|
+
...opts.valueEnumValues ? { valueEnumValues: opts.valueEnumValues } : {},
|
|
1060
1068
|
...opts.pickNode ? { pickNode: opts.pickNode } : {},
|
|
1061
1069
|
...opts.nodeLabel ? { nodeLabel: opts.nodeLabel } : {}
|
|
1062
1070
|
};
|
|
@@ -1068,7 +1076,7 @@ function mountExpressionEditor(host, opts) {
|
|
|
1068
1076
|
if (raw || v.unparseable) {
|
|
1069
1077
|
host.append(rawArea());
|
|
1070
1078
|
} else if (!src.trim() || !v.ast) {
|
|
1071
|
-
host.append(emptyState());
|
|
1079
|
+
host.append(opts.valueField ? valueEmptyState() : emptyState());
|
|
1072
1080
|
} else {
|
|
1073
1081
|
const ctx = buildCtx(v.ast);
|
|
1074
1082
|
const body = el("div", "exed-body");
|
|
@@ -1112,6 +1120,10 @@ function mountExpressionEditor(host, opts) {
|
|
|
1112
1120
|
}));
|
|
1113
1121
|
return wrap;
|
|
1114
1122
|
}
|
|
1123
|
+
function valueEmptyState() {
|
|
1124
|
+
const ctx = buildCtx(strLit(""));
|
|
1125
|
+
return el("div", "exed-flat", [renderNode(strLit(""), [], ctx)]);
|
|
1126
|
+
}
|
|
1115
1127
|
function addTermControl(ctx) {
|
|
1116
1128
|
const boolean = opts.addTerm === "boolean";
|
|
1117
1129
|
const ops = boolean ? ["and", "or"] : ARITHMETIC_OPS;
|