@limetech/lime-elements 39.17.0 → 39.17.1
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/CHANGELOG.md +9 -0
- package/dist/cjs/limel-form.cjs.entry.js +128 -1
- package/dist/collection/components/form/fields/array-field.js +97 -1
- package/dist/collection/components/form/fields/field-helpers.js +15 -0
- package/dist/collection/components/form/fields/schema-field.js +19 -2
- package/dist/collection/components/form/form.test-schemas.js +118 -0
- package/dist/esm/limel-form.entry.js +128 -1
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-1cb2d781.entry.js → p-e60ffc0a.entry.js} +4 -4
- package/dist/types/components/form/fields/array-field.d.ts +20 -0
- package/dist/types/components/form/fields/field-helpers.d.ts +10 -0
- package/dist/types/components/form/fields/schema-field.d.ts +10 -0
- package/dist/types/components/form/form.test-schemas.d.ts +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [39.17.1](https://github.com/Lundalogik/lime-elements/compare/v39.17.0...v39.17.1) (2026-04-27)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
* **form:** allow clearing custom components nested inside array items ([02910c1](https://github.com/Lundalogik/lime-elements/commit/02910c1b10fca4e5fbea564555dc4db75be82c99))
|
|
7
|
+
* **form:** preserve nested-object leaf data inside array items ([8e29997](https://github.com/Lundalogik/lime-elements/commit/8e299974ed9b3b14dd9eda4313ded2106716225d))
|
|
8
|
+
* **form:** reset obsolete dependent fields inside array items on trigger change ([7e1458e](https://github.com/Lundalogik/lime-elements/commit/7e1458e54467db9f2019a1160a52dad94b7e7a14))
|
|
9
|
+
|
|
1
10
|
## [39.17.0](https://github.com/Lundalogik/lime-elements/compare/v39.16.4...v39.17.0) (2026-04-27)
|
|
2
11
|
|
|
3
12
|
### Features
|
|
@@ -39813,6 +39813,21 @@ function isCustomObjectSchema(schema) {
|
|
|
39813
39813
|
function isAdditionalProperty(schema) {
|
|
39814
39814
|
return schema[ADDITIONAL_PROPERTY_FLAG] === true;
|
|
39815
39815
|
}
|
|
39816
|
+
/**
|
|
39817
|
+
* Check whether a schema permits `null` as a valid value. Used to decide
|
|
39818
|
+
* whether a cleared field value should be preserved as `null` or converted
|
|
39819
|
+
* to `undefined` so the property can be removed from the form data.
|
|
39820
|
+
*
|
|
39821
|
+
* @param schema - the schema to check
|
|
39822
|
+
* @returns true if the schema's type is `'null'` or includes `'null'`
|
|
39823
|
+
*/
|
|
39824
|
+
const schemaAllowsNull = (schema) => {
|
|
39825
|
+
const type = schema === null || schema === void 0 ? void 0 : schema.type;
|
|
39826
|
+
if (!type) {
|
|
39827
|
+
return false;
|
|
39828
|
+
}
|
|
39829
|
+
return Array.isArray(type) ? type.includes('null') : type === 'null';
|
|
39830
|
+
};
|
|
39816
39831
|
|
|
39817
39832
|
/**
|
|
39818
39833
|
* A widget is a concept in react-jsonschema-form (rjsf).
|
|
@@ -40007,7 +40022,7 @@ const BaseSchemaField = defaultFields$2.SchemaField;
|
|
|
40007
40022
|
* @returns whether or not null should be changed to undefined
|
|
40008
40023
|
*/
|
|
40009
40024
|
const shouldChangeToUndefined = (value, schema) => {
|
|
40010
|
-
return value === null && !schema
|
|
40025
|
+
return value === null && !schemaAllowsNull(schema);
|
|
40011
40026
|
};
|
|
40012
40027
|
const hasCustomComponent = (schema) => {
|
|
40013
40028
|
var _a, _b;
|
|
@@ -40094,9 +40109,26 @@ class SchemaField extends React.Component {
|
|
|
40094
40109
|
const { formData, schema } = this.props;
|
|
40095
40110
|
const { rootSchema } = this.props.registry;
|
|
40096
40111
|
this.setState({ modified: true });
|
|
40112
|
+
if (this.isDeepLeafChange(path)) {
|
|
40113
|
+
this.props.onChange(data, path);
|
|
40114
|
+
return;
|
|
40115
|
+
}
|
|
40097
40116
|
const newData = resetDependentFields(formData, data, schema, rootSchema);
|
|
40098
40117
|
this.props.onChange(newData, path);
|
|
40099
40118
|
}
|
|
40119
|
+
/**
|
|
40120
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
40121
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
40122
|
+
* through this SchemaField with a path that is deeper than its own.
|
|
40123
|
+
* The enclosing ArrayField rebuilds the affected item and runs
|
|
40124
|
+
* `resetDependentFields` at the item level (see array-field.ts), so we
|
|
40125
|
+
* must pass the partial leaf data through untouched here.
|
|
40126
|
+
* @param path
|
|
40127
|
+
*/
|
|
40128
|
+
isDeepLeafChange(path) {
|
|
40129
|
+
const { fieldPathId } = this.props;
|
|
40130
|
+
return !!fieldPathId && path.length > fieldPathId.path.length;
|
|
40131
|
+
}
|
|
40100
40132
|
buildCustomComponentProps() {
|
|
40101
40133
|
const { disabled, readonly, name, registry, schema: rawSchema, errorSchema, fieldPathId, } = this.props;
|
|
40102
40134
|
const schema = rawSchema;
|
|
@@ -40208,6 +40240,10 @@ class ArrayField extends React.Component {
|
|
|
40208
40240
|
handleChange(newData, path) {
|
|
40209
40241
|
const { formData: oldData, schema } = this.props;
|
|
40210
40242
|
const { rootSchema } = this.props.registry;
|
|
40243
|
+
if (this.isDeepLeafChange(path)) {
|
|
40244
|
+
this.handleDeepLeafChange(newData, path);
|
|
40245
|
+
return;
|
|
40246
|
+
}
|
|
40211
40247
|
// This case handles when the first list item is added. When there are no
|
|
40212
40248
|
// items we get undefined instead of []
|
|
40213
40249
|
if (!oldData) {
|
|
@@ -40237,11 +40273,102 @@ class ArrayField extends React.Component {
|
|
|
40237
40273
|
}
|
|
40238
40274
|
this.props.onChange(newData, path);
|
|
40239
40275
|
}
|
|
40276
|
+
/**
|
|
40277
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
40278
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
40279
|
+
* through here with the leaf value (not the full array) and a path that
|
|
40280
|
+
* is deeper than this field's own path.
|
|
40281
|
+
* @param path
|
|
40282
|
+
*/
|
|
40283
|
+
isDeepLeafChange(path) {
|
|
40284
|
+
const { formData, fieldPathId } = this.props;
|
|
40285
|
+
return (Array.isArray(formData) &&
|
|
40286
|
+
!!fieldPathId &&
|
|
40287
|
+
path.length > fieldPathId.path.length);
|
|
40288
|
+
}
|
|
40289
|
+
/**
|
|
40290
|
+
* Rebuild the affected item locally so we can (a) restore `undefined`
|
|
40291
|
+
* when the leaf schema does not allow `null` — the built-in handler
|
|
40292
|
+
* coerces `undefined` to `null`, which breaks field clearing for custom
|
|
40293
|
+
* components — and (b) still run `resetDependentFields` at the item
|
|
40294
|
+
* level so sibling fields that became obsolete for the new data are
|
|
40295
|
+
* cleared.
|
|
40296
|
+
* @param newData
|
|
40297
|
+
* @param path
|
|
40298
|
+
*/
|
|
40299
|
+
handleDeepLeafChange(newData, path) {
|
|
40300
|
+
const { formData: oldData, fieldPathId } = this.props;
|
|
40301
|
+
const [indexSegment, ...pathInItem] = path.slice(fieldPathId.path.length);
|
|
40302
|
+
const index = Number(indexSegment);
|
|
40303
|
+
const newArray = [...oldData];
|
|
40304
|
+
newArray[index] = this.buildResetItem(newData, index, pathInItem);
|
|
40305
|
+
this.props.onChange(newArray, fieldPathId.path);
|
|
40306
|
+
}
|
|
40307
|
+
buildResetItem(newData, index, pathInItem) {
|
|
40308
|
+
const { formData: oldData, schema } = this.props;
|
|
40309
|
+
const { rootSchema } = this.props.registry;
|
|
40310
|
+
const itemSchema = (Array.isArray(schema.items) ? schema.items[index] : schema.items);
|
|
40311
|
+
const leafSchema = getSchemaAtPath(itemSchema, pathInItem);
|
|
40312
|
+
const value = newData === null && !schemaAllowsNull(leafSchema)
|
|
40313
|
+
? undefined
|
|
40314
|
+
: newData;
|
|
40315
|
+
const oldItem = oldData[index];
|
|
40316
|
+
const newItem = setValueAtPath(oldItem, pathInItem, value);
|
|
40317
|
+
return resetDependentFields(oldItem, newItem, itemSchema, rootSchema);
|
|
40318
|
+
}
|
|
40240
40319
|
render() {
|
|
40241
40320
|
const arrayProps = Object.assign(Object.assign({}, this.props), { onChange: this.handleChange });
|
|
40242
40321
|
return React.createElement('div', { ref: this.setWrapper }, React.createElement(BaseArrayField, arrayProps));
|
|
40243
40322
|
}
|
|
40244
40323
|
}
|
|
40324
|
+
function getSchemaAtPath(schema, pathSegments) {
|
|
40325
|
+
let current = schema;
|
|
40326
|
+
for (const segment of pathSegments) {
|
|
40327
|
+
if (!current) {
|
|
40328
|
+
return;
|
|
40329
|
+
}
|
|
40330
|
+
current = resolveSchemaSegment(current, segment);
|
|
40331
|
+
}
|
|
40332
|
+
return current;
|
|
40333
|
+
}
|
|
40334
|
+
function resolveSchemaSegment(schema, segment) {
|
|
40335
|
+
var _a;
|
|
40336
|
+
if (typeof segment === 'number') {
|
|
40337
|
+
const items = schema.items;
|
|
40338
|
+
return Array.isArray(items) ? items[segment] : items;
|
|
40339
|
+
}
|
|
40340
|
+
return (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[segment];
|
|
40341
|
+
}
|
|
40342
|
+
/**
|
|
40343
|
+
* Return a copy of `target` with `value` written at the location described
|
|
40344
|
+
* by `pathSegments`, creating intermediate containers as needed. Numeric
|
|
40345
|
+
* segments address arrays; string segments address object keys. `target`
|
|
40346
|
+
* is not mutated.
|
|
40347
|
+
*
|
|
40348
|
+
* @param target
|
|
40349
|
+
* @param pathSegments
|
|
40350
|
+
* @param value
|
|
40351
|
+
* @example
|
|
40352
|
+
* setValueAtPath({ a: { b: 1 } }, ['a', 'b'], 2); // → { a: { b: 2 } }
|
|
40353
|
+
* setValueAtPath([{ x: 1 }], [0, 'x'], 9); // → [{ x: 9 }]
|
|
40354
|
+
*/
|
|
40355
|
+
function setValueAtPath(target, pathSegments, value) {
|
|
40356
|
+
if (pathSegments.length === 0) {
|
|
40357
|
+
return value;
|
|
40358
|
+
}
|
|
40359
|
+
const clone = cloneContainer(target, pathSegments[0]);
|
|
40360
|
+
set(clone, pathSegments, value);
|
|
40361
|
+
return clone;
|
|
40362
|
+
}
|
|
40363
|
+
function cloneContainer(target, firstSegment) {
|
|
40364
|
+
if (Array.isArray(target) || isPlainObject(target)) {
|
|
40365
|
+
return cloneDeep.cloneDeep(target);
|
|
40366
|
+
}
|
|
40367
|
+
if (typeof firstSegment === 'number') {
|
|
40368
|
+
return [];
|
|
40369
|
+
}
|
|
40370
|
+
return {};
|
|
40371
|
+
}
|
|
40245
40372
|
|
|
40246
40373
|
class CodeEditor extends React.Component {
|
|
40247
40374
|
constructor(props) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { getDefaultRegistry } from "@rjsf/core";
|
|
3
|
-
import {
|
|
3
|
+
import { cloneDeep, isPlainObject, set } from "lodash-es";
|
|
4
|
+
import { resetDependentFields, schemaAllowsNull } from "./field-helpers";
|
|
4
5
|
import { ARRAY_REORDER_EVENT } from "../templates/array-field";
|
|
5
6
|
const { fields: defaultFields } = getDefaultRegistry();
|
|
6
7
|
const BaseArrayField = defaultFields.ArrayField;
|
|
@@ -69,6 +70,10 @@ export class ArrayField extends React.Component {
|
|
|
69
70
|
handleChange(newData, path) {
|
|
70
71
|
const { formData: oldData, schema } = this.props;
|
|
71
72
|
const { rootSchema } = this.props.registry;
|
|
73
|
+
if (this.isDeepLeafChange(path)) {
|
|
74
|
+
this.handleDeepLeafChange(newData, path);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
72
77
|
// This case handles when the first list item is added. When there are no
|
|
73
78
|
// items we get undefined instead of []
|
|
74
79
|
if (!oldData) {
|
|
@@ -98,8 +103,99 @@ export class ArrayField extends React.Component {
|
|
|
98
103
|
}
|
|
99
104
|
this.props.onChange(newData, path);
|
|
100
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
108
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
109
|
+
* through here with the leaf value (not the full array) and a path that
|
|
110
|
+
* is deeper than this field's own path.
|
|
111
|
+
* @param path
|
|
112
|
+
*/
|
|
113
|
+
isDeepLeafChange(path) {
|
|
114
|
+
const { formData, fieldPathId } = this.props;
|
|
115
|
+
return (Array.isArray(formData) &&
|
|
116
|
+
!!fieldPathId &&
|
|
117
|
+
path.length > fieldPathId.path.length);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Rebuild the affected item locally so we can (a) restore `undefined`
|
|
121
|
+
* when the leaf schema does not allow `null` — the built-in handler
|
|
122
|
+
* coerces `undefined` to `null`, which breaks field clearing for custom
|
|
123
|
+
* components — and (b) still run `resetDependentFields` at the item
|
|
124
|
+
* level so sibling fields that became obsolete for the new data are
|
|
125
|
+
* cleared.
|
|
126
|
+
* @param newData
|
|
127
|
+
* @param path
|
|
128
|
+
*/
|
|
129
|
+
handleDeepLeafChange(newData, path) {
|
|
130
|
+
const { formData: oldData, fieldPathId } = this.props;
|
|
131
|
+
const [indexSegment, ...pathInItem] = path.slice(fieldPathId.path.length);
|
|
132
|
+
const index = Number(indexSegment);
|
|
133
|
+
const newArray = [...oldData];
|
|
134
|
+
newArray[index] = this.buildResetItem(newData, index, pathInItem);
|
|
135
|
+
this.props.onChange(newArray, fieldPathId.path);
|
|
136
|
+
}
|
|
137
|
+
buildResetItem(newData, index, pathInItem) {
|
|
138
|
+
const { formData: oldData, schema } = this.props;
|
|
139
|
+
const { rootSchema } = this.props.registry;
|
|
140
|
+
const itemSchema = (Array.isArray(schema.items) ? schema.items[index] : schema.items);
|
|
141
|
+
const leafSchema = getSchemaAtPath(itemSchema, pathInItem);
|
|
142
|
+
const value = newData === null && !schemaAllowsNull(leafSchema)
|
|
143
|
+
? undefined
|
|
144
|
+
: newData;
|
|
145
|
+
const oldItem = oldData[index];
|
|
146
|
+
const newItem = setValueAtPath(oldItem, pathInItem, value);
|
|
147
|
+
return resetDependentFields(oldItem, newItem, itemSchema, rootSchema);
|
|
148
|
+
}
|
|
101
149
|
render() {
|
|
102
150
|
const arrayProps = Object.assign(Object.assign({}, this.props), { onChange: this.handleChange });
|
|
103
151
|
return React.createElement('div', { ref: this.setWrapper }, React.createElement(BaseArrayField, arrayProps));
|
|
104
152
|
}
|
|
105
153
|
}
|
|
154
|
+
function getSchemaAtPath(schema, pathSegments) {
|
|
155
|
+
let current = schema;
|
|
156
|
+
for (const segment of pathSegments) {
|
|
157
|
+
if (!current) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
current = resolveSchemaSegment(current, segment);
|
|
161
|
+
}
|
|
162
|
+
return current;
|
|
163
|
+
}
|
|
164
|
+
function resolveSchemaSegment(schema, segment) {
|
|
165
|
+
var _a;
|
|
166
|
+
if (typeof segment === 'number') {
|
|
167
|
+
const items = schema.items;
|
|
168
|
+
return Array.isArray(items) ? items[segment] : items;
|
|
169
|
+
}
|
|
170
|
+
return (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[segment];
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Return a copy of `target` with `value` written at the location described
|
|
174
|
+
* by `pathSegments`, creating intermediate containers as needed. Numeric
|
|
175
|
+
* segments address arrays; string segments address object keys. `target`
|
|
176
|
+
* is not mutated.
|
|
177
|
+
*
|
|
178
|
+
* @param target
|
|
179
|
+
* @param pathSegments
|
|
180
|
+
* @param value
|
|
181
|
+
* @example
|
|
182
|
+
* setValueAtPath({ a: { b: 1 } }, ['a', 'b'], 2); // → { a: { b: 2 } }
|
|
183
|
+
* setValueAtPath([{ x: 1 }], [0, 'x'], 9); // → [{ x: 9 }]
|
|
184
|
+
*/
|
|
185
|
+
function setValueAtPath(target, pathSegments, value) {
|
|
186
|
+
if (pathSegments.length === 0) {
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
const clone = cloneContainer(target, pathSegments[0]);
|
|
190
|
+
set(clone, pathSegments, value);
|
|
191
|
+
return clone;
|
|
192
|
+
}
|
|
193
|
+
function cloneContainer(target, firstSegment) {
|
|
194
|
+
if (Array.isArray(target) || isPlainObject(target)) {
|
|
195
|
+
return cloneDeep(target);
|
|
196
|
+
}
|
|
197
|
+
if (typeof firstSegment === 'number') {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
@@ -69,3 +69,18 @@ export function isCustomObjectSchema(schema) {
|
|
|
69
69
|
function isAdditionalProperty(schema) {
|
|
70
70
|
return schema[ADDITIONAL_PROPERTY_FLAG] === true;
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Check whether a schema permits `null` as a valid value. Used to decide
|
|
74
|
+
* whether a cleared field value should be preserved as `null` or converted
|
|
75
|
+
* to `undefined` so the property can be removed from the form data.
|
|
76
|
+
*
|
|
77
|
+
* @param schema - the schema to check
|
|
78
|
+
* @returns true if the schema's type is `'null'` or includes `'null'`
|
|
79
|
+
*/
|
|
80
|
+
export const schemaAllowsNull = (schema) => {
|
|
81
|
+
const type = schema === null || schema === void 0 ? void 0 : schema.type;
|
|
82
|
+
if (!type) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return Array.isArray(type) ? type.includes('null') : type === 'null';
|
|
86
|
+
};
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { getDefaultRegistry } from "@rjsf/core";
|
|
3
3
|
import { isEmpty } from "lodash-es";
|
|
4
4
|
import { hasValue, isFieldInvalid, isFieldRequired, getErrorText, } from "../validation-display";
|
|
5
|
-
import { resetDependentFields } from "./field-helpers";
|
|
5
|
+
import { resetDependentFields, schemaAllowsNull } from "./field-helpers";
|
|
6
6
|
import { FieldTemplate } from "../templates";
|
|
7
7
|
import { getHelpComponent } from "../help";
|
|
8
8
|
import { TimePicker } from "../widgets/time-picker";
|
|
@@ -34,7 +34,7 @@ const BaseSchemaField = defaultFields.SchemaField;
|
|
|
34
34
|
* @returns whether or not null should be changed to undefined
|
|
35
35
|
*/
|
|
36
36
|
const shouldChangeToUndefined = (value, schema) => {
|
|
37
|
-
return value === null && !schema
|
|
37
|
+
return value === null && !schemaAllowsNull(schema);
|
|
38
38
|
};
|
|
39
39
|
const hasCustomComponent = (schema) => {
|
|
40
40
|
var _a, _b;
|
|
@@ -121,9 +121,26 @@ export class SchemaField extends React.Component {
|
|
|
121
121
|
const { formData, schema } = this.props;
|
|
122
122
|
const { rootSchema } = this.props.registry;
|
|
123
123
|
this.setState({ modified: true });
|
|
124
|
+
if (this.isDeepLeafChange(path)) {
|
|
125
|
+
this.props.onChange(data, path);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
124
128
|
const newData = resetDependentFields(formData, data, schema, rootSchema);
|
|
125
129
|
this.props.onChange(newData, path);
|
|
126
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
133
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
134
|
+
* through this SchemaField with a path that is deeper than its own.
|
|
135
|
+
* The enclosing ArrayField rebuilds the affected item and runs
|
|
136
|
+
* `resetDependentFields` at the item level (see array-field.ts), so we
|
|
137
|
+
* must pass the partial leaf data through untouched here.
|
|
138
|
+
* @param path
|
|
139
|
+
*/
|
|
140
|
+
isDeepLeafChange(path) {
|
|
141
|
+
const { fieldPathId } = this.props;
|
|
142
|
+
return !!fieldPathId && path.length > fieldPathId.path.length;
|
|
143
|
+
}
|
|
127
144
|
buildCustomComponentProps() {
|
|
128
145
|
const { disabled, readonly, name, registry, schema: rawSchema, errorSchema, fieldPathId, } = this.props;
|
|
129
146
|
const schema = rawSchema;
|
|
@@ -306,3 +306,121 @@ export const nestedArrayObjectSchema = {
|
|
|
306
306
|
},
|
|
307
307
|
},
|
|
308
308
|
};
|
|
309
|
+
export const topLevelStringCustomComponentSchema = {
|
|
310
|
+
type: 'object',
|
|
311
|
+
properties: {
|
|
312
|
+
icon: {
|
|
313
|
+
type: 'string',
|
|
314
|
+
title: 'Icon',
|
|
315
|
+
lime: { component: { name: 'limel-input-field' } },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
export const arrayItemWithDependenciesSchema = {
|
|
320
|
+
type: 'object',
|
|
321
|
+
properties: {
|
|
322
|
+
entries: {
|
|
323
|
+
type: 'array',
|
|
324
|
+
title: 'Entries',
|
|
325
|
+
items: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {
|
|
328
|
+
useA: {
|
|
329
|
+
type: 'boolean',
|
|
330
|
+
title: 'Use A',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
required: ['useA'],
|
|
334
|
+
dependencies: {
|
|
335
|
+
useA: {
|
|
336
|
+
oneOf: [
|
|
337
|
+
{
|
|
338
|
+
properties: {
|
|
339
|
+
useA: { const: true },
|
|
340
|
+
valueA: {
|
|
341
|
+
type: 'string',
|
|
342
|
+
title: 'Value A',
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
properties: {
|
|
348
|
+
useA: { const: false },
|
|
349
|
+
valueB: {
|
|
350
|
+
type: 'string',
|
|
351
|
+
title: 'Value B',
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
export const arrayItemWithDependenciesAndCustomConfigSchema = {
|
|
363
|
+
type: 'object',
|
|
364
|
+
properties: {
|
|
365
|
+
entries: {
|
|
366
|
+
type: 'array',
|
|
367
|
+
title: 'Entries',
|
|
368
|
+
items: {
|
|
369
|
+
type: 'object',
|
|
370
|
+
properties: {
|
|
371
|
+
useA: {
|
|
372
|
+
type: 'boolean',
|
|
373
|
+
title: 'Use A',
|
|
374
|
+
},
|
|
375
|
+
config: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
title: 'Config',
|
|
378
|
+
additionalProperties: { type: 'string' },
|
|
379
|
+
lime: {
|
|
380
|
+
component: { name: 'limel-input-field' },
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
required: ['useA'],
|
|
385
|
+
dependencies: {
|
|
386
|
+
useA: {
|
|
387
|
+
oneOf: [
|
|
388
|
+
{
|
|
389
|
+
properties: {
|
|
390
|
+
useA: { const: true },
|
|
391
|
+
valueA: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
title: 'Value A',
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
properties: { useA: { const: false } },
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
additionalProperties: true,
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
export const nestedStringCustomComponentSchema = {
|
|
409
|
+
type: 'object',
|
|
410
|
+
properties: {
|
|
411
|
+
views: {
|
|
412
|
+
type: 'array',
|
|
413
|
+
title: 'Views',
|
|
414
|
+
items: {
|
|
415
|
+
type: 'object',
|
|
416
|
+
properties: {
|
|
417
|
+
icon: {
|
|
418
|
+
type: 'string',
|
|
419
|
+
title: 'Icon',
|
|
420
|
+
lime: { component: { name: 'limel-input-field' } },
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
};
|
|
@@ -39811,6 +39811,21 @@ function isCustomObjectSchema(schema) {
|
|
|
39811
39811
|
function isAdditionalProperty(schema) {
|
|
39812
39812
|
return schema[ADDITIONAL_PROPERTY_FLAG] === true;
|
|
39813
39813
|
}
|
|
39814
|
+
/**
|
|
39815
|
+
* Check whether a schema permits `null` as a valid value. Used to decide
|
|
39816
|
+
* whether a cleared field value should be preserved as `null` or converted
|
|
39817
|
+
* to `undefined` so the property can be removed from the form data.
|
|
39818
|
+
*
|
|
39819
|
+
* @param schema - the schema to check
|
|
39820
|
+
* @returns true if the schema's type is `'null'` or includes `'null'`
|
|
39821
|
+
*/
|
|
39822
|
+
const schemaAllowsNull = (schema) => {
|
|
39823
|
+
const type = schema === null || schema === void 0 ? void 0 : schema.type;
|
|
39824
|
+
if (!type) {
|
|
39825
|
+
return false;
|
|
39826
|
+
}
|
|
39827
|
+
return Array.isArray(type) ? type.includes('null') : type === 'null';
|
|
39828
|
+
};
|
|
39814
39829
|
|
|
39815
39830
|
/**
|
|
39816
39831
|
* A widget is a concept in react-jsonschema-form (rjsf).
|
|
@@ -40005,7 +40020,7 @@ const BaseSchemaField = defaultFields$2.SchemaField;
|
|
|
40005
40020
|
* @returns whether or not null should be changed to undefined
|
|
40006
40021
|
*/
|
|
40007
40022
|
const shouldChangeToUndefined = (value, schema) => {
|
|
40008
|
-
return value === null && !schema
|
|
40023
|
+
return value === null && !schemaAllowsNull(schema);
|
|
40009
40024
|
};
|
|
40010
40025
|
const hasCustomComponent = (schema) => {
|
|
40011
40026
|
var _a, _b;
|
|
@@ -40092,9 +40107,26 @@ class SchemaField extends React.Component {
|
|
|
40092
40107
|
const { formData, schema } = this.props;
|
|
40093
40108
|
const { rootSchema } = this.props.registry;
|
|
40094
40109
|
this.setState({ modified: true });
|
|
40110
|
+
if (this.isDeepLeafChange(path)) {
|
|
40111
|
+
this.props.onChange(data, path);
|
|
40112
|
+
return;
|
|
40113
|
+
}
|
|
40095
40114
|
const newData = resetDependentFields(formData, data, schema, rootSchema);
|
|
40096
40115
|
this.props.onChange(newData, path);
|
|
40097
40116
|
}
|
|
40117
|
+
/**
|
|
40118
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
40119
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
40120
|
+
* through this SchemaField with a path that is deeper than its own.
|
|
40121
|
+
* The enclosing ArrayField rebuilds the affected item and runs
|
|
40122
|
+
* `resetDependentFields` at the item level (see array-field.ts), so we
|
|
40123
|
+
* must pass the partial leaf data through untouched here.
|
|
40124
|
+
* @param path
|
|
40125
|
+
*/
|
|
40126
|
+
isDeepLeafChange(path) {
|
|
40127
|
+
const { fieldPathId } = this.props;
|
|
40128
|
+
return !!fieldPathId && path.length > fieldPathId.path.length;
|
|
40129
|
+
}
|
|
40098
40130
|
buildCustomComponentProps() {
|
|
40099
40131
|
const { disabled, readonly, name, registry, schema: rawSchema, errorSchema, fieldPathId, } = this.props;
|
|
40100
40132
|
const schema = rawSchema;
|
|
@@ -40206,6 +40238,10 @@ class ArrayField extends React.Component {
|
|
|
40206
40238
|
handleChange(newData, path) {
|
|
40207
40239
|
const { formData: oldData, schema } = this.props;
|
|
40208
40240
|
const { rootSchema } = this.props.registry;
|
|
40241
|
+
if (this.isDeepLeafChange(path)) {
|
|
40242
|
+
this.handleDeepLeafChange(newData, path);
|
|
40243
|
+
return;
|
|
40244
|
+
}
|
|
40209
40245
|
// This case handles when the first list item is added. When there are no
|
|
40210
40246
|
// items we get undefined instead of []
|
|
40211
40247
|
if (!oldData) {
|
|
@@ -40235,11 +40271,102 @@ class ArrayField extends React.Component {
|
|
|
40235
40271
|
}
|
|
40236
40272
|
this.props.onChange(newData, path);
|
|
40237
40273
|
}
|
|
40274
|
+
/**
|
|
40275
|
+
* RJSF v6's built-in ArrayField shares one onChange handler across all
|
|
40276
|
+
* descendants, so changes to leaves deep inside an array item bubble
|
|
40277
|
+
* through here with the leaf value (not the full array) and a path that
|
|
40278
|
+
* is deeper than this field's own path.
|
|
40279
|
+
* @param path
|
|
40280
|
+
*/
|
|
40281
|
+
isDeepLeafChange(path) {
|
|
40282
|
+
const { formData, fieldPathId } = this.props;
|
|
40283
|
+
return (Array.isArray(formData) &&
|
|
40284
|
+
!!fieldPathId &&
|
|
40285
|
+
path.length > fieldPathId.path.length);
|
|
40286
|
+
}
|
|
40287
|
+
/**
|
|
40288
|
+
* Rebuild the affected item locally so we can (a) restore `undefined`
|
|
40289
|
+
* when the leaf schema does not allow `null` — the built-in handler
|
|
40290
|
+
* coerces `undefined` to `null`, which breaks field clearing for custom
|
|
40291
|
+
* components — and (b) still run `resetDependentFields` at the item
|
|
40292
|
+
* level so sibling fields that became obsolete for the new data are
|
|
40293
|
+
* cleared.
|
|
40294
|
+
* @param newData
|
|
40295
|
+
* @param path
|
|
40296
|
+
*/
|
|
40297
|
+
handleDeepLeafChange(newData, path) {
|
|
40298
|
+
const { formData: oldData, fieldPathId } = this.props;
|
|
40299
|
+
const [indexSegment, ...pathInItem] = path.slice(fieldPathId.path.length);
|
|
40300
|
+
const index = Number(indexSegment);
|
|
40301
|
+
const newArray = [...oldData];
|
|
40302
|
+
newArray[index] = this.buildResetItem(newData, index, pathInItem);
|
|
40303
|
+
this.props.onChange(newArray, fieldPathId.path);
|
|
40304
|
+
}
|
|
40305
|
+
buildResetItem(newData, index, pathInItem) {
|
|
40306
|
+
const { formData: oldData, schema } = this.props;
|
|
40307
|
+
const { rootSchema } = this.props.registry;
|
|
40308
|
+
const itemSchema = (Array.isArray(schema.items) ? schema.items[index] : schema.items);
|
|
40309
|
+
const leafSchema = getSchemaAtPath(itemSchema, pathInItem);
|
|
40310
|
+
const value = newData === null && !schemaAllowsNull(leafSchema)
|
|
40311
|
+
? undefined
|
|
40312
|
+
: newData;
|
|
40313
|
+
const oldItem = oldData[index];
|
|
40314
|
+
const newItem = setValueAtPath(oldItem, pathInItem, value);
|
|
40315
|
+
return resetDependentFields(oldItem, newItem, itemSchema, rootSchema);
|
|
40316
|
+
}
|
|
40238
40317
|
render() {
|
|
40239
40318
|
const arrayProps = Object.assign(Object.assign({}, this.props), { onChange: this.handleChange });
|
|
40240
40319
|
return React.createElement('div', { ref: this.setWrapper }, React.createElement(BaseArrayField, arrayProps));
|
|
40241
40320
|
}
|
|
40242
40321
|
}
|
|
40322
|
+
function getSchemaAtPath(schema, pathSegments) {
|
|
40323
|
+
let current = schema;
|
|
40324
|
+
for (const segment of pathSegments) {
|
|
40325
|
+
if (!current) {
|
|
40326
|
+
return;
|
|
40327
|
+
}
|
|
40328
|
+
current = resolveSchemaSegment(current, segment);
|
|
40329
|
+
}
|
|
40330
|
+
return current;
|
|
40331
|
+
}
|
|
40332
|
+
function resolveSchemaSegment(schema, segment) {
|
|
40333
|
+
var _a;
|
|
40334
|
+
if (typeof segment === 'number') {
|
|
40335
|
+
const items = schema.items;
|
|
40336
|
+
return Array.isArray(items) ? items[segment] : items;
|
|
40337
|
+
}
|
|
40338
|
+
return (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[segment];
|
|
40339
|
+
}
|
|
40340
|
+
/**
|
|
40341
|
+
* Return a copy of `target` with `value` written at the location described
|
|
40342
|
+
* by `pathSegments`, creating intermediate containers as needed. Numeric
|
|
40343
|
+
* segments address arrays; string segments address object keys. `target`
|
|
40344
|
+
* is not mutated.
|
|
40345
|
+
*
|
|
40346
|
+
* @param target
|
|
40347
|
+
* @param pathSegments
|
|
40348
|
+
* @param value
|
|
40349
|
+
* @example
|
|
40350
|
+
* setValueAtPath({ a: { b: 1 } }, ['a', 'b'], 2); // → { a: { b: 2 } }
|
|
40351
|
+
* setValueAtPath([{ x: 1 }], [0, 'x'], 9); // → [{ x: 9 }]
|
|
40352
|
+
*/
|
|
40353
|
+
function setValueAtPath(target, pathSegments, value) {
|
|
40354
|
+
if (pathSegments.length === 0) {
|
|
40355
|
+
return value;
|
|
40356
|
+
}
|
|
40357
|
+
const clone = cloneContainer(target, pathSegments[0]);
|
|
40358
|
+
set(clone, pathSegments, value);
|
|
40359
|
+
return clone;
|
|
40360
|
+
}
|
|
40361
|
+
function cloneContainer(target, firstSegment) {
|
|
40362
|
+
if (Array.isArray(target) || isPlainObject(target)) {
|
|
40363
|
+
return cloneDeep(target);
|
|
40364
|
+
}
|
|
40365
|
+
if (typeof firstSegment === 'number') {
|
|
40366
|
+
return [];
|
|
40367
|
+
}
|
|
40368
|
+
return {};
|
|
40369
|
+
}
|
|
40243
40370
|
|
|
40244
40371
|
class CodeEditor extends React.Component {
|
|
40245
40372
|
constructor(props) {
|