@rjsf/core 6.7.0 → 6.8.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.
@@ -1 +1 @@
1
- {"version":3,"file":"NumberField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/NumberField.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,UAAU,EACV,eAAe,EACf,UAAU,EACV,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAerB;;;;;;;;;;;;;;;;GAgBG;AACH,iBAAS,WAAW,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,gBAAgB,GAAG,UAAU,EAAE,CAAC,SAAS,eAAe,GAAG,GAAG,EACpG,KAAK,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,+BAgD3B;AAED,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"NumberField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/NumberField.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,UAAU,EACV,eAAe,EACf,UAAU,EACV,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAOrB;;;;;;;;;;;;;;;;GAgBG;AACH,iBAAS,WAAW,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,gBAAgB,GAAG,UAAU,EAAE,CAAC,SAAS,eAAe,GAAG,GAAG,EACpG,KAAK,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,+BAsE3B;AAED,eAAe,WAAW,CAAC"}
@@ -1,15 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useState, useCallback } from 'react';
3
- import { asNumber } from '@rjsf/utils';
4
- // Matches a string that ends in a . character, optionally followed by a sequence of
5
- // digits followed by any number of 0 characters up until the end of the line.
6
- // Ensuring that there is at least one prefixed character is important so that
7
- // you don't incorrectly match against "0".
3
+ import { asNumber, getDecimalSeparator, getUiOptions, optionsList } from '@rjsf/utils';
4
+ // Static matchers for standard '.' separator used during normalization inside handleChange
8
5
  const trailingCharMatcherWithPrefix = /\.([0-9]*0)*$/;
9
- // This is used for trimming the trailing 0 and . characters without affecting
10
- // the rest of the string. Its possible to use one RegEx with groups for this
11
- // functionality, but it is fairly complex compared to simply defining two
12
- // different matchers.
13
6
  const trailingCharMatcher = /[0.]0*$/;
14
7
  /**
15
8
  * The NumberField class has some special handling for dealing with trailing
@@ -32,6 +25,8 @@ function NumberField(props) {
32
25
  const { registry, onChange, formData, value: initialValue } = props;
33
26
  const [lastValue, setLastValue] = useState(initialValue);
34
27
  const { StringField } = registry.fields;
28
+ const separator = getDecimalSeparator();
29
+ const escapedSeparator = separator === '.' ? '\\.' : separator;
35
30
  let value = formData;
36
31
  /** Handle the change from the `StringField` to properly convert to a number
37
32
  *
@@ -40,9 +35,11 @@ function NumberField(props) {
40
35
  const handleChange = useCallback((newValue, path, errorSchema, id) => {
41
36
  // Cache the original value in component state
42
37
  setLastValue(newValue);
38
+ // Convert locale separator to standard '.' first
39
+ const standardValue = typeof newValue === 'string' ? newValue.replace(separator, '.') : newValue;
43
40
  // Normalize decimals that don't start with a zero character in advance so
44
41
  // that the rest of the normalization logic is simpler
45
- const normalizedValue = `${newValue}`.startsWith('.') ? `0${newValue}` : newValue;
42
+ const normalizedValue = `${standardValue}`.startsWith('.') ? `0${standardValue}` : standardValue;
46
43
  // Check that the value is a string (this can happen if the widget used is a
47
44
  // <select>, due to an enum declaration etc) then, if the value ends in a
48
45
  // trailing decimal point or multiple zeroes, strip the trailing values
@@ -50,18 +47,32 @@ function NumberField(props) {
50
47
  ? asNumber(normalizedValue.replace(trailingCharMatcher, ''))
51
48
  : asNumber(normalizedValue);
52
49
  onChange(processed, path, errorSchema, id);
53
- }, [onChange]);
50
+ }, [onChange, separator]);
54
51
  if (typeof lastValue === 'string' && typeof value === 'number') {
55
52
  // Construct a regular expression that checks for a string that consists
56
- // of the formData value suffixed with zero or one '.' characters and zero
53
+ // of the formData value suffixed with zero or one locale separator characters and zero
57
54
  // or more '0' characters
58
- const re = new RegExp(`^(${String(value).replace('.', '\\.')})?\\.?0*$`);
55
+ const re = new RegExp(`^(${String(value).replace('.', escapedSeparator)})?${escapedSeparator}?0*$`);
59
56
  // If the cached "lastValue" is a match, use that instead of the formData
60
57
  // value to prevent the input value from changing in the UI
61
58
  if (lastValue.match(re)) {
62
59
  value = lastValue;
63
60
  }
64
61
  }
65
- return _jsx(StringField, { ...props, formData: value, onChange: handleChange });
62
+ // Format value to use the locale separator for rendering if it is a number
63
+ let displayValue = value;
64
+ if (typeof value === 'number' && separator !== '.') {
65
+ const { schema, uiSchema } = props;
66
+ const { schemaUtils } = registry;
67
+ const enumOptions = schemaUtils.isSelect(schema) ? optionsList(schema, uiSchema) : undefined;
68
+ const defaultWidget = enumOptions ? 'select' : 'text';
69
+ const { widget = defaultWidget } = getUiOptions(uiSchema);
70
+ // Do not convert the value to a locale-specific string for radio, select,
71
+ // or hidden widgets because option matching relies on the original numeric value.
72
+ if (widget !== 'radio' && widget !== 'select' && widget !== 'hidden') {
73
+ displayValue = String(value).replace('.', separator);
74
+ }
75
+ }
76
+ return _jsx(StringField, { ...props, formData: displayValue, onChange: handleChange });
66
77
  }
67
78
  export default NumberField;
@@ -1 +1 @@
1
- {"version":3,"file":"ObjectField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/ObjectField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAIV,UAAU,EACV,eAAe,EAIf,UAAU,EACV,gBAAgB,EACjB,MAAM,aAAa,CAAC;AA6LrB;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,gBAAgB,GAAG,UAAU,EAAE,CAAC,SAAS,eAAe,GAAG,GAAG,EACnH,KAAK,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,+BA8O3B"}
1
+ {"version":3,"file":"ObjectField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/ObjectField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAIV,UAAU,EACV,eAAe,EAIf,UAAU,EACV,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAuMrB;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,gBAAgB,GAAG,UAAU,EAAE,CAAC,SAAS,eAAe,GAAG,GAAG,EACnH,KAAK,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,+BAyP3B"}
@@ -39,6 +39,12 @@ function getDefaultValue(translateString, type) {
39
39
  return translateString(TranslatableString.NewStringDefault);
40
40
  }
41
41
  }
42
+ function isAdditionalPropertySchema(schema) {
43
+ return Boolean(schema?.[ADDITIONAL_PROPERTY_FLAG]);
44
+ }
45
+ function getAdditionalPropertyOrder(schemaProperties) {
46
+ return Object.keys(schemaProperties).filter((property) => isAdditionalPropertySchema(schemaProperties[property]));
47
+ }
42
48
  /** The `ObjectFieldProperty` component is used to render the `SchemaField` for a child property of an object
43
49
  */
44
50
  function ObjectFieldPropertyFn(props) {
@@ -103,10 +109,15 @@ export default function ObjectField(props) {
103
109
  formDataRef.current = formData;
104
110
  const schema = useMemo(() => schemaUtils.retrieveSchema(rawSchema, formData, true), [schemaUtils, rawSchema, formData]);
105
111
  const uiOptions = useMemo(() => getUiOptions(uiSchema, globalUiOptions), [uiSchema, globalUiOptions]);
106
- const { properties: schemaProperties = {} } = schema;
112
+ const schemaProperties = useMemo(() => schema.properties ?? {}, [schema.properties]);
107
113
  // All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
108
114
  const childFieldPathId = props.childFieldPathId ?? fieldPathId;
109
115
  const lastRenamedProperty = useRef({ previousKey: '', currentKey: undefined });
116
+ const [additionalPropertyOrder, setAdditionalPropertyOrder] = useState(() => getAdditionalPropertyOrder(schemaProperties));
117
+ const definedPropertyOrder = useMemo(() => {
118
+ const additionalPropertySet = new Set(getAdditionalPropertyOrder(schemaProperties));
119
+ return Object.keys(schemaProperties).filter((property) => !additionalPropertySet.has(property));
120
+ }, [schemaProperties]);
110
121
  const templateTitle = uiOptions.title ?? schema.title ?? title ?? name;
111
122
  const description = uiOptions.description ?? schema.description;
112
123
  const renderOptionalField = shouldRenderOptionalField(registry, schema, required, uiSchema);
@@ -169,6 +180,7 @@ export default function ObjectField(props) {
169
180
  lastRenamedProperty.current.currentKey = newKey;
170
181
  lastRenamedProperty.current.previousKey = getAvailableKey(newKey, newFormData);
171
182
  }
183
+ setAdditionalPropertyOrder((order) => [...order, newKey]);
172
184
  onChange(newFormData, childFieldPathId.path);
173
185
  }, [formData, onChange, translateString, schemaUtils, childFieldPathId, getAvailableKey, schema]);
174
186
  /** Returns a callback function that deals with the rename of a key for an additional property for a schema. That
@@ -197,6 +209,7 @@ export default function ObjectField(props) {
197
209
  lastRenamedProperty.current.previousKey = oldKey;
198
210
  }
199
211
  lastRenamedProperty.current.currentKey = actualNewKey;
212
+ setAdditionalPropertyOrder((order) => order.map((property) => (property === oldKey ? actualNewKey : property)));
200
213
  onChange(renamedObj, childFieldPathId.path);
201
214
  }
202
215
  }, [onChange, childFieldPathId, getAvailableKey]);
@@ -204,6 +217,7 @@ export default function ObjectField(props) {
204
217
  * value for the path plus the key to be removed
205
218
  */
206
219
  const handleRemoveProperty = useCallback((key) => {
220
+ setAdditionalPropertyOrder((order) => order.filter((property) => property !== key));
207
221
  onChange(ADDITIONAL_PROPERTY_KEY_REMOVE, [...childFieldPathId.path, key]);
208
222
  }, [onChange, childFieldPathId]);
209
223
  /** Returns the stable React key for a property. For the most recently renamed
@@ -219,8 +233,9 @@ export default function ObjectField(props) {
219
233
  }, []);
220
234
  if (!renderOptionalField || hasFormData) {
221
235
  try {
222
- const properties = Object.keys(schemaProperties);
223
- orderedProperties = orderProperties(properties, uiOptions.order);
236
+ const definedPropertySet = new Set(definedPropertyOrder);
237
+ const currentAdditionalProperties = additionalPropertyOrder.filter((property) => Object.hasOwn(schemaProperties, property) && !definedPropertySet.has(property));
238
+ orderedProperties = orderProperties([...definedPropertyOrder, ...currentAdditionalProperties], uiOptions.order);
224
239
  }
225
240
  catch (err) {
226
241
  return (_jsxs("div", { children: [_jsx("p", { className: 'rjsf-config-error', style: { color: 'red' }, children: _jsx(Markdown, { options: { disableParsingRawHTML: true }, children: translateString(TranslatableString.InvalidObjectField, [name || 'root', err.message]) }) }), _jsx("pre", { children: JSON.stringify(schema) })] }));
@@ -233,7 +248,7 @@ export default function ObjectField(props) {
233
248
  title: uiOptions.label === false ? '' : templateTitle,
234
249
  description: uiOptions.label === false ? undefined : description,
235
250
  properties: orderedProperties.map((propertyName) => {
236
- const addedByAdditionalProperties = Boolean(schema.properties?.[propertyName]?.[ADDITIONAL_PROPERTY_FLAG]);
251
+ const addedByAdditionalProperties = isAdditionalPropertySchema(schema.properties?.[propertyName]);
237
252
  const fieldUiSchema = addedByAdditionalProperties ? uiSchema.additionalProperties : uiSchema[propertyName];
238
253
  const hidden = getUiOptions(fieldUiSchema).widget === 'hidden';
239
254
  const content = (_jsx(ObjectFieldProperty, { propertyName: propertyName, required: isRequired(schema, propertyName), schema: get(schema, [PROPERTIES_KEY, propertyName], {}), uiSchema: fieldUiSchema, errorSchema: get(errorSchema, [propertyName]), fieldPathId: childFieldPathId, formData: get(formData, [propertyName]), handleKeyRename: handleKeyRename, handleRemoveProperty: handleRemoveProperty, addedByAdditionalProperties: addedByAdditionalProperties, onChange: onChange, onBlur: onBlur, onFocus: onFocus, registry: registry, disabled: disabled, readonly: readonly, hideError: hideError }, getStableKey(propertyName)));
@@ -1 +1 @@
1
- {"version":3,"file":"TimeWidget.d.ts","sourceRoot":"","sources":["../../../src/components/widgets/TimeWidget.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG9F;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,gBAAgB,GAAG,UAAU,EAAE,CAAC,SAAS,eAAe,GAAG,GAAG,EAClH,KAAK,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,+BAO5B"}
1
+ {"version":3,"file":"TimeWidget.d.ts","sourceRoot":"","sources":["../../../src/components/widgets/TimeWidget.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG9F;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,SAAS,gBAAgB,GAAG,UAAU,EAAE,CAAC,SAAS,eAAe,GAAG,GAAG,EAClH,KAAK,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,+BAsB5B"}
@@ -7,8 +7,20 @@ import { getTemplate } from '@rjsf/utils';
7
7
  * @param props - The `WidgetProps` for this component
8
8
  */
9
9
  export default function TimeWidget(props) {
10
- const { onChange, options, registry } = props;
10
+ const { onChange, options, registry, schema, value } = props;
11
11
  const BaseInputTemplate = getTemplate('BaseInputTemplate', registry, options);
12
- const handleChange = useCallback((value) => onChange(value ? `${value}:00` : undefined), [onChange]);
13
- return _jsx(BaseInputTemplate, { type: 'time', ...props, onChange: handleChange });
12
+ const hasSecondPrecision = typeof schema.multipleOf === 'number' && Number.isFinite(schema.multipleOf) && schema.multipleOf < 60;
13
+ const handleChange = useCallback((newValue) => {
14
+ if (!newValue) {
15
+ onChange(undefined);
16
+ }
17
+ else if (hasSecondPrecision) {
18
+ onChange(newValue);
19
+ }
20
+ else {
21
+ onChange(`${newValue}:00`);
22
+ }
23
+ }, [hasSecondPrecision, onChange]);
24
+ const displayValue = typeof value === 'string' && !hasSecondPrecision && /^\d{2}:\d{2}:00$/.test(value) ? value.slice(0, -3) : value;
25
+ return _jsx(BaseInputTemplate, { type: 'time', ...props, value: displayValue, onChange: handleChange });
14
26
  }