@bluprynt/forms-viewer 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Headless React components for rendering and editing JSON-driven dynamic forms. P
5
5
  ## Key Capabilities
6
6
 
7
7
  - **Read-only rendering** — `FormViewer` renders a form from a definition and document using your custom view components, with automatic visibility and validation display.
8
- - **Editable forms** — `FormEditor` renders an editable form with per-field `onChange` handlers, array item manipulation (add, remove, move), and live re-validation on every change.
8
+ - **Editable forms** — `FormEditor` renders an editable form with per-field `onChange` handlers, array and array-of-rows item manipulation (add, remove, move), and live re-validation on every change.
9
9
  - **Section navigation** — `FormSections` enumerates visible top-level sections (including a synthetic root for ungrouped fields) and tracks the active section.
10
10
  - **Validation display** — `FormFieldsValidation` groups field-level errors by field; `FormDocumentValidation` renders document-level errors (schema mismatch, missing `submittedAt`, etc.).
11
11
  - **Headless architecture** — all rendering is delegated to component maps you provide (`ViewerComponentMap`, `EditorComponentMap`), so the package has zero UI opinions.
@@ -220,11 +220,18 @@ Component map for read-only rendering. Each key maps a field type to a React com
220
220
  | `date` | `DateViewProps` | Date field display |
221
221
  | `select` | `SelectViewProps` | Select field display (receives `options`) |
222
222
  | `array` | `ArrayViewProps` | Array field display (receives `children` for items, `itemDef`) |
223
+ | `array_obj?` | `ArrayObjViewProps` | Array-of-rows display. Receives `itemsDef`, the resolved `kind` (`'list'` \| `'table'`) and `children: ReactNode[][]`, indexed `children[row][subField]` and aligned with `itemsDef`. |
223
224
  | `file` | `FileViewProps` | File field display |
224
225
  | `section` | `SectionViewProps` | Section wrapper (receives `children`) |
226
+ | `multiselect?` | `MultiselectViewProps` | Multi-select display. Value is `(string \| number)[]`; receives `options`. |
227
+ | `static?` | `StaticViewProps` | Display-only text block. Receives **only** `field` — no `value`, `errors` or `suggestion`. |
228
+ | `blockchain?` | `BlockchainViewProps` | CAIP address display. Value is a `string`, stored verbatim. |
229
+ | `sustainability?` | `SustainabilityViewProps` | Sustainability references display. Value is `{ address, ccri?, cmc? }`. |
225
230
  | `error?` | `ErrorProps` | Optional inline error renderer |
226
231
 
227
- All view field props extend `BaseViewFieldProps` which provides `field: FieldContentItem` and `errors: FieldValidationError[]`.
232
+ All view field props extend `BaseViewFieldProps` which provides `field: FieldContentItem` and `errors: FieldValidationError[]`. `StaticViewProps` is the exception: it carries only `field: StaticFieldContentItem`, whose `text` is the content and whose `label` is an optional heading.
233
+
234
+ `array_obj`, `multiselect`, `static`, `blockchain` and `sustainability` are **optional** keys, so existing maps keep compiling. A field whose component is not supplied renders nothing rather than throwing.
228
235
 
229
236
  ### EditorComponentMap
230
237
 
@@ -238,11 +245,24 @@ Component map for editable rendering. Extends view props with mutation callbacks
238
245
  | `date` | `DateEditProps` | Date input with `onChange(value)` |
239
246
  | `select` | `SelectEditProps` | Select input with `onChange(value)` and `options` |
240
247
  | `array` | `ArrayEditProps` | Array editor with `onAddItem`, `onRemoveItem(index)`, `onMoveItem(from, to)` |
248
+ | `array_obj?` | `ArrayObjEditProps` | Array-of-rows editor with the same three row handlers. `onAddItem` appends an empty row; the per-cell `onChange` reaches each sub-field component. |
241
249
  | `file` | `FileEditProps` | File input with `onChange(value)` |
242
250
  | `section` | `SectionEditProps` | Section wrapper (receives `children`) |
251
+ | `multiselect?` | `MultiselectEditProps` | Checkbox group. `onChange((string \| number)[] \| undefined)` — send the whole array. |
252
+ | `static?` | `StaticEditProps` | Display-only text block. Identical to `StaticViewProps`; there is nothing to edit. |
253
+ | `blockchain?` | `BlockchainEditProps` | Text input with `onChange(value)`. The CAIP format is never checked by the engine. |
254
+ | `sustainability?` | `SustainabilityEditProps` | Object editor with `onChange(value)` — send the whole `{ address, ccri, cmc }`. Emit `undefined` when every part is blank. |
243
255
  | `error?` | `ErrorProps` | Optional inline error renderer |
244
256
 
245
- All edit field props extend `BaseEditFieldProps` which adds `onChange(value)` to the base view props.
257
+ All edit field props extend `BaseEditFieldProps`, which adds `onChange(value)` plus the optional
258
+ `onAcceptSuggestion()` / `onRejectSuggestion()` handlers to the base view props. `StaticEditProps` does
259
+ not — a static block has no `onChange`, so the same component serves both maps.
260
+
261
+ Every field component — read-only and editable — also receives the field's authoring metadata on
262
+ `field` (`description`, `placeholder`, `answer_guidelines`, `reference_id`) and an optional
263
+ `suggestion` (`{ value, confidence, source }`) when the document carries one for that field. Accepting
264
+ copies the suggested value into the field's value and keeps the suggestion; rejecting removes the
265
+ suggestion and leaves the value alone. See [CHANGES.md](../../CHANGES.md).
246
266
 
247
267
  ## Exported API
248
268
 
@@ -267,10 +287,13 @@ All edit field props extend `BaseEditFieldProps` which adds `onChange(value)` to
267
287
  | `FormValuesEditor` | Class | Fluent editor for reading and writing form values against a definition |
268
288
  | `DocumentError` | Class | Error representing a document-level validation failure |
269
289
  | `FormDefinition` | Type | JSON schema describing form structure, fields, sections, and rules |
270
- | `FormDocument` | Type | JSON document containing user-submitted form values |
290
+ | `FormDocument` | Type | JSON document containing user-submitted form values and optional suggestions |
291
+ | `FieldSuggestion` | Type | A machine-proposed answer: `{ value, confidence, source }` |
292
+ | `FormSuggestions` | Type | Sparse map of suggestions keyed by stringified field id |
271
293
  | `FormValidationResult` | Type | Validation outcome with document-level and field-level errors |
272
- | `FieldType` | Type | Union of supported field types (`string`, `number`, `boolean`, `date`, `select`, `array`, `file`) |
294
+ | `FieldType` | Type | Union of supported field types (`string`, `number`, `boolean`, `date`, `select`, `multiselect`, `array`, `array_obj`, `file`, `static`, `blockchain`, `sustainability`) |
273
295
  | `FileValue` | Type | File field value containing name, MIME type, size, and URL |
296
+ | `SustainabilityValue` | Type | Sustainability field value: `{ address, ccri?, cmc? }` |
274
297
 
275
298
  ## Documentation
276
299
 
package/dist/index.cjs CHANGED
@@ -98,12 +98,15 @@ const getFieldErrors = (fieldErrors, fieldId, itemIndex) => {
98
98
  * Array fields store their per-item schema in `arrayField.item` ({@link ArrayItemDef}).
99
99
  * Components that render individual array items need a standard `FieldContentItem` shape,
100
100
  * so this helper projects the item definition into one, preserving the parent field's `id`
101
- * while adopting the item's `type`, `label`, `description`, `validation`, and `options`.
101
+ * while adopting the item's `type`, `label`, authoring metadata, `validation`, and `options`.
102
+ *
103
+ * A `static` item projects to a `StaticFieldContentItem`, so the return type is
104
+ * the wider {@link AnyFieldContentItem}.
102
105
  *
103
106
  * @param arrayField - The array field definition whose `.item` property describes the item schema.
104
107
  * @param _index - The zero-based position of the item in the array (reserved for future use,
105
108
  * e.g. per-item overrides).
106
- * @returns A `FieldContentItem` that can be passed directly to a typed field component.
109
+ * @returns An `AnyFieldContentItem` that can be passed directly to a typed field component.
107
110
  *
108
111
  * @example
109
112
  * ```ts
@@ -118,16 +121,79 @@ const getFieldErrors = (fieldErrors, fieldId, itemIndex) => {
118
121
  */
119
122
  const getArrayField = (arrayField, _index) => {
120
123
  const itemDef = arrayField.item;
124
+ const metadata = {
125
+ description: itemDef.description,
126
+ placeholder: itemDef.placeholder,
127
+ answer_guidelines: itemDef.answer_guidelines,
128
+ reference_id: itemDef.reference_id
129
+ };
130
+ if (itemDef.type === "static") return {
131
+ id: arrayField.id,
132
+ type: "static",
133
+ text: itemDef.text,
134
+ label: itemDef.label,
135
+ ...metadata
136
+ };
121
137
  return {
122
138
  id: arrayField.id,
123
139
  type: itemDef.type,
124
140
  label: itemDef.label,
125
- description: itemDef.description,
141
+ ...metadata,
126
142
  validation: itemDef.validation,
127
143
  options: itemDef.options
128
144
  };
129
145
  };
130
146
  /**
147
+ * Creates a {@link AnyFieldContentItem} representing one sub-field of an `array_obj` field.
148
+ *
149
+ * Unlike an `array` item, a sub-field carries its own id, so the projection keeps it:
150
+ * it is both the key its value is stored under inside each row and a stable React key.
151
+ *
152
+ * @param subField - The sub-field definition taken from the field's `items` list.
153
+ * @returns An `AnyFieldContentItem` that can be passed directly to a typed field component.
154
+ */
155
+ const getArrayObjField = (subField) => {
156
+ const metadata = {
157
+ description: subField.description,
158
+ placeholder: subField.placeholder,
159
+ answer_guidelines: subField.answer_guidelines,
160
+ reference_id: subField.reference_id
161
+ };
162
+ if (subField.type === "static") return {
163
+ id: subField.id,
164
+ type: "static",
165
+ text: subField.text,
166
+ label: subField.label,
167
+ ...metadata
168
+ };
169
+ return {
170
+ id: subField.id,
171
+ type: subField.type,
172
+ label: subField.label,
173
+ ...metadata,
174
+ validation: subField.validation,
175
+ options: subField.options
176
+ };
177
+ };
178
+ /**
179
+ * Retrieves the validation errors for one sub-field of one row of an `array_obj` field.
180
+ *
181
+ * Errors for an `array_obj` are all stored under the container's id; the row is
182
+ * identified by `itemIndex` and the sub-field by `itemFieldId`.
183
+ *
184
+ * @param fieldErrors - Map from field id to its validation errors.
185
+ * @param fieldId - The numeric id of the `array_obj` field.
186
+ * @param index - Zero-based row index.
187
+ * @param subFieldId - The numeric id of the sub-field.
188
+ * @returns Errors for that cell. Empty array if none.
189
+ */
190
+ const getArrayObjItemErrors = (fieldErrors, fieldId, index, subFieldId) => (fieldErrors.get(fieldId) ?? []).filter((e) => e.itemIndex === index && e.itemFieldId === subFieldId);
191
+ /**
192
+ * Retrieves the row-level validation errors for one row of an `array_obj` field --
193
+ * those carrying an `itemIndex` but no `itemFieldId`, such as a row that is not an object.
194
+ */
195
+ const getArrayObjRowErrors = (fieldErrors, fieldId, index) => (fieldErrors.get(fieldId) ?? []).filter((e) => e.itemIndex === index && e.itemFieldId == null);
196
+ /**
131
197
  * Recursively searches content items for a section with the given id.
132
198
  *
133
199
  * Walks the content tree depth-first, checking sections and their nested
@@ -155,7 +221,13 @@ const findSection = (items, sectionId) => {
155
221
  };
156
222
  //#endregion
157
223
  //#region src/form/form-items.tsx
158
- const FormItems = ({ items, visibilityMap, values, fieldErrors, components, showInlineValidation, renderFieldProps, renderArrayItemProps }) => {
224
+ /** Looks up the component for a value field type. Optional map keys may be absent. */
225
+ const componentFor = (components, type) => components[type];
226
+ /** `select` and `multiselect` components are the only ones that receive injected options. */
227
+ const withOptions = (props, def) => {
228
+ if (def.type === "select" || def.type === "multiselect") props.options = def.options ?? [];
229
+ };
230
+ const FormItems = ({ items, visibilityMap, values, suggestions, fieldErrors, components, showInlineValidation, renderFieldProps, renderArrayItemProps, renderArrayObjItemProps }) => {
159
231
  const SectionComponent = components.section;
160
232
  const ErrorComponent = showInlineValidation ? components.error : void 0;
161
233
  const elements = [];
@@ -167,11 +239,13 @@ const FormItems = ({ items, visibilityMap, values, fieldErrors, components, show
167
239
  items: item.content,
168
240
  visibilityMap,
169
241
  values,
242
+ suggestions,
170
243
  fieldErrors,
171
244
  components,
172
245
  showInlineValidation,
173
246
  renderFieldProps,
174
- renderArrayItemProps
247
+ renderArrayItemProps,
248
+ renderArrayObjItemProps
175
249
  })
176
250
  }, item.id));
177
251
  else if (item.type === "array") {
@@ -180,23 +254,29 @@ const FormItems = ({ items, visibilityMap, values, fieldErrors, components, show
180
254
  const ArrayComponent = components.array;
181
255
  const arrayValue = values[String(item.id)] ?? [];
182
256
  const itemDef = item.item;
183
- const ItemComponent = components[itemDef.type];
184
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArrayComponent, {
257
+ const ItemComponent = componentFor(components, itemDef.type);
258
+ const arrayProps = {
185
259
  field: item,
186
260
  value: values[String(item.id)],
187
261
  itemDef: item.item,
188
262
  errors,
189
- ...modeProps,
263
+ ...modeProps
264
+ };
265
+ const arraySuggestion = suggestions?.[String(item.id)];
266
+ if (arraySuggestion) arrayProps.suggestion = arraySuggestion;
267
+ elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArrayComponent, {
268
+ ...arrayProps,
190
269
  children: ItemComponent ? arrayValue.map((itemValue, index) => {
191
- const itemErrors = getFieldErrors(fieldErrors, item.id, index);
192
270
  const synthetic = getArrayField(item, index);
271
+ if (synthetic.type === "static") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ItemComponent, { field: synthetic }) }, `${item.id}-${index}`);
272
+ const itemErrors = getFieldErrors(fieldErrors, item.id, index);
193
273
  const baseProps = {
194
274
  field: synthetic,
195
275
  value: itemValue,
196
276
  errors: itemErrors,
197
277
  ...renderArrayItemProps?.(item, index) ?? {}
198
278
  };
199
- if (itemDef.type === "select") baseProps.options = itemDef.options ?? [];
279
+ withOptions(baseProps, itemDef);
200
280
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ItemComponent, { ...baseProps }), ErrorComponent && itemErrors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorComponent, {
201
281
  errors: itemErrors,
202
282
  field: synthetic
@@ -207,6 +287,55 @@ const FormItems = ({ items, visibilityMap, values, fieldErrors, components, show
207
287
  errors,
208
288
  field: item
209
289
  }, `${item.id}-error`));
290
+ } else if (item.type === "array_obj") {
291
+ const ArrayObjComponent = components.array_obj;
292
+ if (!ArrayObjComponent) continue;
293
+ const itemsDef = item.items ?? [];
294
+ const rows = values[String(item.id)] ?? [];
295
+ const errors = [...getFieldErrors(fieldErrors, item.id), ...rows.flatMap((_row, index) => getArrayObjRowErrors(fieldErrors, item.id, index))];
296
+ const modeProps = renderFieldProps?.(item) ?? {};
297
+ const arrayProps = {
298
+ field: item,
299
+ value: values[String(item.id)],
300
+ itemsDef,
301
+ kind: item.kind ?? "list",
302
+ errors,
303
+ ...modeProps
304
+ };
305
+ const arraySuggestion = suggestions?.[String(item.id)];
306
+ if (arraySuggestion) arrayProps.suggestion = arraySuggestion;
307
+ const children = rows.map((row, index) => itemsDef.map((subField) => {
308
+ const key = `${item.id}-${index}-${subField.id}`;
309
+ const SubComponent = componentFor(components, subField.type);
310
+ if (!SubComponent) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, {}, key);
311
+ const synthetic = getArrayObjField(subField);
312
+ if (subField.type === "static") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubComponent, { field: synthetic }) }, key);
313
+ const cellErrors = getArrayObjItemErrors(fieldErrors, item.id, index, subField.id);
314
+ const cellModeProps = renderArrayObjItemProps?.(item, index, subField.id) ?? {};
315
+ const cellProps = {
316
+ field: synthetic,
317
+ value: row?.[String(subField.id)],
318
+ errors: cellErrors,
319
+ ...cellModeProps
320
+ };
321
+ withOptions(cellProps, subField);
322
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubComponent, { ...cellProps }), ErrorComponent && cellErrors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorComponent, {
323
+ errors: cellErrors,
324
+ field: synthetic
325
+ })] }, key);
326
+ }));
327
+ elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ArrayObjComponent, {
328
+ ...arrayProps,
329
+ children
330
+ }, item.id));
331
+ if (ErrorComponent && errors.length > 0) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorComponent, {
332
+ errors,
333
+ field: item
334
+ }, `${item.id}-error`));
335
+ } else if (item.type === "static") {
336
+ const StaticComponent = components.static;
337
+ if (!StaticComponent) continue;
338
+ elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StaticComponent, { field: item }, item.id));
210
339
  } else {
211
340
  const errors = getFieldErrors(fieldErrors, item.id);
212
341
  const modeProps = renderFieldProps?.(item) ?? {};
@@ -216,8 +345,11 @@ const FormItems = ({ items, visibilityMap, values, fieldErrors, components, show
216
345
  errors,
217
346
  ...modeProps
218
347
  };
219
- if (item.type === "select") fieldProps.options = item.options ?? [];
220
- const FieldComponent = components[item.type];
348
+ const suggestion = suggestions?.[String(item.id)];
349
+ if (suggestion) fieldProps.suggestion = suggestion;
350
+ withOptions(fieldProps, item);
351
+ const FieldComponent = componentFor(components, item.type);
352
+ if (!FieldComponent) continue;
221
353
  elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldComponent, { ...fieldProps }, item.id));
222
354
  if (ErrorComponent && errors.length > 0) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorComponent, {
223
355
  errors,
@@ -231,18 +363,21 @@ const FormItems = ({ items, visibilityMap, values, fieldErrors, components, show
231
363
  //#endregion
232
364
  //#region src/form/form-content.tsx
233
365
  const FormContent = (props) => {
234
- const { items, visibilityMap, values, fieldErrors, section, showInlineValidation } = props;
366
+ const { items, visibilityMap, values, suggestions, fieldErrors, section, showInlineValidation } = props;
235
367
  const sharedProps = props.mode === "editor" ? {
236
368
  visibilityMap,
237
369
  values,
370
+ suggestions,
238
371
  fieldErrors,
239
372
  components: props.components,
240
373
  showInlineValidation,
241
374
  renderFieldProps: props.renderFieldProps,
242
- renderArrayItemProps: props.renderArrayItemProps
375
+ renderArrayItemProps: props.renderArrayItemProps,
376
+ renderArrayObjItemProps: props.renderArrayObjItemProps
243
377
  } : {
244
378
  visibilityMap,
245
379
  values,
380
+ suggestions,
246
381
  fieldErrors,
247
382
  components: props.components,
248
383
  showInlineValidation
@@ -275,19 +410,21 @@ const resolveDefaultSection = (items, visibilityMap) => {
275
410
  //#region src/form-editor.tsx
276
411
  const FormEditor = ({ components, onChange }) => {
277
412
  const { definition, data, visibilityMap, fieldErrors, section, engine, showInlineValidation, documentErrors } = useFormContext();
278
- const { renderFieldProps, renderArrayItemProps } = useEditorHandlers(engine, data, onChange);
413
+ const { renderFieldProps, renderArrayItemProps, renderArrayObjItemProps } = useEditorHandlers(engine, data, onChange);
279
414
  if (!definition || !data || documentErrors && documentErrors.length > 0) return null;
280
415
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormContent, {
281
416
  mode: "editor",
282
417
  items: definition.content,
283
418
  visibilityMap,
284
419
  values: data.values,
420
+ suggestions: data.suggestions,
285
421
  fieldErrors,
286
422
  components,
287
423
  section,
288
424
  showInlineValidation,
289
425
  renderFieldProps,
290
- renderArrayItemProps
426
+ renderArrayItemProps,
427
+ renderArrayObjItemProps
291
428
  });
292
429
  };
293
430
  const useEditorHandlers = (engine, data, onChange) => {
@@ -323,7 +460,7 @@ const useEditorHandlers = (engine, data, onChange) => {
323
460
  return handler;
324
461
  }, []);
325
462
  const arrayHandlersMap = (0, react.useRef)(/* @__PURE__ */ new Map());
326
- const getArrayHandlers = (0, react.useCallback)((fieldId) => {
463
+ const getArrayHandlers = (0, react.useCallback)((fieldId, newItem) => {
327
464
  const key = String(fieldId);
328
465
  let handlers = arrayHandlersMap.current.get(key);
329
466
  if (!handlers) {
@@ -342,7 +479,7 @@ const useEditorHandlers = (engine, data, onChange) => {
342
479
  };
343
480
  handlers = {
344
481
  onAddItem: () => fire((values) => {
345
- values.push(void 0);
482
+ values.push(newItem());
346
483
  return values;
347
484
  }),
348
485
  onRemoveItem: (index) => fire((values) => {
@@ -359,6 +496,26 @@ const useEditorHandlers = (engine, data, onChange) => {
359
496
  }
360
497
  return handlers;
361
498
  }, []);
499
+ const suggestionHandlersMap = (0, react.useRef)(/* @__PURE__ */ new Map());
500
+ const getSuggestionHandlers = (0, react.useCallback)((fieldId) => {
501
+ const key = String(fieldId);
502
+ let handlers = suggestionHandlersMap.current.get(key);
503
+ if (!handlers) {
504
+ const fire = (next) => {
505
+ const { data, onChange, engine } = stateRef.current;
506
+ if (!data || !engine) return;
507
+ const document = next(data);
508
+ if (document === data) return;
509
+ onChange(document, engine.validate(document));
510
+ };
511
+ handlers = {
512
+ onAcceptSuggestion: () => fire((document) => (0, _bluprynt_forms_core.applySuggestion)(document, fieldId)),
513
+ onRejectSuggestion: () => fire((document) => (0, _bluprynt_forms_core.removeSuggestion)(document, fieldId))
514
+ };
515
+ suggestionHandlersMap.current.set(key, handlers);
516
+ }
517
+ return handlers;
518
+ }, []);
362
519
  const arrayItemOnChangeMap = (0, react.useRef)(/* @__PURE__ */ new Map());
363
520
  const getArrayItemOnChange = (0, react.useCallback)((fieldId, index) => {
364
521
  const mapKey = `${fieldId}-${index}`;
@@ -383,15 +540,51 @@ const useEditorHandlers = (engine, data, onChange) => {
383
540
  }
384
541
  return handler;
385
542
  }, []);
543
+ const arrayObjItemOnChangeMap = (0, react.useRef)(/* @__PURE__ */ new Map());
544
+ const getArrayObjItemOnChange = (0, react.useCallback)((fieldId, index, subFieldId) => {
545
+ const mapKey = `${fieldId}-${index}-${subFieldId}`;
546
+ let handler = arrayObjItemOnChangeMap.current.get(mapKey);
547
+ if (!handler) {
548
+ handler = (newValue) => {
549
+ const fieldKey = String(fieldId);
550
+ const { data, onChange, engine } = stateRef.current;
551
+ if (!data || !engine) return;
552
+ const rows = [...data.values[fieldKey] ?? []];
553
+ const current = rows[index];
554
+ rows[index] = {
555
+ ...typeof current === "object" && current !== null && !Array.isArray(current) ? current : {},
556
+ [String(subFieldId)]: newValue
557
+ };
558
+ const document = {
559
+ ...data,
560
+ values: {
561
+ ...data.values,
562
+ [fieldKey]: rows
563
+ }
564
+ };
565
+ onChange(document, engine.validate(document));
566
+ };
567
+ arrayObjItemOnChangeMap.current.set(mapKey, handler);
568
+ }
569
+ return handler;
570
+ }, []);
386
571
  return {
387
572
  renderFieldProps: (field) => {
388
- if (field.type === "array") return {
573
+ if (field.type === "array" || field.type === "array_obj") {
574
+ const newItem = field.type === "array_obj" ? () => ({}) : () => void 0;
575
+ return {
576
+ onChange: getFieldOnChange(field.id),
577
+ ...getArrayHandlers(field.id, newItem),
578
+ ...getSuggestionHandlers(field.id)
579
+ };
580
+ }
581
+ return {
389
582
  onChange: getFieldOnChange(field.id),
390
- ...getArrayHandlers(field.id)
583
+ ...getSuggestionHandlers(field.id)
391
584
  };
392
- return { onChange: getFieldOnChange(field.id) };
393
585
  },
394
- renderArrayItemProps: (field, index) => ({ onChange: getArrayItemOnChange(field.id, index) })
586
+ renderArrayItemProps: (field, index) => ({ onChange: getArrayItemOnChange(field.id, index) }),
587
+ renderArrayObjItemProps: (field, index, subFieldId) => ({ onChange: getArrayObjItemOnChange(field.id, index, subFieldId) })
395
588
  };
396
589
  };
397
590
  //#endregion
@@ -418,7 +611,7 @@ const FormFieldsValidation = ({ container: ContainerComponent, field: FieldCompo
418
611
  if (groups.length === 0) return null;
419
612
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContainerComponent, { children: groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldComponent, {
420
613
  ...group,
421
- children: group.errors.map((error) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorComponent, { ...error }, `${error.fieldId}-${error.rule}-${error.itemIndex ?? ""}`))
614
+ children: group.errors.map((error) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorComponent, { ...error }, `${error.fieldId}-${error.rule}-${error.itemIndex ?? ""}-${error.itemFieldId ?? ""}`))
422
615
  }, group.field?.id)) });
423
616
  };
424
617
  //#endregion
@@ -472,6 +665,7 @@ const FormViewer = ({ components }) => {
472
665
  items: definition.content,
473
666
  visibilityMap,
474
667
  values: data.values,
668
+ suggestions: data.suggestions,
475
669
  fieldErrors,
476
670
  components,
477
671
  section,