@wildwinter/expr-editor 0.4.0 → 0.6.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 CHANGED
@@ -50,6 +50,11 @@ Notable options beyond the basics:
50
50
  outcome-style value cell: `valueEnumValues` offers a target's enum values on
51
51
  the root literal, and `valueField` makes an empty value render one editable
52
52
  "set a value…" pill instead of the condition clause-menu empty state.
53
+ - `flagValue: { target, flags }` — flat compact editor for a
54
+ `set_flags(@target, …)` value whose target is implied by context: renders only
55
+ the flag-delta pills plus an "+ flag" chip (which picks sign + name from
56
+ `flags`), hiding the function name and target arg, and seeds the call on the
57
+ first flag add. Pair with `requireNonEmpty` so the last flag can't be removed.
53
58
  - `setText(on)` — the host's raw-text toggle (`</>`); unparseable input falls
54
59
  back to raw text automatically.
55
60
 
package/dist/index.cjs CHANGED
@@ -541,7 +541,9 @@ function flagEditor(ctx, path, node, anchor) {
541
541
  const call = getNodeAt(ctx.getAst(), callPath);
542
542
  const flagsVar = call?.kind === "call" ? call.args[0] : void 0;
543
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;
544
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);
545
547
  ctx.openPopover(anchor, (close) => {
546
548
  const wrap = el("div", "exed-menu");
547
549
  wrap.append(el("div", "exed-menu-head", ["Flag set?"]));
@@ -566,6 +568,12 @@ function flagEditor(ctx, path, node, anchor) {
566
568
  close();
567
569
  } }));
568
570
  }
571
+ if (canRemove) {
572
+ wrap.append(button("exed-opt danger", "Remove flag", () => {
573
+ ctx.apply(deleteAt(ctx.getAst(), path));
574
+ close();
575
+ }));
576
+ }
569
577
  return wrap;
570
578
  });
571
579
  }
@@ -1075,8 +1083,12 @@ function mountExpressionEditor(host, opts) {
1075
1083
  closePopover();
1076
1084
  host.replaceChildren();
1077
1085
  const v = validateSource(src, opts.schema, opts.dialect);
1078
- if (raw || v.unparseable) {
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) {
1079
1089
  host.append(rawArea());
1090
+ } else if (flagCompact) {
1091
+ host.append(flagValueBody(astIsFlagCall ? v.ast : null));
1080
1092
  } else if (!src.trim() || !v.ast) {
1081
1093
  host.append(opts.valueField ? valueEmptyState() : emptyState());
1082
1094
  } else {
@@ -1126,6 +1138,61 @@ function mountExpressionEditor(host, opts) {
1126
1138
  const ctx = buildCtx(strLit(""));
1127
1139
  return el("div", "exed-flat", [renderNode(strLit(""), [], ctx)]);
1128
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
+ const ctx = buildCtx(call ?? boolLit(true));
1149
+ if (call) {
1150
+ call.args.forEach((arg, i) => {
1151
+ if (arg.kind === "flagdelta") wrap.append(renderNode(arg, ["args", i], ctx));
1152
+ });
1153
+ }
1154
+ wrap.append(addFlagChip(ctx, call));
1155
+ return wrap;
1156
+ }
1157
+ function addFlagChip(ctx, call) {
1158
+ const flags = opts.flagValue?.flags ?? [];
1159
+ const used = call ? call.args.filter((a) => a.kind === "flagdelta").map((a) => a.name) : [];
1160
+ const available = flags.filter((f) => !used.includes(f));
1161
+ return button("exed-add exed-addflag", "+ flag", (e) => {
1162
+ const anchor = e.currentTarget;
1163
+ ctx.openPopover(anchor, (close) => {
1164
+ let sign = "+";
1165
+ const wrap = el("div", "exed-menu");
1166
+ wrap.append(el("div", "exed-menu-head", ["Add flag"]));
1167
+ const signRow = el("div", "exed-field-row");
1168
+ const drawSign = () => {
1169
+ signRow.replaceChildren(
1170
+ button(`exed-opt${sign === "+" ? " sel" : ""}`, "+ set", () => {
1171
+ sign = "+";
1172
+ drawSign();
1173
+ }),
1174
+ button(`exed-opt${sign === "-" ? " sel" : ""}`, "\u2212 unset", () => {
1175
+ sign = "-";
1176
+ drawSign();
1177
+ })
1178
+ );
1179
+ };
1180
+ drawSign();
1181
+ wrap.append(signRow);
1182
+ const commit = (name) => {
1183
+ const delta = flagDelta(sign, name);
1184
+ emit(toSrc(call ? { ...call, args: [...call.args, delta] } : callNode("set_flags", [parseRef(opts.flagValue.target), delta])));
1185
+ close();
1186
+ };
1187
+ if (available.length) {
1188
+ for (const n of available) wrap.append(button("exed-opt", n, () => commit(n)));
1189
+ } else {
1190
+ wrap.append(el("div", "exed-hint", [flags.length === 0 ? "This flags property declares no flag values yet." : "All declared flags are already used."]));
1191
+ }
1192
+ return wrap;
1193
+ });
1194
+ }, "add a flag");
1195
+ }
1129
1196
  function addTermControl(ctx) {
1130
1197
  const boolean = opts.addTerm === "boolean";
1131
1198
  const ops = boolean ? ["and", "or"] : ARITHMETIC_OPS;