@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/dist/index.d.cts CHANGED
@@ -276,6 +276,16 @@ interface ExpressionEditorOptions {
276
276
  * placeholder pill (a root string literal) rather than the condition
277
277
  * clause-menu empty state. Pairs with mode:"flat". */
278
278
  valueField?: boolean;
279
+ /** Flat compact editor for a `set_flags(@target, +a, -b)` value whose target is
280
+ * implied by context (an outcome row's target column). Renders only the
281
+ * flag-delta pills plus an "+ flag" chip, hiding the function name and target
282
+ * arg. `target` is the name-form ref of the flags property (e.g. "@quests"),
283
+ * `flags` its declared flag names (offered by the "+ flag" picker). Pairs
284
+ * with mode:"flat"; set requireNonEmpty so the last flag can't be removed. */
285
+ flagValue?: {
286
+ target: string;
287
+ flags: string[];
288
+ };
279
289
  /** Emitted on every edit (name-form; "" when cleared). */
280
290
  onChange: (src: string) => void;
281
291
  /** Notified when the author starts (true) / stops (false) editing inside a popover
package/dist/index.d.ts CHANGED
@@ -276,6 +276,16 @@ interface ExpressionEditorOptions {
276
276
  * placeholder pill (a root string literal) rather than the condition
277
277
  * clause-menu empty state. Pairs with mode:"flat". */
278
278
  valueField?: boolean;
279
+ /** Flat compact editor for a `set_flags(@target, +a, -b)` value whose target is
280
+ * implied by context (an outcome row's target column). Renders only the
281
+ * flag-delta pills plus an "+ flag" chip, hiding the function name and target
282
+ * arg. `target` is the name-form ref of the flags property (e.g. "@quests"),
283
+ * `flags` its declared flag names (offered by the "+ flag" picker). Pairs
284
+ * with mode:"flat"; set requireNonEmpty so the last flag can't be removed. */
285
+ flagValue?: {
286
+ target: string;
287
+ flags: string[];
288
+ };
279
289
  /** Emitted on every edit (name-form; "" when cleared). */
280
290
  onChange: (src: string) => void;
281
291
  /** Notified when the author starts (true) / stops (false) editing inside a popover
package/dist/index.js CHANGED
@@ -539,7 +539,9 @@ function flagEditor(ctx, path, node, anchor) {
539
539
  const call = getNodeAt(ctx.getAst(), callPath);
540
540
  const flagsVar = call?.kind === "call" ? call.args[0] : void 0;
541
541
  const entry = flagsVar?.kind === "scopedvar" ? lookup(ctx.catalogue, flagsVar.scope, flagsVar.name) : null;
542
+ const flagCount = call?.kind === "call" ? call.args.filter((a) => a.kind === "flagdelta").length : 0;
542
543
  const used = call?.kind === "call" ? call.args.filter((a, i) => a.kind === "flagdelta" && i !== path[path.length - 1]).map((a) => a.name) : [];
544
+ const canRemove = !(ctx.requireNonEmpty && flagCount <= 1);
543
545
  ctx.openPopover(anchor, (close) => {
544
546
  const wrap = el("div", "exed-menu");
545
547
  wrap.append(el("div", "exed-menu-head", ["Flag set?"]));
@@ -564,6 +566,12 @@ function flagEditor(ctx, path, node, anchor) {
564
566
  close();
565
567
  } }));
566
568
  }
569
+ if (canRemove) {
570
+ wrap.append(button("exed-opt danger", "Remove flag", () => {
571
+ ctx.apply(deleteAt(ctx.getAst(), path));
572
+ close();
573
+ }));
574
+ }
567
575
  return wrap;
568
576
  });
569
577
  }
@@ -1073,8 +1081,12 @@ function mountExpressionEditor(host, opts) {
1073
1081
  closePopover();
1074
1082
  host.replaceChildren();
1075
1083
  const v = validateSource(src, opts.schema, opts.dialect);
1076
- if (raw || v.unparseable) {
1084
+ const flagCompact = !!opts.flagValue && (opts.mode ?? "tree") === "flat";
1085
+ const astIsFlagCall = v.ast?.kind === "call" && (v.ast.name === "set_flags" || v.ast.name === "check_flags");
1086
+ if (raw || v.unparseable || flagCompact && !!v.ast && !astIsFlagCall) {
1077
1087
  host.append(rawArea());
1088
+ } else if (flagCompact) {
1089
+ host.append(flagValueBody(astIsFlagCall ? v.ast : null));
1078
1090
  } else if (!src.trim() || !v.ast) {
1079
1091
  host.append(opts.valueField ? valueEmptyState() : emptyState());
1080
1092
  } else {
@@ -1124,6 +1136,61 @@ function mountExpressionEditor(host, opts) {
1124
1136
  const ctx = buildCtx(strLit(""));
1125
1137
  return el("div", "exed-flat", [renderNode(strLit(""), [], ctx)]);
1126
1138
  }
1139
+ function parseRef(ref) {
1140
+ const bare = ref.replace(/^@/, "");
1141
+ const dot = bare.indexOf(".");
1142
+ return dot >= 0 ? scopedVar(bare.slice(0, dot), bare.slice(dot + 1)) : scopedVar(defaultScope, bare);
1143
+ }
1144
+ function flagValueBody(call) {
1145
+ const wrap = el("div", "exed-flat exed-flagvalue");
1146
+ const ctx = buildCtx(call ?? boolLit(true));
1147
+ if (call) {
1148
+ call.args.forEach((arg, i) => {
1149
+ if (arg.kind === "flagdelta") wrap.append(renderNode(arg, ["args", i], ctx));
1150
+ });
1151
+ }
1152
+ wrap.append(addFlagChip(ctx, call));
1153
+ return wrap;
1154
+ }
1155
+ function addFlagChip(ctx, call) {
1156
+ const flags = opts.flagValue?.flags ?? [];
1157
+ const used = call ? call.args.filter((a) => a.kind === "flagdelta").map((a) => a.name) : [];
1158
+ const available = flags.filter((f) => !used.includes(f));
1159
+ return button("exed-add exed-addflag", "+ flag", (e) => {
1160
+ const anchor = e.currentTarget;
1161
+ ctx.openPopover(anchor, (close) => {
1162
+ let sign = "+";
1163
+ const wrap = el("div", "exed-menu");
1164
+ wrap.append(el("div", "exed-menu-head", ["Add flag"]));
1165
+ const signRow = el("div", "exed-field-row");
1166
+ const drawSign = () => {
1167
+ signRow.replaceChildren(
1168
+ button(`exed-opt${sign === "+" ? " sel" : ""}`, "+ set", () => {
1169
+ sign = "+";
1170
+ drawSign();
1171
+ }),
1172
+ button(`exed-opt${sign === "-" ? " sel" : ""}`, "\u2212 unset", () => {
1173
+ sign = "-";
1174
+ drawSign();
1175
+ })
1176
+ );
1177
+ };
1178
+ drawSign();
1179
+ wrap.append(signRow);
1180
+ const commit = (name) => {
1181
+ const delta = flagDelta(sign, name);
1182
+ emit(toSrc(call ? { ...call, args: [...call.args, delta] } : callNode("set_flags", [parseRef(opts.flagValue.target), delta])));
1183
+ close();
1184
+ };
1185
+ if (available.length) {
1186
+ for (const n of available) wrap.append(button("exed-opt", n, () => commit(n)));
1187
+ } else {
1188
+ wrap.append(el("div", "exed-hint", [flags.length === 0 ? "This flags property declares no flag values yet." : "All declared flags are already used."]));
1189
+ }
1190
+ return wrap;
1191
+ });
1192
+ }, "add a flag");
1193
+ }
1127
1194
  function addTermControl(ctx) {
1128
1195
  const boolean = opts.addTerm === "boolean";
1129
1196
  const ops = boolean ? ["and", "or"] : ARITHMETIC_OPS;