@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 +28 -5
- package/dist/index.cjs +217 -23
- package/dist/index.d.cts +152 -45
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +152 -45
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +218 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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
|
|
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`,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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(
|
|
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")
|
|
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
|
-
...
|
|
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,
|