@qoretechnologies/reqraft 0.10.38 → 0.10.39-pr.95.g3503e91

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 (102) hide show
  1. package/.claude/CLAUDE.md +12 -0
  2. package/dist/components/codeSize/index.d.ts +37 -0
  3. package/dist/components/codeSize/index.d.ts.map +1 -0
  4. package/dist/components/codeSize/index.js +87 -0
  5. package/dist/components/codeSize/index.js.map +1 -0
  6. package/dist/components/form/engine/CompactRow.d.ts +20 -1
  7. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  8. package/dist/components/form/engine/CompactRow.js +357 -30
  9. package/dist/components/form/engine/CompactRow.js.map +1 -1
  10. package/dist/components/form/engine/FormEngine.d.ts +24 -1
  11. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  12. package/dist/components/form/engine/FormEngine.js +179 -39
  13. package/dist/components/form/engine/FormEngine.js.map +1 -1
  14. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts +37 -0
  15. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -0
  16. package/dist/components/form/engine/_structuredData/SchemaDataView.js +335 -0
  17. package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -0
  18. package/dist/components/form/engine/compactRowStyles.d.ts +13 -0
  19. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  20. package/dist/components/form/engine/compactRowStyles.js +30 -5
  21. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  22. package/dist/components/form/engine/readFirst.d.ts +73 -1
  23. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  24. package/dist/components/form/engine/readFirst.js +175 -5
  25. package/dist/components/form/engine/readFirst.js.map +1 -1
  26. package/dist/components/form/fields/Field.d.ts.map +1 -1
  27. package/dist/components/form/fields/Field.js +1 -1
  28. package/dist/components/form/fields/Field.js.map +1 -1
  29. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +12 -2
  30. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  31. package/dist/components/form/fields/allowed-values/AllowedValues.js +16 -6
  32. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  33. package/dist/components/form/fields/array/ArrayAuto.d.ts.map +1 -1
  34. package/dist/components/form/fields/array/ArrayAuto.js +51 -21
  35. package/dist/components/form/fields/array/ArrayAuto.js.map +1 -1
  36. package/dist/components/form/fields/array/ArrayAutoField.d.ts.map +1 -1
  37. package/dist/components/form/fields/array/ArrayAutoField.js +42 -25
  38. package/dist/components/form/fields/array/ArrayAutoField.js.map +1 -1
  39. package/dist/components/form/fields/auto/AutoFormField.d.ts +4 -0
  40. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  41. package/dist/components/form/fields/auto/AutoFormField.js +29 -4
  42. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  43. package/dist/components/form/fields/long-string/LongString.d.ts +9 -1
  44. package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
  45. package/dist/components/form/fields/long-string/LongString.js +25 -4
  46. package/dist/components/form/fields/long-string/LongString.js.map +1 -1
  47. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  48. package/dist/components/form/fields/template/TemplateField.js +12 -2
  49. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  50. package/dist/helpers/common.d.ts +8 -1
  51. package/dist/helpers/common.d.ts.map +1 -1
  52. package/dist/helpers/common.js +9 -1
  53. package/dist/helpers/common.js.map +1 -1
  54. package/dist/helpers/optionUiTypes.d.ts +30 -0
  55. package/dist/helpers/optionUiTypes.d.ts.map +1 -0
  56. package/dist/helpers/optionUiTypes.js +148 -0
  57. package/dist/helpers/optionUiTypes.js.map +1 -0
  58. package/dist/helpers/options.d.ts +19 -0
  59. package/dist/helpers/options.d.ts.map +1 -1
  60. package/dist/helpers/options.js +25 -1
  61. package/dist/helpers/options.js.map +1 -1
  62. package/dist/helpers/providerValue.d.ts +68 -0
  63. package/dist/helpers/providerValue.d.ts.map +1 -0
  64. package/dist/helpers/providerValue.js +50 -0
  65. package/dist/helpers/providerValue.js.map +1 -0
  66. package/dist/helpers/singleLineString.d.ts +37 -0
  67. package/dist/helpers/singleLineString.d.ts.map +1 -0
  68. package/dist/helpers/singleLineString.js +66 -0
  69. package/dist/helpers/singleLineString.js.map +1 -0
  70. package/dist/helpers/validations.d.ts +85 -2
  71. package/dist/helpers/validations.d.ts.map +1 -1
  72. package/dist/helpers/validations.js +802 -76
  73. package/dist/helpers/validations.js.map +1 -1
  74. package/dist/index.d.ts +1 -0
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +1 -0
  77. package/dist/index.js.map +1 -1
  78. package/package.json +1 -1
  79. package/src/components/codeSize/index.tsx +79 -0
  80. package/src/components/form/engine/CompactRow.tsx +436 -31
  81. package/src/components/form/engine/FormEngine.stories.tsx +802 -15
  82. package/src/components/form/engine/FormEngine.tsx +180 -13
  83. package/src/components/form/engine/_structuredData/SchemaDataView.stories.tsx +233 -0
  84. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +630 -0
  85. package/src/components/form/engine/compactRowStyles.ts +44 -2
  86. package/src/components/form/engine/readFirst.ts +201 -5
  87. package/src/components/form/fields/Field.tsx +3 -1
  88. package/src/components/form/fields/allowed-values/AllowedValues.tsx +24 -2
  89. package/src/components/form/fields/array/ArrayAuto.tsx +35 -4
  90. package/src/components/form/fields/array/ArrayAutoField.stories.tsx +134 -0
  91. package/src/components/form/fields/array/ArrayAutoField.tsx +22 -4
  92. package/src/components/form/fields/auto/AutoFormField.tsx +30 -1
  93. package/src/components/form/fields/long-string/LongString.tsx +46 -5
  94. package/src/components/form/fields/template/TemplateField.tsx +16 -1
  95. package/src/helpers/common.ts +11 -1
  96. package/src/helpers/optionUiTypes.ts +153 -0
  97. package/src/helpers/options.ts +23 -0
  98. package/src/helpers/providerValue.ts +125 -0
  99. package/src/helpers/singleLineString.ts +61 -0
  100. package/src/helpers/validations.ts +1105 -80
  101. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +23 -2
  102. package/src/index.tsx +1 -0
@@ -202,7 +202,7 @@ var StyledCommitDock = styled_components_1.default.div(templateObject_2 || (temp
202
202
  return "".concat((0, colors_1.changeDarkness)($bg, 0.4)).concat((0, colors_1.percentToHexAlpha)(55));
203
203
  });
204
204
  var getType = function (type, operators, operator) {
205
- var finalType = getTypeFromOperator(operators, (0, exports.fixOperatorValue)(operator)) || type;
205
+ var finalType = getTypeFromOperator(operators, (0, common_1.fixOperatorValue)(operator)) || type;
206
206
  var resolvedType = (0, isArray_1.default)(finalType) ? finalType[0] : finalType;
207
207
  // A value can be received before its server-driven option schema. Keep the
208
208
  // renderer type-safe during that transition without guessing a concrete type.
@@ -216,10 +216,11 @@ var getTypeFromOperator = function (operators, operatorData) {
216
216
  }
217
217
  return ((_a = operators[(0, lodash_1.last)(operatorData)]) === null || _a === void 0 ? void 0 : _a.type) || null;
218
218
  };
219
- var fixOperatorValue = function (operator) {
220
- return (0, isArray_1.default)(operator) ? operator : [operator];
221
- };
222
- exports.fixOperatorValue = fixOperatorValue;
219
+ /** Re-exported from `helpers/common` so existing importers keep working; it
220
+ * lives there because `helpers/validations` needs it and cannot import this
221
+ * module (this module imports the validators). */
222
+ var common_2 = require("../../../helpers/common");
223
+ Object.defineProperty(exports, "fixOperatorValue", { enumerable: true, get: function () { return common_2.fixOperatorValue; } });
223
224
  var getOptionSchemaStorageType = function (option, isRendererOnly) {
224
225
  if (isRendererOnly === void 0) { isRendererOnly = rendererTypes_1.isRendererOnlyUiType; }
225
226
  return (((0, readFirst_1.isFixedCompactAllowedValueOption)(option)
@@ -253,7 +254,7 @@ var fixOptions = function (value, options, operators, isRendererOnly) {
253
254
  return fixedValue;
254
255
  }
255
256
  (0, lodash_1.forEach)(options, function (option, name) {
256
- var _a, _b, _c, _d, _f, _g, _h;
257
+ var _a, _b, _c, _d, _f, _g, _h, _j;
257
258
  if (option.value ||
258
259
  option.required_groups ||
259
260
  ((option.required || option.preselected) && !fixedValue[name]) ||
@@ -264,14 +265,30 @@ var fixOptions = function (value, options, operators, isRendererOnly) {
264
265
  ((_c = fixedValue[name]) === null || _c === void 0 ? void 0 : _c.value) === undefined) {
265
266
  obj = option.default_value;
266
267
  }
268
+ else if (option.default_view === 'expression' &&
269
+ ((_d = fixedValue[name]) === null || _d === void 0 ? void 0 : _d.value) === undefined) {
270
+ // A field the schema declares as opening in expression mode starts as an
271
+ // EMPTY expression, not as its raw default. `isDefaultFunction` already
272
+ // makes the renderer open that way, so without this the data disagreed
273
+ // with what the operator was looking at: an expression editor whose
274
+ // value was still the plain default, and no `is_expression` on the field
275
+ // until the first edit.
276
+ obj = {
277
+ type: type,
278
+ value: {
279
+ args: [],
280
+ },
281
+ is_expression: true,
282
+ };
283
+ }
267
284
  else {
268
285
  obj = {
269
286
  type: type,
270
- value: (_g = (_f = (typeof fixedValue[name] === 'object' ?
271
- (_d = fixedValue[name]) === null || _d === void 0 ? void 0 : _d.value
272
- : fixedValue[name])) !== null && _f !== void 0 ? _f : option.value) !== null && _g !== void 0 ? _g : (0, common_1.getDefaultValue)(option),
287
+ value: (_h = (_g = (typeof fixedValue[name] === 'object' ?
288
+ (_f = fixedValue[name]) === null || _f === void 0 ? void 0 : _f.value
289
+ : fixedValue[name])) !== null && _g !== void 0 ? _g : option.value) !== null && _h !== void 0 ? _h : (0, common_1.getDefaultValue)(option),
273
290
  };
274
- if ((_h = fixedValue[name]) === null || _h === void 0 ? void 0 : _h.is_expression) {
291
+ if ((_j = fixedValue[name]) === null || _j === void 0 ? void 0 : _j.is_expression) {
275
292
  obj.is_expression = true;
276
293
  }
277
294
  }
@@ -280,7 +297,7 @@ var fixOptions = function (value, options, operators, isRendererOnly) {
280
297
  });
281
298
  var res = (0, reduce_1.default)(fixedValue, function (newValue, option, optionName) {
282
299
  var _a;
283
- var _b, _c, _d, _f, _g, _h, _j, _k;
300
+ var _b, _c, _d, _f, _g, _h, _j;
284
301
  var newOption = option;
285
302
  if (!(0, lodash_1.isPlainObject)(newOption) || !(newOption === null || newOption === void 0 ? void 0 : newOption.type)) {
286
303
  var isUntypedEnvelope = (0, lodash_1.isPlainObject)(newOption) && 'value' in newOption;
@@ -295,17 +312,35 @@ var fixOptions = function (value, options, operators, isRendererOnly) {
295
312
  isRendererOnly(newOption.type)) {
296
313
  newOption = __assign(__assign({}, newOption), { type: (0, exports.getType)(getOptionSchemaStorageType(options === null || options === void 0 ? void 0 : options[optionName], isRendererOnly), operators, newOption.op) });
297
314
  }
315
+ // A value that is not one of the declared choices is dropped. What counts
316
+ // as "one of the choices" is `findAllowedValueOption` — the same predicate
317
+ // the read-first row uses to LABEL a value — because a value the row can
318
+ // name is by definition a value the form must keep. Inlining a narrower
319
+ // test here (envelope and `name`, but not a bare `value`) silently erased
320
+ // every value declared the bare way: the collapsed row still showed its
321
+ // display name while the editor showed "—" and the value never reached
322
+ // the submitted data.
323
+ // `allowed_values: []` is NOT "no value is permitted" — it is the server
324
+ // saying it has no reference values to offer right now (the app-action
325
+ // catalogue ships exactly that, with an
326
+ // `option_reference_values_unavailable` message attached). An empty array
327
+ // is truthy, so testing the array itself made every such option erase the
328
+ // operator's stored value on the first render, before the
329
+ // connection-refreshed schema arrived to say otherwise — and the emptied
330
+ // form then autosaved over the draft, so the value was gone for good.
331
+ // Only an option that actually declares choices can have a value that is
332
+ // not one of them.
298
333
  if (newOption.value !== undefined &&
299
- ((_c = options === null || options === void 0 ? void 0 : options[optionName]) === null || _c === void 0 ? void 0 : _c.allowed_values) &&
300
- !((_f = (_d = options === null || options === void 0 ? void 0 : options[optionName]) === null || _d === void 0 ? void 0 : _d.allowed_values) === null || _f === void 0 ? void 0 : _f.find(function (allowedValue) { var _a; return ((_a = allowedValue.value) === null || _a === void 0 ? void 0 : _a.value) === newOption.value || allowedValue.name === newOption.value; })) &&
334
+ ((_d = (_c = options === null || options === void 0 ? void 0 : options[optionName]) === null || _c === void 0 ? void 0 : _c.allowed_values) === null || _d === void 0 ? void 0 : _d.length) &&
335
+ !(0, readFirst_1.findAllowedValueOption)(newOption.value, options === null || options === void 0 ? void 0 : options[optionName]) &&
301
336
  !(0, TemplateField_1.isValueTemplate)(newOption.value) &&
302
- !((_g = options === null || options === void 0 ? void 0 : options[optionName]) === null || _g === void 0 ? void 0 : _g.multiselect) &&
303
- !((_h = options === null || options === void 0 ? void 0 : options[optionName]) === null || _h === void 0 ? void 0 : _h.allowed_values_creatable)) {
337
+ !((_f = options === null || options === void 0 ? void 0 : options[optionName]) === null || _f === void 0 ? void 0 : _f.multiselect) &&
338
+ !((_g = options === null || options === void 0 ? void 0 : options[optionName]) === null || _g === void 0 ? void 0 : _g.allowed_values_creatable)) {
304
339
  newOption.value = undefined;
305
340
  }
306
341
  if (newOption.value &&
307
- ((_j = options === null || options === void 0 ? void 0 : options[optionName]) === null || _j === void 0 ? void 0 : _j.readonly) &&
308
- ((_k = options === null || options === void 0 ? void 0 : options[optionName]) === null || _k === void 0 ? void 0 : _k.default_value) &&
342
+ ((_h = options === null || options === void 0 ? void 0 : options[optionName]) === null || _h === void 0 ? void 0 : _h.readonly) &&
343
+ ((_j = options === null || options === void 0 ? void 0 : options[optionName]) === null || _j === void 0 ? void 0 : _j.default_value) &&
309
344
  (0, common_1.getDefaultValue)(options === null || options === void 0 ? void 0 : options[optionName]) !== newOption.value) {
310
345
  newOption.value = (0, common_1.getDefaultValue)(options[optionName]);
311
346
  }
@@ -369,7 +404,7 @@ var FormEngineImpl = function (_a) {
369
404
  // form draws; destructured here only so it cannot reach `rest` and be spread
370
405
  // onto a DOM node
371
406
  _markdownRenderer = _a.markdownRenderer, // eslint-disable-line @typescript-eslint/no-unused-vars
372
- rendererOnlyUiTypes = _a.rendererOnlyUiTypes, inheritedFromParent = _a.inheritedFromParent, autoFocusFirstRequired = _a.autoFocusFirstRequired, initialExpandedOptions = _a.initialExpandedOptions, rest = __rest(_a, ["name", "uniqueName", "value", "onChange", "onSingleOptionsChange", "onDependableOptionChange", "placeholder", "noValueString", "isValid", "url", "customUrl", "operatorsUrl", "onOptionsLoaded", "recordRequiresSearchOptions", "readOnly", "allowTemplates", "interfaceContext", "templateFieldProps", "forceDropdown", "showTypeToggle", "compact", "compactFlush", "compactNested", "compactPanelProps", "commitMode", "expandMode", "onCommit", "operators", "groups", "optionsLoader", "onValidityChange", "optionActions", "optionActionsCollapse", "componentOverrides", "codePreviewRenderer", "markdownRenderer", "rendererOnlyUiTypes", "inheritedFromParent", "autoFocusFirstRequired", "initialExpandedOptions"]);
407
+ rendererOnlyUiTypes = _a.rendererOnlyUiTypes, inheritedFromParent = _a.inheritedFromParent, autoFocusFirstRequired = _a.autoFocusFirstRequired, expandFirstRequired = _a.expandFirstRequired, initialExpandedOptions = _a.initialExpandedOptions, rest = __rest(_a, ["name", "uniqueName", "value", "onChange", "onSingleOptionsChange", "onDependableOptionChange", "placeholder", "noValueString", "isValid", "url", "customUrl", "operatorsUrl", "onOptionsLoaded", "recordRequiresSearchOptions", "readOnly", "allowTemplates", "interfaceContext", "templateFieldProps", "forceDropdown", "showTypeToggle", "compact", "compactFlush", "compactNested", "compactPanelProps", "commitMode", "expandMode", "onCommit", "operators", "groups", "optionsLoader", "onValidityChange", "optionActions", "optionActionsCollapse", "componentOverrides", "codePreviewRenderer", "markdownRenderer", "rendererOnlyUiTypes", "inheritedFromParent", "autoFocusFirstRequired", "expandFirstRequired", "initialExpandedOptions"]);
373
408
  // Built-ins + whatever the consumer declared for its own injected editors.
374
409
  var isRendererOnly = (0, react_2.useMemo)(function () { return (0, rendererTypes_1.createRendererOnlyUiTypeCheck)(rendererOnlyUiTypes); }, [JSON.stringify(rendererOnlyUiTypes)]);
375
410
  // Pointer CAPABILITY and viewport WIDTH are different questions and both
@@ -411,19 +446,59 @@ var FormEngineImpl = function (_a) {
411
446
  var _0 = (0, react_2.useState)(false), showInvalidOptionsOnly = _0[0], setShowInvalidOptionsOnly = _0[1];
412
447
  // Which options are expanded into their editor (several can be open at once).
413
448
  var _1 = (0, react_2.useState)([]), expandedOptions = _1[0], setExpandedOptions = _1[1];
449
+ // --- Reveal a status box's content when it is opened ----------------------
450
+ //
451
+ // The status boxes stack, so the last one ("Optional", which holds every
452
+ // not-yet-added field) sits at the bottom of the form — frequently at the
453
+ // bottom of the scroll container too. Opening it mounts its rows BELOW the
454
+ // fold: the box's own header is all that stays on screen, the click appears to
455
+ // have done nothing, and the fields it just revealed have to be hunted for by
456
+ // scrolling.
457
+ //
458
+ // The box that was just opened, read by the layout effect below. Held as state
459
+ // rather than acted on inside the collapse handler because the handler runs
460
+ // BEFORE React re-renders — at that moment the rows do not exist yet, so the
461
+ // box has not grown and there is nothing to scroll to. A layout effect runs
462
+ // after the DOM is updated and before paint, which makes the reveal
463
+ // deterministic instead of a guessed delay.
464
+ var _2 = (0, react_2.useState)(undefined), openedStatusBox = _2[0], setOpenedStatusBox = _2[1];
465
+ var statusBoxRefs = (0, react_2.useRef)({});
466
+ (0, react_2.useLayoutEffect)(function () {
467
+ var _a, _b;
468
+ if (!openedStatusBox) {
469
+ return;
470
+ }
471
+ var element = statusBoxRefs.current[openedStatusBox];
472
+ // Consume it either way: a box that has gone (a search narrowed it out of
473
+ // existence) must not leave a pending reveal armed for a later render.
474
+ setOpenedStatusBox(undefined);
475
+ // `scrollIntoView` is not implemented in jsdom, and a missing reveal must
476
+ // never break a form.
477
+ if (!(element === null || element === void 0 ? void 0 : element.scrollIntoView)) {
478
+ return;
479
+ }
480
+ // 'nearest' scrolls the MINIMUM needed: nothing at all when the box is
481
+ // already fully visible, and a top-alignment when it is taller than the
482
+ // viewport (header plus the first rows — the right answer for a long
483
+ // Optional list). Anything else moves the page when it did not need to.
484
+ element.scrollIntoView({
485
+ block: 'nearest',
486
+ behavior: ((_b = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-reduced-motion: reduce)')) === null || _b === void 0 ? void 0 : _b.matches) ? 'auto' : 'smooth',
487
+ });
488
+ }, [openedStatusBox]);
414
489
  // Remembers each row's last settled status box, so an actively-edited field
415
490
  // stays put when its status flips (e.g. becomes valid) instead of jumping to
416
491
  // another box mid-edit and stealing focus. Keyed by option name.
417
492
  var settledBucket = (0, react_2.useRef)({});
418
493
  // Measured form width (not viewport — the form lives in drawers/panels of
419
494
  // arbitrary width) drives the stacked narrow layout.
420
- var _2 = (0, react_use_1.useMeasure)(), compactWrapRef = _2[0], compactWrapWidth = _2[1].width;
495
+ var _3 = (0, react_use_1.useMeasure)(), compactWrapRef = _3[0], compactWrapWidth = _3[1].width;
421
496
  // Own handle on the scroll wrap (useMeasure's ref is a callback, no `.current`)
422
497
  // so the label-column measurement can publish its CSS var on the element. It's
423
498
  // STATE, not a ref: the wrap mounts only after the loading-skeleton gate
424
499
  // resolves, so the measurement effect must re-run when the node appears — a
425
500
  // ref wouldn't retrigger it.
426
- var _3 = (0, react_2.useState)(null), compactWrapNode = _3[0], setCompactWrapNode = _3[1];
501
+ var _4 = (0, react_2.useState)(null), compactWrapNode = _4[0], setCompactWrapNode = _4[1];
427
502
  // Ref mirror of the wrap node for callbacks that must not re-create when it
428
503
  // mounts (flashOptions reads it inside a rAF).
429
504
  var compactWrapNodeRef = (0, react_2.useRef)(null);
@@ -477,8 +552,8 @@ var FormEngineImpl = function (_a) {
477
552
  var readRowHeights = (0, react_2.useRef)({});
478
553
  // Required-groups linkage: hovering a group chip highlights every member row;
479
554
  // clicking a sibling in the chip's popover scrolls to it and flashes it.
480
- var _4 = (0, react_2.useState)([]), highlightedOptions = _4[0], setHighlightedOptions = _4[1];
481
- var _5 = (0, react_2.useState)([]), flashedOptions = _5[0], setFlashedOptions = _5[1];
555
+ var _5 = (0, react_2.useState)([]), highlightedOptions = _5[0], setHighlightedOptions = _5[1];
556
+ var _6 = (0, react_2.useState)([]), flashedOptions = _6[0], setFlashedOptions = _6[1];
482
557
  var flashTimeout = (0, react_2.useRef)();
483
558
  var flashOptions = (0, react_2.useCallback)(function (optionNames, scrollToFirst) {
484
559
  if (scrollToFirst === void 0) { scrollToFirst = false; }
@@ -527,16 +602,16 @@ var FormEngineImpl = function (_a) {
527
602
  });
528
603
  var compactNarrow = !!compactWrapWidth && compactWrapWidth < 480;
529
604
  // Info panels auto-open on Tier-1 content; the per-row user override sticks.
530
- var _6 = (0, react_2.useState)({}), infoPanelOverrides = _6[0], setInfoPanelOverrides = _6[1];
605
+ var _7 = (0, react_2.useState)({}), infoPanelOverrides = _7[0], setInfoPanelOverrides = _7[1];
531
606
  // Toolbar filters affect the listed rows only — the meter reflects the full set.
532
- var _7 = (0, react_2.useState)(false), requiredOnly = _7[0], setRequiredOnly = _7[1];
533
- var _8 = (0, react_2.useState)(''), compactQuery = _8[0], setCompactQuery = _8[1];
607
+ var _8 = (0, react_2.useState)(false), requiredOnly = _8[0], setRequiredOnly = _8[1];
608
+ var _9 = (0, react_2.useState)(''), compactQuery = _9[0], setCompactQuery = _9[1];
534
609
  // Compact field sort (Fields menu → "Sort by"); 'schema' = declared order.
535
- var _9 = (0, react_2.useState)('schema'), compactSort = _9[0], setCompactSort = _9[1];
536
- var _10 = (0, react_2.useState)(function () { return ({
610
+ var _10 = (0, react_2.useState)('schema'), compactSort = _10[0], setCompactSort = _10[1];
611
+ var _11 = (0, react_2.useState)(function () { return ({
537
612
  fields: (0, exports.fixOptions)(value, options || {}, undefined, isRendererOnly),
538
613
  meta: undefined,
539
- }); }), localValue = _10[0], setLocalValue = _10[1];
614
+ }); }), localValue = _11[0], setLocalValue = _11[1];
540
615
  var originalValue = (0, react_2.useRef)();
541
616
  // Track the last value we emitted via onChange so we can skip re-applying fixOptions
542
617
  // when the parent echoes it back as the new value prop (controlled component loop prevention)
@@ -545,7 +620,7 @@ var FormEngineImpl = function (_a) {
545
620
  originalValue.current = localValue.fields;
546
621
  }
547
622
  var unavailableOptionsCount = (0, react_2.useRef)(0);
548
- var _11 = (0, useQorusTypes_1.useQorusTypes)(), compactValue = _11.compactValue, typesLoading = _11.loading;
623
+ var _12 = (0, useQorusTypes_1.useQorusTypes)(), compactValue = _12.compactValue, typesLoading = _12.loading;
549
624
  var templates = (0, useTemplates_1.useTemplates)(allowTemplates, rest.stringTemplates, interfaceContext);
550
625
  (0, react_2.useEffect)(function () {
551
626
  if ((0, lodash_1.isEqual)(localValue.fields, value)) {
@@ -813,7 +888,7 @@ var FormEngineImpl = function (_a) {
813
888
  var handleOperatorChange = (0, react_2.useCallback)(function (optionName, currentValue, operator, index) {
814
889
  var _a;
815
890
  setLocalValue === null || setLocalValue === void 0 ? void 0 : setLocalValue({
816
- fields: __assign(__assign({}, currentValue), (_a = {}, _a[optionName] = __assign(__assign({}, currentValue[optionName]), { op: (0, exports.fixOperatorValue)(currentValue[optionName].op).map(function (op, idx) {
891
+ fields: __assign(__assign({}, currentValue), (_a = {}, _a[optionName] = __assign(__assign({}, currentValue[optionName]), { op: (0, common_1.fixOperatorValue)(currentValue[optionName].op).map(function (op, idx) {
817
892
  if (idx === index) {
818
893
  return operator;
819
894
  }
@@ -825,14 +900,14 @@ var FormEngineImpl = function (_a) {
825
900
  var handleAddOperator = (0, react_2.useCallback)(function (optionName, currentValue, index) {
826
901
  var _a;
827
902
  setLocalValue === null || setLocalValue === void 0 ? void 0 : setLocalValue({
828
- fields: __assign(__assign({}, currentValue), (_a = {}, _a[optionName] = __assign(__assign({}, currentValue[optionName]), { op: (0, common_1.insertAtIndex)((0, exports.fixOperatorValue)(currentValue[optionName].op), index, null) }), _a)),
903
+ fields: __assign(__assign({}, currentValue), (_a = {}, _a[optionName] = __assign(__assign({}, currentValue[optionName]), { op: (0, common_1.insertAtIndex)((0, common_1.fixOperatorValue)(currentValue[optionName].op), index, null) }), _a)),
829
904
  meta: undefined,
830
905
  });
831
906
  }, []);
832
907
  var handleRemoveOperator = (0, react_2.useCallback)(function (optionName, currentValue, index) {
833
908
  var _a;
834
909
  setLocalValue === null || setLocalValue === void 0 ? void 0 : setLocalValue({
835
- fields: __assign(__assign({}, currentValue), (_a = {}, _a[optionName] = __assign(__assign({}, currentValue[optionName]), { op: (0, exports.fixOperatorValue)(currentValue[optionName].op).filter(function (_op, idx) { return idx !== index; }) }), _a)),
910
+ fields: __assign(__assign({}, currentValue), (_a = {}, _a[optionName] = __assign(__assign({}, currentValue[optionName]), { op: (0, common_1.fixOperatorValue)(currentValue[optionName].op).filter(function (_op, idx) { return idx !== index; }) }), _a)),
836
911
  meta: undefined,
837
912
  });
838
913
  }, []);
@@ -1235,7 +1310,11 @@ var FormEngineImpl = function (_a) {
1235
1310
  // (or below-the-fold) form is never scrolled into view on mount.
1236
1311
  var autoFocusNameRef = (0, react_2.useRef)(undefined);
1237
1312
  (0, react_2.useEffect)(function () {
1238
- if (!autoFocusFirstRequired || !compact || !options) {
1313
+ // Two decisions, one scan. `autoFocusFirstRequired` opens the row AND puts
1314
+ // the caret in it; `expandFirstRequired` only opens it. Setting both keeps
1315
+ // the focusing behaviour, since focusing implies opening.
1316
+ var wantsFocus = !!autoFocusFirstRequired;
1317
+ if ((!wantsFocus && !expandFirstRequired) || !compact || !options) {
1239
1318
  return;
1240
1319
  }
1241
1320
  if (hasAutoFocusedRef.current) {
@@ -1251,8 +1330,14 @@ var FormEngineImpl = function (_a) {
1251
1330
  // clean mount focus rests on the body, so the intended "drop into the first
1252
1331
  // field" still fires. Bailing here leaves `hasAutoFocusedRef` false, so it
1253
1332
  // retries once focus is free.
1333
+ //
1334
+ // Only the FOCUSING variant waits: opening a row moves no caret, so there is
1335
+ // nothing to steal and nothing to wait for. Making the expand-only variant
1336
+ // wait here would make it a no-op in its main use — a sub-form mounted by a
1337
+ // click, where the button that mounted it still holds focus and never gives
1338
+ // it up.
1254
1339
  var active = document.activeElement;
1255
- if (active && active !== document.body) {
1340
+ if (wantsFocus && active && active !== document.body) {
1256
1341
  return;
1257
1342
  }
1258
1343
  var target = (0, readFirst_1.getFirstAttentionOptionName)(orderedNames, function (fieldName) {
@@ -1276,7 +1361,10 @@ var FormEngineImpl = function (_a) {
1276
1361
  hasAutoFocusedRef.current = true;
1277
1362
  if (target) {
1278
1363
  // Set the ref before the state update so CompactRow's focus timer sees it.
1279
- autoFocusNameRef.current = target;
1364
+ // Expand-only leaves it unset, which is what keeps the caret where it is.
1365
+ if (wantsFocus) {
1366
+ autoFocusNameRef.current = target;
1367
+ }
1280
1368
  setExpandedOptions(function (prev) {
1281
1369
  return prev.includes(target) ? prev
1282
1370
  : expandMode === 'multi' ? __spreadArray(__spreadArray([], prev, true), [target], false) : [target];
@@ -1284,6 +1372,7 @@ var FormEngineImpl = function (_a) {
1284
1372
  }
1285
1373
  }, [
1286
1374
  autoFocusFirstRequired,
1375
+ expandFirstRequired,
1287
1376
  compact,
1288
1377
  options,
1289
1378
  availableOptions,
@@ -1360,7 +1449,7 @@ var FormEngineImpl = function (_a) {
1360
1449
  suppressSchemaMessages) {
1361
1450
  var _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
1362
1451
  var type = _a.type, other = __rest(_a, ["type"]);
1363
- var operatorParts = (0, exports.fixOperatorValue)(other.op);
1452
+ var operatorParts = (0, common_1.fixOperatorValue)(other.op);
1364
1453
  // The RENDERER type for this row — which editor mounts. Distinct from the
1365
1454
  // storage type on the value envelope: a `richtext` field stores a string
1366
1455
  // but must render the richtext editor, so `ui_type` wins here.
@@ -1674,8 +1763,40 @@ var FormEngineImpl = function (_a) {
1674
1763
  if (requiredOnly && !((schema === null || schema === void 0 ? void 0 : schema.required) || (schema === null || schema === void 0 ? void 0 : schema.required_groups))) {
1675
1764
  return false;
1676
1765
  }
1677
- return matchesQuery(optionName);
1766
+ // A field that absorbs a sibling also answers for it: searching
1767
+ // "language" has to surface the row that now holds the language control,
1768
+ // or the field becomes unreachable through the filter.
1769
+ if (matchesQuery(optionName))
1770
+ return true;
1771
+ var absorb = schema === null || schema === void 0 ? void 0 : schema.absorb_fields;
1772
+ return !!(absorb === null || absorb === void 0 ? void 0 : absorb.some(function (absorbedName) { return matchesQuery(absorbedName); }));
1678
1773
  };
1774
+ // Fields a sibling absorbs into its own container.
1775
+ //
1776
+ // A field declaring `absorb_fields: ['language']` renders that sibling's
1777
+ // control inside its own row instead of letting it have a row of its own.
1778
+ // The pair this exists for is a code editor and its language: they are one
1779
+ // decision — "what is this code, and in what language" — and reading them
1780
+ // as two unrelated rows one above the other is what the layout used to say.
1781
+ //
1782
+ // Absorbed fields stay in `grouped`, and therefore in the Set / Optional /
1783
+ // Needs-attention counts and in the completion meter. Only the ROW is
1784
+ // skipped. Dropping them from the buckets instead would leave the meter
1785
+ // ("13/18 set", computed from `availableOptions`) disagreeing with the box
1786
+ // badge beside it, which counts row entries.
1787
+ var absorbedBy = {};
1788
+ (0, lodash_1.forEach)(options, function (option, optionName) {
1789
+ var absorb = option === null || option === void 0 ? void 0 : option.absorb_fields;
1790
+ if (!(absorb === null || absorb === void 0 ? void 0 : absorb.length))
1791
+ return;
1792
+ absorb.forEach(function (absorbedName) {
1793
+ // Only absorb a sibling that exists and is not already spoken for; a
1794
+ // schema naming a missing field must not make the field disappear.
1795
+ if ((options === null || options === void 0 ? void 0 : options[absorbedName]) && !absorbedBy[absorbedName]) {
1796
+ absorbedBy[absorbedName] = optionName;
1797
+ }
1798
+ });
1799
+ });
1679
1800
  // Group the options by their raw `group` key, remembering the order groups
1680
1801
  // first appear in the schema. Each row carries a `hidden` flag.
1681
1802
  var groupOrder = [];
@@ -1807,18 +1928,30 @@ var FormEngineImpl = function (_a) {
1807
1928
  // together at the first member's slot and rendered as a connected rail (flat
1808
1929
  // rows — no wrapper — so the value surface applies normally; the rail + nodes
1809
1930
  // are drawn per member). Narrow stacks fall back to flat rows.
1931
+ // The absorbed siblings a host row must render, in schema order. Empty for
1932
+ // every row that absorbs nothing, which is all of them by default.
1933
+ var absorbedNamesFor = function (hostName) {
1934
+ return Object.keys(absorbedBy).filter(function (absorbedName) { return absorbedBy[absorbedName] === hostName; });
1935
+ };
1810
1936
  var renderGroupRows = function (names) {
1811
1937
  var renderRow = function (entry, clustered, clusterFirst, clusterLast) { return ((0, jsx_runtime_1.jsx)(CompactRow_1.CompactRow, { optionName: entry.name, optionField: entry.hidden ?
1812
1938
  {
1813
1939
  type: getOptionSchemaStorageType(options === null || options === void 0 ? void 0 : options[entry.name], isRendererOnly),
1814
1940
  value: undefined,
1815
1941
  }
1816
- : shownOptions[entry.name], hidden: entry.hidden, clustered: clustered, clusterFirst: clusterFirst, clusterLast: clusterLast }, entry.name)); };
1942
+ : shownOptions[entry.name], hidden: entry.hidden, clustered: clustered, clusterFirst: clusterFirst, clusterLast: clusterLast,
1943
+ // The siblings this row renders inside itself instead of leaving
1944
+ // them a row of their own — see `absorbedBy`.
1945
+ absorbedFields: absorbedNamesFor(entry.name) }, entry.name)); };
1817
1946
  // (Clustering runs in narrow mode too now — the "One of the below is
1818
1947
  // required" box wraps the members regardless of width; it no longer relies
1819
1948
  // on a contiguous rail.)
1820
1949
  var emitted = new Set();
1821
1950
  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]; };
1951
+ // An absorbed field keeps its place in the buckets — so the Set /
1952
+ // Optional counts and the completion meter still see it — but its host
1953
+ // renders its control, so it gets no row of its own here.
1954
+ names = names.filter(function (entry) { return !absorbedBy[entry.name]; });
1822
1955
  return names.map(function (entry) {
1823
1956
  var grp = groupOf(entry.name);
1824
1957
  if (!grp)
@@ -1868,7 +2001,14 @@ var FormEngineImpl = function (_a) {
1868
2001
  var boxBg = box.key === 'optional' ?
1869
2002
  (0, colors_1.changeDarkness)((0, colors_1.getMainBackgroundColor)(theme), 0.06)
1870
2003
  : undefined;
1871
- 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,
2004
+ return ((0, jsx_runtime_1.jsx)(compactRowStyles_1.StyledStatusBox, { "$accent": accent, "$bg": boxBg, ref: function (node) {
2005
+ statusBoxRefs.current[box.key] = node;
2006
+ },
2007
+ // Opening arms the reveal; collapsing disarms it, so a
2008
+ // close never scrolls.
2009
+ onCollapseChange: function (collapsed) {
2010
+ return setOpenedStatusBox(collapsed ? undefined : box.key);
2011
+ }, flat: true, minimal: true, collapseButtonProps: { flat: true, minimal: true, size: 'small' }, collapsible: true,
1872
2012
  // The Optional box now holds every not-yet-added field, so
1873
2013
  // it starts COLLAPSED to keep the form focused on what's in
1874
2014
  // use. But a SEARCH must surface matching addable fields —