@qoretechnologies/reqraft 0.10.2 → 0.10.5

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.
Files changed (74) hide show
  1. package/.claude/CLAUDE.md +5 -0
  2. package/design/COMPACT_ENGINE_REDESIGN.md +156 -0
  3. package/design/FORM_ENGINE_COMPACT_UX_PLAN.md +353 -0
  4. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  5. package/dist/components/form/engine/CompactRow.js +158 -101
  6. package/dist/components/form/engine/CompactRow.js.map +1 -1
  7. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  8. package/dist/components/form/engine/CompactToolbar.js +122 -105
  9. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  10. package/dist/components/form/engine/FormEngine.d.ts +9 -1
  11. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  12. package/dist/components/form/engine/FormEngine.js +272 -82
  13. package/dist/components/form/engine/FormEngine.js.map +1 -1
  14. package/dist/components/form/engine/compactRowStyles.d.ts +6 -3
  15. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  16. package/dist/components/form/engine/compactRowStyles.js +76 -49
  17. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  18. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  19. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  20. package/dist/components/form/engine/compactToolbarContext.js.map +1 -1
  21. package/dist/components/form/engine/readFirst.d.ts +19 -0
  22. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  23. package/dist/components/form/engine/readFirst.js +22 -1
  24. package/dist/components/form/engine/readFirst.js.map +1 -1
  25. package/dist/components/form/engine/variants/VariantCalmTable.d.ts +6 -0
  26. package/dist/components/form/engine/variants/VariantCalmTable.d.ts.map +1 -0
  27. package/dist/components/form/engine/variants/VariantCalmTable.js +94 -0
  28. package/dist/components/form/engine/variants/VariantCalmTable.js.map +1 -0
  29. package/dist/components/form/engine/variants/VariantCards.d.ts +6 -0
  30. package/dist/components/form/engine/variants/VariantCards.d.ts.map +1 -0
  31. package/dist/components/form/engine/variants/VariantCards.js +80 -0
  32. package/dist/components/form/engine/variants/VariantCards.js.map +1 -0
  33. package/dist/components/form/engine/variants/VariantFocus.d.ts +7 -0
  34. package/dist/components/form/engine/variants/VariantFocus.d.ts.map +1 -0
  35. package/dist/components/form/engine/variants/VariantFocus.js +138 -0
  36. package/dist/components/form/engine/variants/VariantFocus.js.map +1 -0
  37. package/dist/components/form/engine/variants/VariantMinimal.d.ts +6 -0
  38. package/dist/components/form/engine/variants/VariantMinimal.d.ts.map +1 -0
  39. package/dist/components/form/engine/variants/VariantMinimal.js +73 -0
  40. package/dist/components/form/engine/variants/VariantMinimal.js.map +1 -0
  41. package/dist/components/form/engine/variants/focusDemo.d.ts +13 -0
  42. package/dist/components/form/engine/variants/focusDemo.d.ts.map +1 -0
  43. package/dist/components/form/engine/variants/focusDemo.js +139 -0
  44. package/dist/components/form/engine/variants/focusDemo.js.map +1 -0
  45. package/dist/components/form/engine/variants/variantModel.d.ts +70 -0
  46. package/dist/components/form/engine/variants/variantModel.d.ts.map +1 -0
  47. package/dist/components/form/engine/variants/variantModel.js +133 -0
  48. package/dist/components/form/engine/variants/variantModel.js.map +1 -0
  49. package/dist/components/form/engine/variants/variantParts.d.ts +79 -0
  50. package/dist/components/form/engine/variants/variantParts.d.ts.map +1 -0
  51. package/dist/components/form/engine/variants/variantParts.js +191 -0
  52. package/dist/components/form/engine/variants/variantParts.js.map +1 -0
  53. package/dist/components/form/fields/auto/AutoFormField.d.ts +3 -0
  54. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  55. package/dist/components/form/fields/auto/AutoFormField.js +5 -2
  56. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/components/form/engine/CompactRow.tsx +273 -258
  59. package/src/components/form/engine/CompactToolbar.tsx +112 -85
  60. package/src/components/form/engine/FormEngine.stories.tsx +239 -115
  61. package/src/components/form/engine/FormEngine.tsx +332 -83
  62. package/src/components/form/engine/compactRowStyles.ts +221 -144
  63. package/src/components/form/engine/compactToolbarContext.ts +1 -0
  64. package/src/components/form/engine/readFirst.ts +35 -0
  65. package/src/components/form/engine/variants/FormEngineVariants.stories.tsx +119 -0
  66. package/src/components/form/engine/variants/VariantCalmTable.tsx +242 -0
  67. package/src/components/form/engine/variants/VariantCards.tsx +212 -0
  68. package/src/components/form/engine/variants/VariantFocus.tsx +382 -0
  69. package/src/components/form/engine/variants/VariantMinimal.tsx +170 -0
  70. package/src/components/form/engine/variants/focusDemo.ts +145 -0
  71. package/src/components/form/engine/variants/variantModel.ts +216 -0
  72. package/src/components/form/engine/variants/variantParts.tsx +313 -0
  73. package/src/components/form/fields/auto/AutoFormField.stories.tsx +9 -2
  74. package/src/components/form/fields/auto/AutoFormField.tsx +8 -0
@@ -297,40 +297,44 @@ var FormEngine = function (_a) {
297
297
  var _b, _c, _d, _f, _g;
298
298
  var name = _a.name, uniqueName = _a.uniqueName, value = _a.value, onChange = _a.onChange, onSingleOptionsChange = _a.onSingleOptionsChange, onDependableOptionChange = _a.onDependableOptionChange, placeholder = _a.placeholder, noValueString = _a.noValueString, // eslint-disable-line @typescript-eslint/no-unused-vars
299
299
  isValid = _a.isValid, // eslint-disable-line @typescript-eslint/no-unused-vars
300
- url = _a.url, customUrl = _a.customUrl, operatorsUrl = _a.operatorsUrl, onOptionsLoaded = _a.onOptionsLoaded, recordRequiresSearchOptions = _a.recordRequiresSearchOptions, readOnly = _a.readOnly, _h = _a.allowTemplates, allowTemplates = _h === void 0 ? true : _h, interfaceContext = _a.interfaceContext, templateFieldProps = _a.templateFieldProps, _j = _a.showTypeToggle, showTypeToggle = _j === void 0 ? true : _j, compact = _a.compact, _k = _a.compactFlush, compactFlush = _k === void 0 ? false : _k, _l = _a.commitMode, commitMode = _l === void 0 ? 'immediate' : _l, _m = _a.expandMode, expandMode = _m === void 0 ? 'single' : _m, onCommit = _a.onCommit, operatorsProp = _a.operators, groups = _a.groups, optionsLoader = _a.optionsLoader, onValidityChange = _a.onValidityChange, optionActions = _a.optionActions, componentOverrides = _a.componentOverrides, rest = __rest(_a, ["name", "uniqueName", "value", "onChange", "onSingleOptionsChange", "onDependableOptionChange", "placeholder", "noValueString", "isValid", "url", "customUrl", "operatorsUrl", "onOptionsLoaded", "recordRequiresSearchOptions", "readOnly", "allowTemplates", "interfaceContext", "templateFieldProps", "showTypeToggle", "compact", "compactFlush", "commitMode", "expandMode", "onCommit", "operators", "groups", "optionsLoader", "onValidityChange", "optionActions", "componentOverrides"]);
301
- var _o = (0, react_2.useState)((rest === null || rest === void 0 ? void 0 : rest.options) || undefined), options = _o[0], setOptions = _o[1];
300
+ url = _a.url, customUrl = _a.customUrl, operatorsUrl = _a.operatorsUrl, onOptionsLoaded = _a.onOptionsLoaded, recordRequiresSearchOptions = _a.recordRequiresSearchOptions, readOnly = _a.readOnly, _h = _a.allowTemplates, allowTemplates = _h === void 0 ? true : _h, interfaceContext = _a.interfaceContext, templateFieldProps = _a.templateFieldProps, _j = _a.showTypeToggle, showTypeToggle = _j === void 0 ? true : _j, compact = _a.compact, _k = _a.compactFlush, compactFlush = _k === void 0 ? false : _k, _l = _a.compactNested, compactNested = _l === void 0 ? false : _l, _m = _a.commitMode, commitMode = _m === void 0 ? 'immediate' : _m, _o = _a.expandMode, expandMode = _o === void 0 ? 'single' : _o, onCommit = _a.onCommit, operatorsProp = _a.operators, groups = _a.groups, optionsLoader = _a.optionsLoader, onValidityChange = _a.onValidityChange, optionActions = _a.optionActions, componentOverrides = _a.componentOverrides, rest = __rest(_a, ["name", "uniqueName", "value", "onChange", "onSingleOptionsChange", "onDependableOptionChange", "placeholder", "noValueString", "isValid", "url", "customUrl", "operatorsUrl", "onOptionsLoaded", "recordRequiresSearchOptions", "readOnly", "allowTemplates", "interfaceContext", "templateFieldProps", "showTypeToggle", "compact", "compactFlush", "compactNested", "commitMode", "expandMode", "onCommit", "operators", "groups", "optionsLoader", "onValidityChange", "optionActions", "componentOverrides"]);
301
+ var _p = (0, react_2.useState)((rest === null || rest === void 0 ? void 0 : rest.options) || undefined), options = _p[0], setOptions = _p[1];
302
302
  // optionsLoader lifecycle: loading feeds the skeleton gate, error the banner.
303
- var _p = (0, react_2.useState)(!!optionsLoader && !(rest === null || rest === void 0 ? void 0 : rest.options)), optionsLoading = _p[0], setOptionsLoading = _p[1];
304
- var _q = (0, react_2.useState)(), optionsError = _q[0], setOptionsError = _q[1];
303
+ var _q = (0, react_2.useState)(!!optionsLoader && !(rest === null || rest === void 0 ? void 0 : rest.options)), optionsLoading = _q[0], setOptionsLoading = _q[1];
304
+ var _r = (0, react_2.useState)(), optionsError = _r[0], setOptionsError = _r[1];
305
305
  // Operators: prop-provided (compact) or fetched via operatorsUrl (dpql,
306
306
  // ported from IDE Options) — the fetch overrides the seeded prop value.
307
- var _r = (0, react_2.useState)(operatorsProp), operators = _r[0], setOperators = _r[1];
307
+ var _s = (0, react_2.useState)(operatorsProp), operators = _s[0], setOperators = _s[1];
308
308
  // Remote-fetch loading (ported from IDE Options); only relevant when one of
309
309
  // the fetch urls is set — schema-as-props consumers never see the skeleton.
310
- var _s = (0, react_2.useState)(!!(url || customUrl || operatorsUrl)), loading = _s[0], setLoading = _s[1];
310
+ var _t = (0, react_2.useState)(!!(url || customUrl || operatorsUrl)), loading = _t[0], setLoading = _t[1];
311
311
  var confirmAction = (0, reqore_1.useReqoreProperty)('confirmAction');
312
312
  var theme = (0, reqore_1.useReqoreTheme)();
313
- var _t = (0, react_2.useState)(), focusedEditing = _t[0], setFocusedEditing = _t[1];
314
- var _u = (0, react_2.useState)(false), showFieldTypes = _u[0], setShowFieldTypes = _u[1];
313
+ var _u = (0, react_2.useState)(), focusedEditing = _u[0], setFocusedEditing = _u[1];
314
+ var _v = (0, react_2.useState)(false), showFieldTypes = _v[0], setShowFieldTypes = _v[1];
315
315
  // Global toggle (toolbar ⓘ): reveal the short-description info panel on every
316
316
  // field that has a short_desc. Per-row ⓘ overrides still win over it.
317
317
  // Global field-info visibility, tri-state: `undefined` = default (critical
318
318
  // messages auto-open, the rest closed); `true` = show all; `false` = hide all
319
319
  // (even message fields). The toolbar ⓘ drives it.
320
- var _v = (0, react_2.useState)(undefined), showAllDescriptions = _v[0], setShowAllDescriptions = _v[1];
321
- var _w = (0, react_2.useState)(), showHelpForOption = _w[0], setShowHelpForOption = _w[1];
322
- var _x = (0, react_2.useState)(false), showInvalidOptionsOnly = _x[0], setShowInvalidOptionsOnly = _x[1];
320
+ var _w = (0, react_2.useState)(undefined), showAllDescriptions = _w[0], setShowAllDescriptions = _w[1];
321
+ var _x = (0, react_2.useState)(), showHelpForOption = _x[0], setShowHelpForOption = _x[1];
322
+ var _y = (0, react_2.useState)(false), showInvalidOptionsOnly = _y[0], setShowInvalidOptionsOnly = _y[1];
323
323
  // Which options are expanded into their editor (several can be open at once).
324
- var _y = (0, react_2.useState)([]), expandedOptions = _y[0], setExpandedOptions = _y[1];
324
+ var _z = (0, react_2.useState)([]), expandedOptions = _z[0], setExpandedOptions = _z[1];
325
+ // Remembers each row's last settled status box, so an actively-edited field
326
+ // stays put when its status flips (e.g. becomes valid) instead of jumping to
327
+ // another box mid-edit and stealing focus. Keyed by option name.
328
+ var settledBucket = (0, react_2.useRef)({});
325
329
  // Measured form width (not viewport — the form lives in drawers/panels of
326
330
  // arbitrary width) drives the stacked narrow layout.
327
- var _z = (0, react_use_1.useMeasure)(), compactWrapRef = _z[0], compactWrapWidth = _z[1].width;
331
+ var _0 = (0, react_use_1.useMeasure)(), compactWrapRef = _0[0], compactWrapWidth = _0[1].width;
328
332
  // Own handle on the scroll wrap (useMeasure's ref is a callback, no `.current`)
329
333
  // so the label-column measurement can publish its CSS var on the element. It's
330
334
  // STATE, not a ref: the wrap mounts only after the loading-skeleton gate
331
335
  // resolves, so the measurement effect must re-run when the node appears — a
332
336
  // ref wouldn't retrigger it.
333
- var _0 = (0, react_2.useState)(null), compactWrapNode = _0[0], setCompactWrapNode = _0[1];
337
+ var _1 = (0, react_2.useState)(null), compactWrapNode = _1[0], setCompactWrapNode = _1[1];
334
338
  var setCompactWrap = (0, react_2.useCallback)(function (node) {
335
339
  compactWrapRef(node);
336
340
  setCompactWrapNode(function (prev) { return (prev === node ? prev : node); });
@@ -367,15 +371,21 @@ var FormEngine = function (_a) {
367
371
  var readRowHeights = (0, react_2.useRef)({});
368
372
  // Required-groups linkage: hovering a group chip highlights every member row;
369
373
  // clicking a sibling in the chip's popover scrolls to it and flashes it.
370
- var _1 = (0, react_2.useState)([]), highlightedOptions = _1[0], setHighlightedOptions = _1[1];
371
- var _2 = (0, react_2.useState)([]), flashedOptions = _2[0], setFlashedOptions = _2[1];
374
+ var _2 = (0, react_2.useState)([]), highlightedOptions = _2[0], setHighlightedOptions = _2[1];
375
+ var _3 = (0, react_2.useState)([]), flashedOptions = _3[0], setFlashedOptions = _3[1];
372
376
  var flashTimeout = (0, react_2.useRef)();
373
377
  var flashOptions = (0, react_2.useCallback)(function (optionNames, scrollToFirst) {
374
- var _a;
375
378
  if (scrollToFirst === void 0) { scrollToFirst = false; }
376
379
  if (scrollToFirst && optionNames[0]) {
377
- (_a = document
378
- .querySelector(".readfirst-row[data-field=\"".concat(optionNames[0], "\"]"))) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'center', behavior: 'smooth' });
380
+ // Defer to the next frame: when this fires for a field that just changed
381
+ // panels, its row has only just re-mounted in the new box scrolling in the
382
+ // same tick targets the stale (pre-move) layout, so the page doesn't budge.
383
+ // A rAF lets the new position settle first.
384
+ requestAnimationFrame(function () {
385
+ var _a;
386
+ (_a = document
387
+ .querySelector(".readfirst-row[data-field=\"".concat(optionNames[0], "\"]"))) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'center', behavior: 'smooth' });
388
+ });
379
389
  }
380
390
  setFlashedOptions(optionNames);
381
391
  clearTimeout(flashTimeout.current);
@@ -383,18 +393,37 @@ var FormEngine = function (_a) {
383
393
  }, []);
384
394
  var flashOption = (0, react_2.useCallback)(function (optionName) { return flashOptions([optionName], true); }, [flashOptions]);
385
395
  (0, react_2.useEffect)(function () { return function () { return clearTimeout(flashTimeout.current); }; }, []);
396
+ // Follow a field across panels: when its status bucket changes — e.g. you fill
397
+ // an optional field and it jumps to Set / Needs attention — scroll to its new
398
+ // row and flash it so it's easy to keep track of. `settledBucket` holds each
399
+ // field's current panel (frozen while the field is being edited, it re-buckets
400
+ // on collapse), so diffing it after every render catches the move the instant it
401
+ // lands in the new panel. Runs every render; the diff is cheap and only fires a
402
+ // scroll on an ACTUAL move of a non-expanded field.
403
+ var prevSettledBucket = (0, react_2.useRef)({});
404
+ (0, react_2.useEffect)(function () {
405
+ if (!compact)
406
+ return;
407
+ var cur = settledBucket.current;
408
+ var prev = prevSettledBucket.current;
409
+ var moved = Object.keys(cur).find(function (name) { return prev[name] && prev[name] !== cur[name] && !expandedOptions.includes(name); });
410
+ prevSettledBucket.current = __assign({}, cur);
411
+ if (moved) {
412
+ flashOptions([moved], true);
413
+ }
414
+ });
386
415
  var compactNarrow = !!compactWrapWidth && compactWrapWidth < 480;
387
416
  // Info panels auto-open on Tier-1 content; the per-row user override sticks.
388
- var _3 = (0, react_2.useState)({}), infoPanelOverrides = _3[0], setInfoPanelOverrides = _3[1];
417
+ var _4 = (0, react_2.useState)({}), infoPanelOverrides = _4[0], setInfoPanelOverrides = _4[1];
389
418
  // Toolbar filters affect the listed rows only — the meter reflects the full set.
390
- var _4 = (0, react_2.useState)(false), requiredOnly = _4[0], setRequiredOnly = _4[1];
391
- var _5 = (0, react_2.useState)(''), compactQuery = _5[0], setCompactQuery = _5[1];
419
+ var _5 = (0, react_2.useState)(false), requiredOnly = _5[0], setRequiredOnly = _5[1];
420
+ var _6 = (0, react_2.useState)(''), compactQuery = _6[0], setCompactQuery = _6[1];
392
421
  // Compact field sort (Fields menu → "Sort by"); 'schema' = declared order.
393
- var _6 = (0, react_2.useState)('schema'), compactSort = _6[0], setCompactSort = _6[1];
394
- var _7 = (0, react_2.useState)(function () { return ({
422
+ var _7 = (0, react_2.useState)('schema'), compactSort = _7[0], setCompactSort = _7[1];
423
+ var _8 = (0, react_2.useState)(function () { return ({
395
424
  fields: (0, exports.fixOptions)(value, options || {}),
396
425
  meta: undefined,
397
- }); }), localValue = _7[0], setLocalValue = _7[1];
426
+ }); }), localValue = _8[0], setLocalValue = _8[1];
398
427
  var originalValue = (0, react_2.useRef)();
399
428
  // Track the last value we emitted via onChange so we can skip re-applying fixOptions
400
429
  // when the parent echoes it back as the new value prop (controlled component loop prevention)
@@ -403,7 +432,7 @@ var FormEngine = function (_a) {
403
432
  originalValue.current = localValue.fields;
404
433
  }
405
434
  var unavailableOptionsCount = (0, react_2.useRef)(0);
406
- var _8 = (0, useQorusTypes_1.useQorusTypes)(), compactValue = _8.compactValue, typesLoading = _8.loading;
435
+ var _9 = (0, useQorusTypes_1.useQorusTypes)(), compactValue = _9.compactValue, typesLoading = _9.loading;
407
436
  var templates = (0, useTemplates_1.useTemplates)(allowTemplates, rest.stringTemplates, interfaceContext);
408
437
  (0, react_2.useEffect)(function () {
409
438
  if ((0, lodash_1.isEqual)(localValue.fields, value)) {
@@ -726,6 +755,10 @@ var FormEngine = function (_a) {
726
755
  meta: undefined,
727
756
  };
728
757
  });
758
+ // Collapse it too: a removed field drops back to the (collapsed) Optional box
759
+ // as a quiet addable row — if it was being edited, that editor must close
760
+ // rather than linger as an open editor for a field that's no longer added.
761
+ setExpandedOptions(function (prev) { return prev.filter(function (name) { return name !== optionName; }); });
729
762
  }, []);
730
763
  var handleAddOptionalFieldChange = (0, react_2.useCallback)(function (_name, optionName) {
731
764
  var _a, _b, _c;
@@ -889,6 +922,67 @@ var FormEngine = function (_a) {
889
922
  return __assign(__assign({}, newValue), (_a = {}, _a[optionName] = option, _a));
890
923
  }, {});
891
924
  }, [showInvalidOptionsOnly, JSON.stringify(availableOptions)]);
925
+ // Read-first STATUS / BOX for one option — lifted to component scope so the
926
+ // status boxes (renderCompact) and the header's "needs attention" count share
927
+ // exactly one definition. One-of group members travel together (bucket by the
928
+ // group's satisfaction); everything else by its own status.
929
+ var schemaMsgIntent = (0, react_2.useCallback)(function (name) {
930
+ var _a;
931
+ var msgs = (((_a = options === null || options === void 0 ? void 0 : options[name]) === null || _a === void 0 ? void 0 : _a.messages) || []);
932
+ if (msgs.some(function (m) { return m.intent === 'danger'; }))
933
+ return 'danger';
934
+ if (msgs.some(function (m) { return m.intent === 'warning'; }))
935
+ return 'warning';
936
+ return undefined;
937
+ }, [JSON.stringify(options)]);
938
+ var getOptionStatus = (0, react_2.useCallback)(function (name, hidden) {
939
+ var _a;
940
+ if (hidden === void 0) { hidden = false; }
941
+ if (hidden)
942
+ return 'optional';
943
+ var schema = options === null || options === void 0 ? void 0 : options[name];
944
+ var type = ((schema === null || schema === void 0 ? void 0 : schema.ui_type) || (schema === null || schema === void 0 ? void 0 : schema.type));
945
+ var value = (_a = availableOptions === null || availableOptions === void 0 ? void 0 : availableOptions[name]) === null || _a === void 0 ? void 0 : _a.value;
946
+ var empty = (0, readFirst_1.isOptionValueEmpty)(value);
947
+ var reqGroups = (schema === null || schema === void 0 ? void 0 : schema.required_groups) || [];
948
+ var required = !!((schema === null || schema === void 0 ? void 0 : schema.required) || reqGroups.length);
949
+ var covered = empty &&
950
+ reqGroups.some(function (g) {
951
+ var by = requiredGroupsInfo.satisfiedBy[g];
952
+ return !!by && by !== name;
953
+ });
954
+ var msgIntent = schemaMsgIntent(name);
955
+ var invalid = (!empty && !isOptionValid(name, type, value)) || msgIntent === 'danger';
956
+ return (0, readFirst_1.getReadFirstStatus)({
957
+ empty: empty,
958
+ required: required,
959
+ covered: covered,
960
+ invalid: invalid,
961
+ warned: msgIntent === 'warning',
962
+ });
963
+ }, [
964
+ JSON.stringify(options),
965
+ JSON.stringify(availableOptions),
966
+ isOptionValid,
967
+ requiredGroupsInfo,
968
+ schemaMsgIntent,
969
+ ]);
970
+ var getOptionBucket = (0, react_2.useCallback)(function (name, hidden) {
971
+ var _a;
972
+ if (hidden === void 0) { hidden = false; }
973
+ if (!hidden) {
974
+ var reqGroups = ((_a = options === null || options === void 0 ? void 0 : options[name]) === null || _a === void 0 ? void 0 : _a.required_groups) || [];
975
+ if (reqGroups.length) {
976
+ return reqGroups.some(function (g) { return !requiredGroupsInfo.satisfiedBy[g]; }) ? 'attention' : 'set';
977
+ }
978
+ }
979
+ return (0, readFirst_1.getReadFirstBucket)(getOptionStatus(name, hidden));
980
+ }, [JSON.stringify(options), requiredGroupsInfo, getOptionStatus]);
981
+ // How many fields are in the "Needs attention" box — drives the header link.
982
+ var readFirstAttentionCount = (0, react_2.useMemo)(function () {
983
+ return Object.keys(availableOptions || {}).filter(function (name) { return getOptionBucket(name) === 'attention'; })
984
+ .length;
985
+ }, [JSON.stringify(availableOptions), getOptionBucket]);
892
986
  var getIntent = (0, react_2.useCallback)(
893
987
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
894
988
  function (optName, type, optValue, _op) {
@@ -1011,13 +1105,27 @@ var FormEngine = function (_a) {
1011
1105
  // The info panel below the row keeps showing schema messages while editing —
1012
1106
  // rendering them in the editor too would balloon a one-line edit.
1013
1107
  suppressSchemaMessages) {
1014
- var _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1108
+ var _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p;
1015
1109
  var type = _a.type, other = __rest(_a, ["type"]);
1016
1110
  var operatorParts = (0, exports.fixOperatorValue)(other.op);
1017
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(suppressSchemaMessages ? [] : ((_b = options === null || options === void 0 ? void 0 : options[optionName]) === null || _b === void 0 ? void 0 : _b.messages) || []).map(function (_a, index) {
1018
- var intent = _a.intent, title = _a.title, content = _a.content;
1019
- return ((0, jsx_runtime_1.jsx)(reqore_1.ReqoreMessage, { intent: intent, title: title, opaque: false, size: 'small', margin: 'bottom', children: content }, title || index));
1020
- }), operators && (0, size_1.default)(operators) ?
1111
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(function () {
1112
+ var _a;
1113
+ var schemaMsgs = (suppressSchemaMessages ? [] : ((_a = options === null || options === void 0 ? void 0 : options[optionName]) === null || _a === void 0 ? void 0 : _a.messages) || []);
1114
+ if (!schemaMsgs.length)
1115
+ return null;
1116
+ var items = schemaMsgs.map(function (_a, index) {
1117
+ var intent = _a.intent, title = _a.title, content = _a.content;
1118
+ return ((0, jsx_runtime_1.jsx)(reqore_1.ReqoreMessage, { intent: intent, title: title, opaque: false, size: 'small',
1119
+ // Compact: flat (no border) to match the read-row info panels;
1120
+ // classic forms keep the bordered, bottom-margined message.
1121
+ flat: compact || undefined, margin: compact ? undefined : 'bottom', children: content }, title || index));
1122
+ });
1123
+ // Compact: stack them in a 4px-gap panel so a field's messages look
1124
+ // identical whether the row is collapsed (read panel) or expanded.
1125
+ return compact ?
1126
+ (0, jsx_runtime_1.jsx)("div", { className: 'options-readfirst-info-panel', style: { display: 'flex', flexFlow: 'column', gap: 4, marginBottom: 8 }, children: items })
1127
+ : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: items });
1128
+ })(), operators && (0, size_1.default)(operators) ?
1021
1129
  (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreControlGroup, { fill: true, wrap: true, className: 'operators', children: operatorParts.map(function (operator, index) {
1022
1130
  var _a, _b;
1023
1131
  return ((0, jsx_runtime_1.jsxs)(react_2.default.Fragment, { children: [(0, jsx_runtime_1.jsx)(Select_1.SelectFormField, { items: (0, map_1.default)(operators, function (op) { return (__assign(__assign({}, op), { value: op.name })); }), disabled: readOnly, value: operator && "".concat((_a = operators === null || operators === void 0 ? void 0 : operators[operator]) === null || _a === void 0 ? void 0 : _a.name), onChange: function (val) {
@@ -1033,18 +1141,21 @@ var FormEngine = function (_a) {
1033
1141
  : null] }, index));
1034
1142
  }) }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreVerticalSpacer, { height: 5 })] })
1035
1143
  : null, (0, react_1.createElement)(TemplateField_1.TemplateField, __assign({ fluid: true }, options === null || options === void 0 ? void 0 : options[optionName], {
1144
+ // Propagate compact so an arg_schema field renders a COMPACT sub-form
1145
+ // (consistent with the parent) rather than the classic FormEngine.
1146
+ compact: compact,
1036
1147
  // SEAM: forwarded through TemplateField's rest-spread to AutoFormField,
1037
1148
  // which renders consumer-injected editors by field type/ui_type.
1038
- componentOverrides: componentOverrides, allowTemplates: !!(allowTemplates && ((_c = options === null || options === void 0 ? void 0 : options[optionName]) === null || _c === void 0 ? void 0 : _c.supports_templates)), allowFunctions: !!((_d = options === null || options === void 0 ? void 0 : options[optionName]) === null || _d === void 0 ? void 0 : _d.supports_expressions),
1149
+ componentOverrides: componentOverrides, allowTemplates: !!(allowTemplates && ((_b = options === null || options === void 0 ? void 0 : options[optionName]) === null || _b === void 0 ? void 0 : _b.supports_templates)), allowFunctions: !!((_c = options === null || options === void 0 ? void 0 : options[optionName]) === null || _c === void 0 ? void 0 : _c.supports_expressions),
1039
1150
  // reqraft: form-level expression fields get the Visual/Text shell
1040
1151
  // (DPQL text mode); opt out per-form via `templateFieldProps`.
1041
- allowTextExpressions: true, allowCustomValues: ((_f = options === null || options === void 0 ? void 0 : options[optionName]) === null || _f === void 0 ? void 0 : _f.supports_custom_values) !== false && type !== 'any', templates: templates.value }, (0, exports.getTypeAndCanBeNull)(type, (_g = options === null || options === void 0 ? void 0 : options[optionName]) === null || _g === void 0 ? void 0 : _g.allowed_values, other.op), { ui_type: type, name: optionName, uniqueName: "".concat(uniqueName ? "".concat(uniqueName, ".") : "".concat(name ? "".concat(name, ".") : '')).concat(optionName), onChange:
1152
+ allowTextExpressions: true, allowCustomValues: ((_d = options === null || options === void 0 ? void 0 : options[optionName]) === null || _d === void 0 ? void 0 : _d.supports_custom_values) !== false && type !== 'any', templates: templates.value }, (0, exports.getTypeAndCanBeNull)(type, (_f = options === null || options === void 0 ? void 0 : options[optionName]) === null || _f === void 0 ? void 0 : _f.allowed_values, other.op), { ui_type: type, name: optionName, uniqueName: "".concat(uniqueName ? "".concat(uniqueName, ".") : "".concat(name ? "".concat(name, ".") : '')).concat(optionName), onChange:
1042
1153
  // Identity-stable on purpose: the typed fields debounce on
1043
1154
  // `[localValue, onChange]` — an inline lambda resets the pending emit
1044
1155
  // every render and the typed value can starve.
1045
- handleValueChange, key: optionName, arg_schema: (_h = options === null || options === void 0 ? void 0 : options[optionName]) === null || _h === void 0 ? void 0 : _h.arg_schema, noSoft: !!(rest === null || rest === void 0 ? void 0 : rest.options), value: other.value, isFunction: other.is_expression, isDefaultFunction: ((_j = options === null || options === void 0 ? void 0 : options[optionName]) === null || _j === void 0 ? void 0 : _j.default_view) === 'expression', sensitive: (_k = options === null || options === void 0 ? void 0 : options[optionName]) === null || _k === void 0 ? void 0 : _k.sensitive, default_value: (0, common_1.getDefaultValue)(options === null || options === void 0 ? void 0 : options[optionName]), isDefaultTemplate: ((_l = options === null || options === void 0 ? void 0 : options[optionName]) === null || _l === void 0 ? void 0 : _l.default_view) === 'template', allowed_values: (_m = options === null || options === void 0 ? void 0 : options[optionName]) === null || _m === void 0 ? void 0 : _m.allowed_values, disabled: ((_o = options === null || options === void 0 ? void 0 : options[optionName]) === null || _o === void 0 ? void 0 : _o.disabled) ||
1156
+ handleValueChange, key: optionName, arg_schema: (_g = options === null || options === void 0 ? void 0 : options[optionName]) === null || _g === void 0 ? void 0 : _g.arg_schema, noSoft: !!(rest === null || rest === void 0 ? void 0 : rest.options), value: other.value, isFunction: other.is_expression, isDefaultFunction: ((_h = options === null || options === void 0 ? void 0 : options[optionName]) === null || _h === void 0 ? void 0 : _h.default_view) === 'expression', sensitive: (_j = options === null || options === void 0 ? void 0 : options[optionName]) === null || _j === void 0 ? void 0 : _j.sensitive, default_value: (0, common_1.getDefaultValue)(options === null || options === void 0 ? void 0 : options[optionName]), isDefaultTemplate: ((_k = options === null || options === void 0 ? void 0 : options[optionName]) === null || _k === void 0 ? void 0 : _k.default_view) === 'template', allowed_values: (_l = options === null || options === void 0 ? void 0 : options[optionName]) === null || _l === void 0 ? void 0 : _l.allowed_values, disabled: ((_m = options === null || options === void 0 ? void 0 : options[optionName]) === null || _m === void 0 ? void 0 : _m.disabled) ||
1046
1157
  readOnly ||
1047
- !(0, validations_1.hasAllDependenciesFullfilled)((_p = options === null || options === void 0 ? void 0 : options[optionName]) === null || _p === void 0 ? void 0 : _p.depends_on, availableOptions, options || {}), readOnly: readOnly, size: editorSize || rest.size, menuItems: ((_q = options === null || options === void 0 ? void 0 : options[optionName]) === null || _q === void 0 ? void 0 : _q.ui_type) === 'any' ?
1158
+ !(0, validations_1.hasAllDependenciesFullfilled)((_o = options === null || options === void 0 ? void 0 : options[optionName]) === null || _o === void 0 ? void 0 : _o.depends_on, availableOptions, options || {}), readOnly: readOnly, size: editorSize || rest.size, menuItems: ((_p = options === null || options === void 0 ? void 0 : options[optionName]) === null || _p === void 0 ? void 0 : _p.ui_type) === 'any' ?
1048
1159
  getCustomMenuTemplateItems(optionName)
1049
1160
  : undefined }, templateFieldProps)), (0, jsx_runtime_1.jsx)(OptionFieldMessages_1.OptionFieldMessages, { schema: options || {}, allOptions: availableOptions, name: optionName, option: __assign({ type: type }, other), getType: getTypeForOption }), operators && (0, size_1.default)(operators) && (0, size_1.default)(other.op) ?
1050
1161
  (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreVerticalSpacer, { height: 5 }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreMessage, { size: 'small', children: (0, jsx_runtime_1.jsxs)(reqore_1.ReqoreTagGroup, { children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreTag, { size: 'small', labelKey: 'WHERE', label: optionName }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreTag, { size: 'small', labelKey: 'IS', label: operatorParts.join(' ') }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreTag, { size: 'small', intent: 'info', label: other.value ?
@@ -1187,6 +1298,7 @@ var FormEngine = function (_a) {
1187
1298
  var compactToolbarContextValue = (0, react_2.useMemo)(function () { return ({
1188
1299
  readOnly: readOnly,
1189
1300
  invalidCount: (0, size_1.default)(validityData.invalidFields),
1301
+ attentionCount: readFirstAttentionCount,
1190
1302
  completion: readFirstCompletion,
1191
1303
  showInvalidOnly: showInvalidOptionsOnly,
1192
1304
  onToggleInvalidOnly: handleToggleInvalidOnly,
@@ -1211,6 +1323,7 @@ var FormEngine = function (_a) {
1211
1323
  }); }, [
1212
1324
  readOnly,
1213
1325
  validityData,
1326
+ readFirstAttentionCount,
1214
1327
  readFirstCompletion,
1215
1328
  showInvalidOptionsOnly,
1216
1329
  handleToggleInvalidOnly,
@@ -1265,15 +1378,18 @@ var FormEngine = function (_a) {
1265
1378
  pushRow(optionName, false);
1266
1379
  }
1267
1380
  });
1268
- // When searching, also surface matching hidden optional fields (not yet
1269
- // added) so the search spans the whole schema, not just the visible rows.
1270
- if (query) {
1271
- (0, lodash_1.forEach)(filteredOptions, function (_schema, optionName) {
1272
- if (matchesQuery(optionName)) {
1273
- pushRow(optionName, true);
1274
- }
1275
- });
1276
- }
1381
+ // Surface EVERY not-yet-added optional field as an addable (hidden) row, so
1382
+ // the whole schema is browsable inline they all land in the Optional box
1383
+ // (hidden ⇒ 'optional' bucket) instead of being buried in the Fields menu.
1384
+ // Narrowed by the same filters as the listed rows (search query + required-
1385
+ // only). availableOptions (listed) and filteredOptions (these) are disjoint —
1386
+ // the former is built from fixedValue keys, the latter excludes them — so a
1387
+ // field is never both a listed and a hidden row.
1388
+ (0, lodash_1.forEach)(filteredOptions, function (_schema, optionName) {
1389
+ if (matchesFilters(optionName)) {
1390
+ pushRow(optionName, true);
1391
+ }
1392
+ });
1277
1393
  // User sort (Fields menu → "Sort by"), applied WITHIN each group so the
1278
1394
  // group sections and the required-group rails are preserved. Schema order is
1279
1395
  // the default and the stable tiebreaker (Array.sort is stable, and each
@@ -1317,6 +1433,57 @@ var FormEngine = function (_a) {
1317
1433
  return 1;
1318
1434
  return groupOrder.indexOf(a) - groupOrder.indexOf(b);
1319
1435
  });
1436
+ var buckets = {
1437
+ attention: {},
1438
+ set: {},
1439
+ optional: {},
1440
+ };
1441
+ var bucketGroups = { attention: [], set: [], optional: [] };
1442
+ // Freeze the box of any field currently being edited (or whose one-of group
1443
+ // has an edited member) to its last settled box — so finishing an edit that
1444
+ // flips its status doesn't remount it in another box and steal focus.
1445
+ var stableBucketOf = function (entry) {
1446
+ var _a;
1447
+ var fresh = getOptionBucket(entry.name, entry.hidden);
1448
+ var groupBeingEdited = !entry.hidden &&
1449
+ (((_a = options === null || options === void 0 ? void 0 : options[entry.name]) === null || _a === void 0 ? void 0 : _a.required_groups) || []).some(function (g) {
1450
+ return (requiredGroupsInfo.members[g] || []).some(function (m) { return expandedOptions.includes(m); });
1451
+ });
1452
+ if (!entry.hidden && (expandedOptions.includes(entry.name) || groupBeingEdited)) {
1453
+ var memo = settledBucket.current[entry.name];
1454
+ if (memo)
1455
+ return memo;
1456
+ }
1457
+ settledBucket.current[entry.name] = fresh;
1458
+ return fresh;
1459
+ };
1460
+ groupKeys.forEach(function (groupName) {
1461
+ grouped[groupName].forEach(function (entry) {
1462
+ var b = stableBucketOf(entry);
1463
+ if (!buckets[b][groupName]) {
1464
+ buckets[b][groupName] = [];
1465
+ bucketGroups[b].push(groupName);
1466
+ }
1467
+ buckets[b][groupName].push(entry);
1468
+ });
1469
+ });
1470
+ var bucketCount = function (b) {
1471
+ return bucketGroups[b].reduce(function (n, g) { return n + buckets[b][g].length; }, 0);
1472
+ };
1473
+ // 'general' / 'optional' are the SYNTHETIC fallback group keys getOptionGroup
1474
+ // assigns to fields with no explicit `group` — printing a "General"/"Optional"
1475
+ // sub-label for those is just noise, so suppress it. BUT a consumer may also
1476
+ // use 'general' as a REAL group (defining it in the `groups` prop and tagging
1477
+ // fields with `group: 'general'`); in that case it's a named group like any
1478
+ // other and DOES get its sub-label.
1479
+ var showGroupSubLabel = function (groupName) {
1480
+ return (groupName !== 'general' && groupName !== 'optional') || !!(groups === null || groups === void 0 ? void 0 : groups[groupName]);
1481
+ };
1482
+ var STATUS_BOXES = [
1483
+ { key: 'attention', label: 'Needs attention', intent: 'warning', icon: 'ErrorWarningLine' },
1484
+ { key: 'set', label: 'Set', intent: 'success', icon: 'CheckLine' },
1485
+ { key: 'optional', label: 'Optional', icon: 'CheckboxBlankCircleLine' },
1486
+ ];
1320
1487
  // Build the rows for one group: contiguous required-group members are pulled
1321
1488
  // together at the first member's slot and rendered as a connected rail (flat
1322
1489
  // rows — no wrapper — so the value surface applies normally; the rail + nodes
@@ -1331,8 +1498,9 @@ var FormEngine = function (_a) {
1331
1498
  }
1332
1499
  : shownOptions[entry.name], hidden: entry.hidden, clustered: clustered, clusterFirst: clusterFirst, clusterLast: clusterLast }, entry.name));
1333
1500
  };
1334
- if (compactNarrow)
1335
- return names.map(function (entry) { return renderRow(entry, false); });
1501
+ // (Clustering runs in narrow mode too now — the "One of the below is
1502
+ // required" box wraps the members regardless of width; it no longer relies
1503
+ // on a contiguous rail.)
1336
1504
  var emitted = new Set();
1337
1505
  var groupOf = function (name) { var _a, _b; return (_b = (_a = options === null || options === void 0 ? void 0 : options[name]) === null || _a === void 0 ? void 0 : _a.required_groups) === null || _b === void 0 ? void 0 : _b[0]; };
1338
1506
  return names.map(function (entry) {
@@ -1345,51 +1513,73 @@ var FormEngine = function (_a) {
1345
1513
  if (memberEntries.length < 2)
1346
1514
  return renderRow(entry, false);
1347
1515
  emitted.add(grp);
1348
- return memberEntries.map(function (e, idx) {
1516
+ var railed = memberEntries.map(function (e, idx) {
1349
1517
  return renderRow(e, true, idx === 0, idx === memberEntries.length - 1);
1350
1518
  });
1519
+ // An UNMET one-of group gets the explicit "One of the below is required"
1520
+ // box (the Focus cluster). A met group needs no banner — the rail + the
1521
+ // "Covers"/"Covered by" chips already say which member satisfies it.
1522
+ if (requiredGroupsInfo.satisfiedBy[grp])
1523
+ return railed;
1524
+ return ((0, jsx_runtime_1.jsxs)(compactRowStyles_1.StyledRequiredClusterBox, { className: 'options-readfirst-required-cluster', "$border": "".concat(cWarning, "33"), "$tint": "".concat(cWarning, "0d"), children: [(0, jsx_runtime_1.jsxs)(compactRowStyles_1.StyledRequiredClusterHeader, { "$color": cWarning, children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreIcon, { icon: 'LinkM', size: '11px', style: { color: cWarning } }), "One of the below is required"] }), railed] }, grp));
1351
1525
  });
1352
1526
  };
1353
- return ((0, jsx_runtime_1.jsx)(exports.OptionsContext.Provider, { value: { schema: options, value: availableOptions }, children: (0, jsx_runtime_1.jsx)(compactRowContext_1.CompactRowContext.Provider, { value: compactRowContextValue, children: (0, jsx_runtime_1.jsxs)(reqore_1.ReqoreErrorBoundary, { children: [showHelpForOption && ((0, jsx_runtime_1.jsx)(OptionsHelpDialog_1.OptionsHelpDialog, { onClose: function () { return setShowHelpForOption(undefined); }, option: options[showHelpForOption] })), (0, jsx_runtime_1.jsx)(compactToolbarContext_1.CompactToolbarContext.Provider, { value: compactToolbarContextValue, children: (0, jsx_runtime_1.jsxs)(StyledCompactWrap, { ref: setCompactWrap, className: 'options-readfirst-scroll', "$flush": compactFlush, children: [(0, jsx_runtime_1.jsxs)(compactRowStyles_1.StyledCompactPanel, { "$headerBg": headerBg, flat: true, stickyHeader: true, padded: false, actions: compactHeaderActions, contentStyle: {
1527
+ return ((0, jsx_runtime_1.jsx)(exports.OptionsContext.Provider, { value: { schema: options, value: availableOptions }, children: (0, jsx_runtime_1.jsx)(compactRowContext_1.CompactRowContext.Provider, { value: compactRowContextValue, children: (0, jsx_runtime_1.jsxs)(reqore_1.ReqoreErrorBoundary, { children: [showHelpForOption && ((0, jsx_runtime_1.jsx)(OptionsHelpDialog_1.OptionsHelpDialog, { onClose: function () { return setShowHelpForOption(undefined); }, option: options[showHelpForOption] })), (0, jsx_runtime_1.jsx)(compactToolbarContext_1.CompactToolbarContext.Provider, { value: compactToolbarContextValue, children: (0, jsx_runtime_1.jsxs)(StyledCompactWrap, { ref: setCompactWrap, className: 'options-readfirst-scroll', "$flush": compactFlush || compactNested, children: [(0, jsx_runtime_1.jsxs)(compactRowStyles_1.StyledCompactPanel
1528
+ // The top-level form scrolls, so its toolbar STICKS and carries a
1529
+ // dark blurred backdrop so content ghosts cleanly beneath it. A
1530
+ // nested (arg_schema) sub-form owns no scroll context — drop the
1531
+ // sticky, the backdrop, and the stacking context so its header is
1532
+ // transparent inside the parent's card.
1533
+ , { "$headerBg": compactNested ? 'transparent' : headerBg, "$nested": compactNested, flat: true,
1534
+ // No panel background: the form sits transparently on whatever
1535
+ // hosts it (page, drawer, or — for an arg_schema field — the
1536
+ // parent's edit card) instead of stacking its own dark surface.
1537
+ // The status boxes keep their own tints; the sticky toolbar keeps
1538
+ // its blurred header via the $headerBg override.
1539
+ transparent: true, stickyHeader: !compactNested, padded: false, actions: compactHeaderActions, contentStyle: {
1354
1540
  display: 'flex',
1355
1541
  flexFlow: 'column',
1356
1542
  gap: '10px',
1357
- padding: '0 0 12px',
1543
+ // Nested sub-form: no surrounding panel padding (it's flush in
1544
+ // the parent card); top-level keeps a small bottom gutter.
1545
+ padding: compactNested ? '0' : '0 0 12px',
1358
1546
  }, children: [(0, size_1.default)(groupKeys) === 0 ?
1359
1547
  (0, jsx_runtime_1.jsx)(reqore_1.ReqoreMessage, { flat: true, opaque: false, size: 'small', children: "No fields match the current filters." })
1360
- : null, groupKeys.map(function (groupName) {
1361
- var names = grouped[groupName];
1362
- var groupConfig = groups === null || groups === void 0 ? void 0 : groups[groupName];
1363
- var invalidCount = names.filter(function (entry) {
1364
- var _a, _b, _c;
1365
- return !entry.hidden &&
1366
- !isOptionValid(entry.name, ((_a = options === null || options === void 0 ? void 0 : options[entry.name]) === null || _a === void 0 ? void 0 : _a.ui_type) ||
1367
- ((_b = options === null || options === void 0 ? void 0 : options[entry.name]) === null || _b === void 0 ? void 0 : _b.type), (_c = shownOptions[entry.name]) === null || _c === void 0 ? void 0 : _c.value);
1368
- }).length;
1369
- return ((0, jsx_runtime_1.jsxs)(reqore_1.ReqorePanel, { flat: true, minimal: true, collapseButtonProps: { flat: true, minimal: true, size: 'small' }, collapsible: true, label: (0, jsx_runtime_1.jsxs)(compactRowStyles_1.StyledGroupHeader, { children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreP, { effect: { weight: 'bold' }, size: 'big', children: (0, readFirst_1.getOptionGroupLabel)(groupName, groups) }), (0, jsx_runtime_1.jsx)(compactRowStyles_1.StyledGroupHeaderLine, { "$color": cGroupLine }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreButton, __assign({ readOnly: true, size: 'tiny', minimal: true, flat: true, compact: true, effect: { uppercase: true, spaced: 1 } }, (groupName === 'optional' ? { label: "".concat(names.length, " optional") }
1370
- : invalidCount ?
1371
- {
1372
- label: "".concat(invalidCount, " to resolve"),
1373
- intent: 'warning',
1374
- icon: 'ErrorWarningLine',
1375
- }
1376
- : {
1377
- label: 'All set',
1378
- intent: 'success',
1379
- icon: 'CheckLine',
1380
- })))] }), icon: groupConfig === null || groupConfig === void 0 ? void 0 : groupConfig.icon, className: 'options-readfirst-group', padded: false, contentStyle: { padding: '4px 4px 6px' }, children: [(groupConfig === null || groupConfig === void 0 ? void 0 : groupConfig.subtitle) ?
1381
- (0, jsx_runtime_1.jsx)(reqore_1.ReqoreP, { size: 'small', effect: { opacity: 0.6 },
1382
- // Indent to the same content line as the rows (StyledGroupBody's
1383
- // `margin-left` clamp) so the subtitle sits tucked under the group
1384
- // name instead of at the panel edge — and clears the group's
1385
- // vertical rule (left:16px) rather than crossing it.
1386
- style: {
1387
- marginTop: 2,
1388
- marginBottom: 8,
1389
- marginLeft: compactRowStyles_1.GROUP_INDENT,
1390
- paddingRight: 10,
1391
- }, children: groupConfig.subtitle })
1392
- : null, (0, jsx_runtime_1.jsx)(compactRowStyles_1.StyledGroupBody, { "$divider": cDivider, "$hover": cHover, "$focus": cWarning, "$success": cSuccess, "$rowBg": cRowBg, "$lineColor": cGroupLine, className: compactNarrow ? 'readfirst-narrow' : undefined, children: renderGroupRows(names) })] }, groupName));
1548
+ : null, STATUS_BOXES.map(function (box) {
1549
+ var groupsInBox = bucketGroups[box.key];
1550
+ var count = bucketCount(box.key);
1551
+ if (!count)
1552
+ return null;
1553
+ var accent = box.key === 'attention' ? cWarning
1554
+ : box.key === 'set' ? cSuccess
1555
+ : cMuted;
1556
+ // The muted "Optional" box reads as a quieter, recessed
1557
+ // surface a touch darker than the page rather than the
1558
+ // faint grey tint the accent would give.
1559
+ var boxBg = box.key === 'optional' ?
1560
+ (0, colors_1.changeDarkness)((0, colors_1.getMainBackgroundColor)(theme), 0.06)
1561
+ : undefined;
1562
+ return ((0, jsx_runtime_1.jsx)(compactRowStyles_1.StyledStatusBox, { "$accent": accent, "$bg": boxBg, flat: true, minimal: true, collapseButtonProps: { flat: true, minimal: true, size: 'small' }, collapsible: true,
1563
+ // The Optional box now holds every not-yet-added field, so
1564
+ // it starts COLLAPSED to keep the form focused on what's in
1565
+ // use. But a SEARCH must surface matching addable fields —
1566
+ // and ReqorePanel unmounts collapsed content — so force it
1567
+ // open whenever a query is active. (isCollapsed is the
1568
+ // panel's controllable state; manual toggling still works
1569
+ // when no query is set.)
1570
+ isCollapsed: box.key === 'optional' && !query, label: (0, jsx_runtime_1.jsxs)(compactRowStyles_1.StyledGroupHeader, { children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreP, { effect: { weight: 'bold' }, size: 'normal', children: box.label }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreTag, { size: 'small', minimal: true, compact: true, intent: box.intent, label: String(count) })] }), icon: box.icon, className: 'options-readfirst-group', padded: false, contentStyle: { padding: '4px 4px 6px' }, children: (0, jsx_runtime_1.jsx)(compactRowStyles_1.StyledGroupBody, { "$divider": cDivider, "$hover": cHover, "$focus": cWarning, "$success": cSuccess, "$rowBg": cRowBg, "$lineColor": cGroupLine, className: compactNarrow ? 'readfirst-narrow' : undefined, children: groupsInBox.map(function (groupName) {
1571
+ var groupConfig = groups === null || groups === void 0 ? void 0 : groups[groupName];
1572
+ return ((0, jsx_runtime_1.jsxs)(react_2.default.Fragment, { children: [showGroupSubLabel(groupName) ?
1573
+ (0, jsx_runtime_1.jsx)(compactRowStyles_1.StyledStatusBoxGroupLabel, { children: (0, readFirst_1.getOptionGroupLabel)(groupName, groups) })
1574
+ : null, showGroupSubLabel(groupName) && (groupConfig === null || groupConfig === void 0 ? void 0 : groupConfig.subtitle) ?
1575
+ (0, jsx_runtime_1.jsx)(reqore_1.ReqoreP, { size: 'small', effect: { opacity: 0.6 }, style: {
1576
+ marginTop: 2,
1577
+ marginBottom: 8,
1578
+ marginLeft: compactRowStyles_1.GROUP_INDENT,
1579
+ paddingRight: 10,
1580
+ }, children: groupConfig.subtitle })
1581
+ : null, renderGroupRows(buckets[box.key][groupName])] }, groupName));
1582
+ }) }) }, box.key));
1393
1583
  })] }), commitMode === 'batched' && !readOnly && dirtyOptionNames.length ?
1394
1584
  (0, jsx_runtime_1.jsx)(StyledCommitDock, { "$bg": cBg, "$border": cDivider, children: (0, jsx_runtime_1.jsxs)(reqore_1.ReqoreControlGroup, { className: 'options-readfirst-commitbar', verticalAlign: 'center', wrap: true, children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreTag, { size: 'tiny', minimal: true, flat: true, compact: true, effect: { uppercase: true, spaced: 1 }, intent: 'warning', icon: 'EditLine', label: "".concat(dirtyOptionNames.length, " unsaved change").concat(dirtyOptionNames.length === 1 ? '' : 's') }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreButton, { size: 'small', intent: 'success', icon: 'CheckLine', fixed: true, className: 'options-readfirst-save', disabled: !validityData.isValid, tooltip: validityData.isValid ?
1395
1585
  'Apply the staged changes'