@rjsf/core 6.7.0 → 6.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rjsf/core",
3
- "version": "6.7.0",
3
+ "version": "6.7.1",
4
4
  "description": "A simple React component capable of building HTML forms out of a JSON schema.",
5
5
  "lint-staged": {
6
6
  "{src,test}/**/*.{ts,tsx,js,jsx}": [
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": ">=18",
53
- "@rjsf/utils": "^6.7.0"
53
+ "@rjsf/utils": "^6.7.1"
54
54
  },
55
55
  "dependencies": {
56
56
  "lodash": "^4.18.1",
@@ -67,9 +67,9 @@
67
67
  "ajv": "^8.20.0",
68
68
  "atob": "^2.1.2",
69
69
  "react-portal": "^4.3.0",
70
- "@rjsf/snapshot-tests": "^6.7.0",
71
- "@rjsf/validator-ajv8": "^6.7.0",
72
- "@rjsf/utils": "^6.7.0"
70
+ "@rjsf/snapshot-tests": "^6.7.1",
71
+ "@rjsf/utils": "^6.7.1",
72
+ "@rjsf/validator-ajv8": "^6.7.1"
73
73
  },
74
74
  "directories": {
75
75
  "test": "test"
@@ -7,18 +7,10 @@ import type {
7
7
  RJSFSchema,
8
8
  StrictRJSFSchema,
9
9
  } from '@rjsf/utils';
10
- import { asNumber } from '@rjsf/utils';
10
+ import { asNumber, getDecimalSeparator, getUiOptions, optionsList } from '@rjsf/utils';
11
11
 
12
- // Matches a string that ends in a . character, optionally followed by a sequence of
13
- // digits followed by any number of 0 characters up until the end of the line.
14
- // Ensuring that there is at least one prefixed character is important so that
15
- // you don't incorrectly match against "0".
12
+ // Static matchers for standard '.' separator used during normalization inside handleChange
16
13
  const trailingCharMatcherWithPrefix = /\.([0-9]*0)*$/;
17
-
18
- // This is used for trimming the trailing 0 and . characters without affecting
19
- // the rest of the string. Its possible to use one RegEx with groups for this
20
- // functionality, but it is fairly complex compared to simply defining two
21
- // different matchers.
22
14
  const trailingCharMatcher = /[0.]0*$/;
23
15
 
24
16
  /**
@@ -45,6 +37,9 @@ function NumberField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
45
37
  const [lastValue, setLastValue] = useState(initialValue);
46
38
  const { StringField } = registry.fields;
47
39
 
40
+ const separator = getDecimalSeparator();
41
+ const escapedSeparator = separator === '.' ? '\\.' : separator;
42
+
48
43
  let value = formData;
49
44
 
50
45
  /** Handle the change from the `StringField` to properly convert to a number
@@ -56,9 +51,12 @@ function NumberField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
56
51
  // Cache the original value in component state
57
52
  setLastValue(newValue);
58
53
 
54
+ // Convert locale separator to standard '.' first
55
+ const standardValue = typeof newValue === 'string' ? newValue.replace(separator, '.') : newValue;
56
+
59
57
  // Normalize decimals that don't start with a zero character in advance so
60
58
  // that the rest of the normalization logic is simpler
61
- const normalizedValue = `${newValue}`.startsWith('.') ? `0${newValue}` : newValue;
59
+ const normalizedValue = `${standardValue}`.startsWith('.') ? `0${standardValue}` : standardValue;
62
60
 
63
61
  // Check that the value is a string (this can happen if the widget used is a
64
62
  // <select>, due to an enum declaration etc) then, if the value ends in a
@@ -70,14 +68,14 @@ function NumberField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
70
68
 
71
69
  onChange(processed as unknown as T, path, errorSchema, id);
72
70
  },
73
- [onChange],
71
+ [onChange, separator],
74
72
  );
75
73
 
76
74
  if (typeof lastValue === 'string' && typeof value === 'number') {
77
75
  // Construct a regular expression that checks for a string that consists
78
- // of the formData value suffixed with zero or one '.' characters and zero
76
+ // of the formData value suffixed with zero or one locale separator characters and zero
79
77
  // or more '0' characters
80
- const re = new RegExp(`^(${String(value).replace('.', '\\.')})?\\.?0*$`);
78
+ const re = new RegExp(`^(${String(value).replace('.', escapedSeparator)})?${escapedSeparator}?0*$`);
81
79
 
82
80
  // If the cached "lastValue" is a match, use that instead of the formData
83
81
  // value to prevent the input value from changing in the UI
@@ -86,7 +84,23 @@ function NumberField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
86
84
  }
87
85
  }
88
86
 
89
- return <StringField {...props} formData={value} onChange={handleChange} />;
87
+ // Format value to use the locale separator for rendering if it is a number
88
+ let displayValue: T | undefined = value;
89
+ if (typeof value === 'number' && separator !== '.') {
90
+ const { schema, uiSchema } = props;
91
+ const { schemaUtils } = registry;
92
+ const enumOptions = schemaUtils.isSelect(schema) ? optionsList(schema, uiSchema) : undefined;
93
+ const defaultWidget = enumOptions ? 'select' : 'text';
94
+ const { widget = defaultWidget } = getUiOptions(uiSchema);
95
+
96
+ // Do not convert the value to a locale-specific string for radio, select,
97
+ // or hidden widgets because option matching relies on the original numeric value.
98
+ if (widget !== 'radio' && widget !== 'select' && widget !== 'hidden') {
99
+ displayValue = String(value).replace('.', separator) as unknown as T;
100
+ }
101
+ }
102
+
103
+ return <StringField {...props} formData={displayValue} onChange={handleChange} />;
90
104
  }
91
105
 
92
106
  export default NumberField;
@@ -72,6 +72,16 @@ function getDefaultValue<T = any, S extends StrictRJSFSchema = RJSFSchema, F ext
72
72
  }
73
73
  }
74
74
 
75
+ function isAdditionalPropertySchema(schema: unknown) {
76
+ return Boolean((schema as RJSFMarkedSchema)?.[ADDITIONAL_PROPERTY_FLAG]);
77
+ }
78
+
79
+ function getAdditionalPropertyOrder<S extends StrictRJSFSchema = RJSFSchema>(
80
+ schemaProperties: NonNullable<S['properties']>,
81
+ ) {
82
+ return Object.keys(schemaProperties).filter((property) => isAdditionalPropertySchema(schemaProperties[property]));
83
+ }
84
+
75
85
  /** Props for the `ObjectFieldProperty` component */
76
86
  interface ObjectFieldPropertyProps<
77
87
  T = any,
@@ -234,10 +244,17 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
234
244
  [schemaUtils, rawSchema, formData],
235
245
  );
236
246
  const uiOptions = useMemo(() => getUiOptions<T, S, F>(uiSchema, globalUiOptions), [uiSchema, globalUiOptions]);
237
- const { properties: schemaProperties = {} } = schema;
247
+ const schemaProperties = useMemo(() => schema.properties ?? {}, [schema.properties]);
238
248
  // All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
239
249
  const childFieldPathId = props.childFieldPathId ?? fieldPathId;
240
250
  const lastRenamedProperty = useRef({ previousKey: '', currentKey: undefined as string | undefined });
251
+ const [additionalPropertyOrder, setAdditionalPropertyOrder] = useState(() =>
252
+ getAdditionalPropertyOrder<S>(schemaProperties),
253
+ );
254
+ const definedPropertyOrder = useMemo(() => {
255
+ const additionalPropertySet = new Set(getAdditionalPropertyOrder<S>(schemaProperties));
256
+ return Object.keys(schemaProperties).filter((property) => !additionalPropertySet.has(property));
257
+ }, [schemaProperties]);
241
258
 
242
259
  const templateTitle = uiOptions.title ?? schema.title ?? title ?? name;
243
260
  const description = uiOptions.description ?? schema.description;
@@ -308,6 +325,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
308
325
  lastRenamedProperty.current.currentKey = newKey;
309
326
  lastRenamedProperty.current.previousKey = getAvailableKey(newKey, newFormData);
310
327
  }
328
+ setAdditionalPropertyOrder((order) => [...order, newKey]);
311
329
  onChange(newFormData, childFieldPathId.path);
312
330
  }, [formData, onChange, translateString, schemaUtils, childFieldPathId, getAvailableKey, schema]);
313
331
 
@@ -339,6 +357,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
339
357
  lastRenamedProperty.current.previousKey = oldKey;
340
358
  }
341
359
  lastRenamedProperty.current.currentKey = actualNewKey;
360
+ setAdditionalPropertyOrder((order) => order.map((property) => (property === oldKey ? actualNewKey : property)));
342
361
  onChange(renamedObj, childFieldPathId.path);
343
362
  }
344
363
  },
@@ -350,6 +369,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
350
369
  */
351
370
  const handleRemoveProperty = useCallback(
352
371
  (key: string) => {
372
+ setAdditionalPropertyOrder((order) => order.filter((property) => property !== key));
353
373
  onChange(ADDITIONAL_PROPERTY_KEY_REMOVE as T, [...childFieldPathId.path, key]);
354
374
  },
355
375
  [onChange, childFieldPathId],
@@ -369,8 +389,11 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
369
389
 
370
390
  if (!renderOptionalField || hasFormData) {
371
391
  try {
372
- const properties = Object.keys(schemaProperties);
373
- orderedProperties = orderProperties(properties, uiOptions.order);
392
+ const definedPropertySet = new Set(definedPropertyOrder);
393
+ const currentAdditionalProperties = additionalPropertyOrder.filter(
394
+ (property) => Object.hasOwn(schemaProperties, property) && !definedPropertySet.has(property),
395
+ );
396
+ orderedProperties = orderProperties([...definedPropertyOrder, ...currentAdditionalProperties], uiOptions.order);
374
397
  } catch (err) {
375
398
  return (
376
399
  <div>
@@ -395,9 +418,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
395
418
  title: uiOptions.label === false ? '' : templateTitle,
396
419
  description: uiOptions.label === false ? undefined : description,
397
420
  properties: orderedProperties.map((propertyName) => {
398
- const addedByAdditionalProperties = Boolean(
399
- (schema.properties?.[propertyName] as RJSFMarkedSchema)?.[ADDITIONAL_PROPERTY_FLAG],
400
- );
421
+ const addedByAdditionalProperties = isAdditionalPropertySchema(schema.properties?.[propertyName]);
401
422
  const fieldUiSchema = addedByAdditionalProperties ? uiSchema.additionalProperties : uiSchema[propertyName];
402
423
  const hidden = getUiOptions<T, S, F>(fieldUiSchema).widget === 'hidden';
403
424
  const content = (