@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 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.type.includes('null');
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 { resetDependentFields } from "./field-helpers";
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.type.includes('null');
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.type.includes('null');
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) {