@bpmn-io/form-js-viewer 1.6.1 → 1.6.3

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/dist/index.es.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import Ids from 'ids';
2
- import { isString, get, some, isNumber, set, findIndex, isArray, isObject, isNil, isDefined, values, uniqueBy, isFunction, bind, assign, groupBy, flatten, isUndefined } from 'min-dash';
2
+ import { isString, get, some, isNil, isObject, isNumber, set, findIndex, isArray, isDefined, values, uniqueBy, isFunction, bind, assign, groupBy, flatten, isUndefined } from 'min-dash';
3
3
  import Big from 'big.js';
4
4
  import classNames from 'classnames';
5
5
  import { jsx, jsxs, Fragment } from 'preact/jsx-runtime';
6
6
  import { useContext, useMemo, useEffect, useRef, useState, useCallback, useLayoutEffect } from 'preact/hooks';
7
7
  import { createContext, createElement, Fragment as Fragment$1, render } from 'preact';
8
+ import isEqual from 'lodash/isEqual';
8
9
  import flatpickr from 'flatpickr';
9
10
  import * as React from 'preact/compat';
10
11
  import { createPortal } from 'preact/compat';
@@ -1084,39 +1085,58 @@ function getOptionsData(formField, formData) {
1084
1085
 
1085
1086
  // transforms the provided options into a normalized format, trimming invalid options
1086
1087
  function normalizeOptionsData(optionsData) {
1087
- return optionsData.filter(_isOptionSomething).map(v => _normalizeOptionsData(v)).filter(v => v);
1088
+ return optionsData.filter(_isAllowedValue).map(_normalizeOption).filter(o => !isNil(o));
1088
1089
  }
1089
- function _normalizeOptionsData(optionData) {
1090
- if (_isAllowedOption(optionData)) {
1091
- // if a primitive is provided, use it as label and value
1090
+
1091
+ /**
1092
+ * Converts the provided option to a normalized format.
1093
+ * If the option is not valid, null is returned.
1094
+ *
1095
+ * @param {object} option
1096
+ * @param {string} option.label
1097
+ * @param {*} option.value
1098
+ *
1099
+ * @returns
1100
+ */
1101
+ function _normalizeOption(option) {
1102
+ // (1) simple primitive case, use it as both label and value
1103
+ if (_isAllowedPrimitive(option)) {
1092
1104
  return {
1093
- value: optionData,
1094
- label: `${optionData}`
1105
+ value: option,
1106
+ label: `${option}`
1095
1107
  };
1096
1108
  }
1097
- if (typeof optionData === 'object') {
1098
- if (!optionData.label && _isAllowedOption(optionData.value)) {
1099
- // if no label is provided, use the value as label
1109
+ if (isObject(option)) {
1110
+ const isValidLabel = _isValidLabel(option.label);
1111
+
1112
+ // (2) no label provided, but value is a simple primitive, use it as label and value
1113
+ if (!isValidLabel && _isAllowedPrimitive(option.value)) {
1100
1114
  return {
1101
- value: optionData.value,
1102
- label: `${optionData.value}`
1115
+ value: option.value,
1116
+ label: `${option.value}`
1103
1117
  };
1104
1118
  }
1105
- if (_isOptionSomething(optionData.value) && _isAllowedOption(optionData.label)) {
1106
- // if both value and label are provided, use them as is, in this scenario, the value may also be an object
1107
- return optionData;
1119
+
1120
+ // (3) both label and value are provided, use them as is
1121
+ if (isValidLabel && _isAllowedValue(option.value)) {
1122
+ return option;
1108
1123
  }
1109
1124
  }
1110
1125
  return null;
1111
1126
  }
1112
- function _isAllowedOption(option) {
1113
- return _isReadableType(option) && _isOptionSomething(option);
1127
+ function _isAllowedPrimitive(value) {
1128
+ const isAllowedPrimitiveType = ['number', 'string', 'boolean'].includes(typeof value);
1129
+ const isValid = value || value === 0 || value === false;
1130
+ return isAllowedPrimitiveType && isValid;
1114
1131
  }
1115
- function _isReadableType(option) {
1116
- return ['number', 'string', 'boolean'].includes(typeof option);
1132
+ function _isValidLabel(label) {
1133
+ return label && isString(label);
1117
1134
  }
1118
- function _isOptionSomething(option) {
1119
- return option || option === 0 || option === false;
1135
+ function _isAllowedValue(value) {
1136
+ if (isObject(value)) {
1137
+ return Object.keys(value).length > 0;
1138
+ }
1139
+ return _isAllowedPrimitive(value);
1120
1140
  }
1121
1141
  function createEmptyOptions(options = {}) {
1122
1142
  const defaults = {};
@@ -1210,30 +1230,6 @@ const buildLoadedState = options => ({
1210
1230
  loadState: LOAD_STATES.LOADED
1211
1231
  });
1212
1232
 
1213
- function useCleanupMultiSelectValues (props) {
1214
- const {
1215
- field,
1216
- options,
1217
- loadState,
1218
- onChange,
1219
- values
1220
- } = props;
1221
-
1222
- // Ensures that the values are always a subset of the possible options
1223
- useEffect(() => {
1224
- if (loadState !== LOAD_STATES.LOADED) {
1225
- return;
1226
- }
1227
- const hasValuesNotInOptions = values.some(v => !options.map(o => o.value).includes(v));
1228
- if (hasValuesNotInOptions) {
1229
- onChange({
1230
- field,
1231
- value: values.filter(v => options.map(o => o.value).includes(v))
1232
- });
1233
- }
1234
- }, [field, options, onChange, JSON.stringify(values), loadState]);
1235
- }
1236
-
1237
1233
  const ENTER_KEYDOWN_EVENT = new KeyboardEvent('keydown', {
1238
1234
  code: 'Enter',
1239
1235
  key: 'Enter',
@@ -1409,6 +1405,12 @@ function sanitizeDateTimePickerValue(options) {
1409
1405
  if (subtype === DATETIME_SUBTYPES.DATETIME && (isInvalidDateString(value) || !isDateTimeInputInformationSufficient(value))) return null;
1410
1406
  return value;
1411
1407
  }
1408
+ function hasEqualValue(value, array) {
1409
+ if (!Array.isArray(array)) {
1410
+ return false;
1411
+ }
1412
+ return array.some(element => isEqual(value, element));
1413
+ }
1412
1414
  function sanitizeSingleSelectValue(options) {
1413
1415
  const {
1414
1416
  formField,
@@ -1417,7 +1419,7 @@ function sanitizeSingleSelectValue(options) {
1417
1419
  } = options;
1418
1420
  try {
1419
1421
  const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1420
- return validValues.includes(value) ? value : null;
1422
+ return hasEqualValue(value, validValues) ? value : null;
1421
1423
  } catch (error) {
1422
1424
  // use default value in case of formatting error
1423
1425
  // TODO(@Skaiir): log a warning when this happens - https://github.com/bpmn-io/form-js/issues/289
@@ -1432,7 +1434,7 @@ function sanitizeMultiSelectValue(options) {
1432
1434
  } = options;
1433
1435
  try {
1434
1436
  const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1435
- return value.filter(v => validValues.includes(v));
1437
+ return value.filter(v => hasEqualValue(v, validValues));
1436
1438
  } catch (error) {
1437
1439
  // use default value in case of formatting error
1438
1440
  // TODO(@Skaiir): log a warning when this happens - https://github.com/bpmn-io/form-js/issues/289
@@ -1440,6 +1442,31 @@ function sanitizeMultiSelectValue(options) {
1440
1442
  }
1441
1443
  }
1442
1444
 
1445
+ function useCleanupMultiSelectValues (props) {
1446
+ const {
1447
+ field,
1448
+ options,
1449
+ loadState,
1450
+ onChange,
1451
+ values
1452
+ } = props;
1453
+
1454
+ // Ensures that the values are always a subset of the possible options
1455
+ useEffect(() => {
1456
+ if (loadState !== LOAD_STATES.LOADED) {
1457
+ return;
1458
+ }
1459
+ const optionValues = options.map(o => o.value);
1460
+ const hasValuesNotInOptions = values.some(v => !hasEqualValue(v, optionValues));
1461
+ if (hasValuesNotInOptions) {
1462
+ onChange({
1463
+ field,
1464
+ value: values.filter(v => hasEqualValue(v, optionValues))
1465
+ });
1466
+ }
1467
+ }, [field, options, onChange, JSON.stringify(values), loadState]);
1468
+ }
1469
+
1443
1470
  const type$d = 'checklist';
1444
1471
  function Checklist(props) {
1445
1472
  const {
@@ -1462,16 +1489,11 @@ function Checklist(props) {
1462
1489
  const {
1463
1490
  required
1464
1491
  } = validate;
1465
- const toggleCheckbox = v => {
1466
- let newValue = [...values];
1467
- if (!newValue.includes(v)) {
1468
- newValue.push(v);
1469
- } else {
1470
- newValue = newValue.filter(x => x != v);
1471
- }
1492
+ const toggleCheckbox = toggledValue => {
1493
+ const newValues = hasEqualValue(toggledValue, values) ? values.filter(value => !isEqual(value, toggledValue)) : [...values, toggledValue];
1472
1494
  props.onChange({
1473
1495
  field,
1474
- value: newValue
1496
+ value: newValues
1475
1497
  });
1476
1498
  };
1477
1499
  const onCheckboxBlur = e => {
@@ -1509,15 +1531,16 @@ function Checklist(props) {
1509
1531
  required: required
1510
1532
  }), loadState == LOAD_STATES.LOADED && options.map((o, index) => {
1511
1533
  const itemDomId = `${domId}-${index}`;
1534
+ const isChecked = hasEqualValue(o.value, values);
1512
1535
  return jsx(Label, {
1513
1536
  id: itemDomId,
1514
1537
  label: o.label,
1515
1538
  class: classNames({
1516
- 'fjs-checked': values.includes(o.value)
1539
+ 'fjs-checked': isChecked
1517
1540
  }),
1518
1541
  required: false,
1519
1542
  children: jsx("input", {
1520
- checked: values.includes(o.value),
1543
+ checked: isChecked,
1521
1544
  class: "fjs-input",
1522
1545
  disabled: disabled,
1523
1546
  readOnly: readonly,
@@ -3279,8 +3302,8 @@ function Numberfield(props) {
3279
3302
  id: domId,
3280
3303
  onKeyDown: onKeyDown,
3281
3304
  onKeyPress: onKeyPress,
3282
- onBlur: () => onBlur && onBlur(),
3283
- onFocus: () => onFocus && onFocus()
3305
+ onBlur: onBlur,
3306
+ onFocus: onFocus
3284
3307
 
3285
3308
  // @ts-ignore
3286
3309
  ,
@@ -3357,7 +3380,8 @@ function useCleanupSingleSelectValue (props) {
3357
3380
  if (loadState !== LOAD_STATES.LOADED) {
3358
3381
  return;
3359
3382
  }
3360
- const hasValueNotInOptions = value && !options.map(o => o.value).includes(value);
3383
+ const optionValues = options.map(o => o.value);
3384
+ const hasValueNotInOptions = value && !hasEqualValue(value, optionValues);
3361
3385
  if (hasValueNotInOptions) {
3362
3386
  onChange({
3363
3387
  field,
@@ -3430,15 +3454,16 @@ function Radio(props) {
3430
3454
  required: required
3431
3455
  }), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
3432
3456
  const itemDomId = `${domId}-${index}`;
3457
+ const isChecked = isEqual(option.value, value);
3433
3458
  return jsx(Label, {
3434
3459
  id: itemDomId,
3435
3460
  label: option.label,
3436
3461
  class: classNames({
3437
- 'fjs-checked': option.value === value
3462
+ 'fjs-checked': isChecked
3438
3463
  }),
3439
3464
  required: false,
3440
3465
  children: jsx("input", {
3441
- checked: option.value === value,
3466
+ checked: isChecked,
3442
3467
  class: "fjs-input",
3443
3468
  disabled: disabled,
3444
3469
  readOnly: readonly,
@@ -3468,6 +3493,21 @@ Radio.config = {
3468
3493
  create: createEmptyOptions
3469
3494
  };
3470
3495
 
3496
+ /**
3497
+ * This hook allows us to retrieve the label from a value in linear time by caching it in a map
3498
+ * @param {Array} options
3499
+ */
3500
+ function useGetLabelCorrelation(options) {
3501
+ // This allows us to retrieve the label from a value in linear time
3502
+ const labelMap = useMemo(() => Object.assign({}, ...options.map(o => ({
3503
+ [_getValueHash(o.value)]: o.label
3504
+ }))), [options]);
3505
+ return useCallback(value => labelMap[_getValueHash(value)], [labelMap]);
3506
+ }
3507
+ const _getValueHash = value => {
3508
+ return isObject(value) ? JSON.stringify(value) : value;
3509
+ };
3510
+
3471
3511
  var _path$q;
3472
3512
  function _extends$r() { _extends$r = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$r.apply(this, arguments); }
3473
3513
  var SvgXMark = function SvgXMark(props) {
@@ -3499,7 +3539,7 @@ function SearchableSelect(props) {
3499
3539
  } = props;
3500
3540
  const [filter, setFilter] = useState('');
3501
3541
  const [isDropdownExpanded, setIsDropdownExpanded] = useState(false);
3502
- const [shouldApplyFilter, setShouldApplyFilter] = useState(true);
3542
+ const [isFilterActive, setIsFilterActive] = useState(true);
3503
3543
  const [isEscapeClosed, setIsEscapeClose] = useState(false);
3504
3544
  const searchbarRef = useRef();
3505
3545
  const eventBus = useService('eventBus');
@@ -3514,23 +3554,22 @@ function SearchableSelect(props) {
3514
3554
  value,
3515
3555
  onChange: props.onChange
3516
3556
  });
3517
-
3518
- // We cache a map of option values to their index so that we don't need to search the whole options array every time to correlate the label
3519
- const valueToOptionMap = useMemo(() => Object.assign({}, ...options.map((o, x) => ({
3520
- [o.value]: options[x]
3521
- }))), [options]);
3522
- const valueLabel = useMemo(() => value && valueToOptionMap[value] && valueToOptionMap[value].label || '', [value, valueToOptionMap]);
3557
+ const getLabelCorrelation = useGetLabelCorrelation(options);
3558
+ const label = useMemo(() => value && getLabelCorrelation(value), [value, getLabelCorrelation]);
3523
3559
 
3524
3560
  // whenever we change the underlying value, set the label to it
3525
3561
  useEffect(() => {
3526
- setFilter(valueLabel);
3527
- }, [valueLabel]);
3562
+ setFilter(label);
3563
+ }, [label]);
3528
3564
  const filteredOptions = useMemo(() => {
3529
- if (loadState === LOAD_STATES.LOADED) {
3530
- return shouldApplyFilter ? options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase())) : options;
3565
+ if (loadState !== LOAD_STATES.LOADED) {
3566
+ return [];
3531
3567
  }
3532
- return [];
3533
- }, [filter, loadState, options, shouldApplyFilter]);
3568
+ if (!filter || !isFilterActive) {
3569
+ return options;
3570
+ }
3571
+ return options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase()));
3572
+ }, [filter, loadState, options, isFilterActive]);
3534
3573
  const setValue = useCallback(option => {
3535
3574
  setFilter(option && option.label || '');
3536
3575
  props.onChange({
@@ -3557,7 +3596,7 @@ function SearchableSelect(props) {
3557
3596
  }) => {
3558
3597
  setIsEscapeClose(false);
3559
3598
  setIsDropdownExpanded(true);
3560
- setShouldApplyFilter(true);
3599
+ setIsFilterActive(true);
3561
3600
  setFilter(target.value || '');
3562
3601
  eventBus.fire('formField.search', {
3563
3602
  formField: field,
@@ -3573,7 +3612,7 @@ function SearchableSelect(props) {
3573
3612
  {
3574
3613
  if (!isDropdownExpanded) {
3575
3614
  setIsDropdownExpanded(true);
3576
- setShouldApplyFilter(false);
3615
+ setIsFilterActive(false);
3577
3616
  }
3578
3617
  keyDownEvent.preventDefault();
3579
3618
  break;
@@ -3591,7 +3630,7 @@ function SearchableSelect(props) {
3591
3630
  const onInputMouseDown = useCallback(() => {
3592
3631
  setIsEscapeClose(false);
3593
3632
  setIsDropdownExpanded(true);
3594
- setShouldApplyFilter(false);
3633
+ setIsFilterActive(false);
3595
3634
  }, []);
3596
3635
  const onInputFocus = useCallback(() => {
3597
3636
  setIsEscapeClose(false);
@@ -3600,9 +3639,9 @@ function SearchableSelect(props) {
3600
3639
  }, [onFocus]);
3601
3640
  const onInputBlur = useCallback(() => {
3602
3641
  setIsDropdownExpanded(false);
3603
- setFilter(valueLabel);
3642
+ setFilter(label);
3604
3643
  onBlur && onBlur();
3605
- }, [onBlur, valueLabel]);
3644
+ }, [onBlur, label]);
3606
3645
  return jsxs(Fragment, {
3607
3646
  children: [jsxs("div", {
3608
3647
  class: classNames('fjs-input-group', {
@@ -3679,12 +3718,8 @@ function SimpleSelect(props) {
3679
3718
  value,
3680
3719
  onChange: props.onChange
3681
3720
  });
3682
-
3683
- // We cache a map of option values to their index so that we don't need to search the whole options array every time to correlate the label
3684
- const valueToOptionMap = useMemo(() => Object.assign({}, ...options.map((o, x) => ({
3685
- [o.value]: options[x]
3686
- }))), [options]);
3687
- const valueLabel = useMemo(() => value && valueToOptionMap[value] && valueToOptionMap[value].label || '', [value, valueToOptionMap]);
3721
+ const getLabelCorrelation = useGetLabelCorrelation(options);
3722
+ const valueLabel = useMemo(() => value && getLabelCorrelation(value), [value, getLabelCorrelation]);
3688
3723
  const setValue = useCallback(option => {
3689
3724
  props.onChange({
3690
3725
  value: option && option.value || null,
@@ -3999,11 +4034,7 @@ function Taglist(props) {
3999
4034
  values,
4000
4035
  onChange: props.onChange
4001
4036
  });
4002
-
4003
- // We cache a map of option values to their index so that we don't need to search the whole options array every time to correlate the label
4004
- const valueToOptionMap = useMemo(() => Object.assign({}, ...options.map((o, x) => ({
4005
- [o.value]: options[x]
4006
- }))), [options]);
4037
+ const getLabelCorrelation = useGetLabelCorrelation(options);
4007
4038
  const hasOptionsLeft = useMemo(() => options.length > values.length, [options.length, values.length]);
4008
4039
 
4009
4040
  // Usage of stringify is necessary here because we want this effect to only trigger when there is a value change to the array
@@ -4011,12 +4042,14 @@ function Taglist(props) {
4011
4042
  if (loadState !== LOAD_STATES.LOADED) {
4012
4043
  return [];
4013
4044
  }
4014
- return options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase()) && !values.includes(o.value));
4045
+ const isValidFilteredOption = option => {
4046
+ const filterMatches = option.label.toLowerCase().includes(filter.toLowerCase());
4047
+ return filterMatches && !hasEqualValue(option.value, values);
4048
+ };
4049
+ return options.filter(isValidFilteredOption);
4015
4050
  }, [filter, options, JSON.stringify(values), loadState]);
4016
4051
  const selectValue = value => {
4017
- if (filter) {
4018
- setFilter('');
4019
- }
4052
+ setFilter('');
4020
4053
 
4021
4054
  // Ensure values cannot be double selected due to latency
4022
4055
  if (values.at(-1) === value) {
@@ -4028,8 +4061,9 @@ function Taglist(props) {
4028
4061
  });
4029
4062
  };
4030
4063
  const deselectValue = value => {
4064
+ const newValues = values.filter(v => !isEqual(v, value));
4031
4065
  props.onChange({
4032
- value: values.filter(v => v != value),
4066
+ value: newValues,
4033
4067
  field
4034
4068
  });
4035
4069
  };
@@ -4139,7 +4173,7 @@ function Taglist(props) {
4139
4173
  onMouseDown: e => e.preventDefault(),
4140
4174
  children: [jsx("span", {
4141
4175
  class: "fjs-taglist-tag-label",
4142
- children: valueToOptionMap[v] ? valueToOptionMap[v].label : undefined
4176
+ children: getLabelCorrelation(v)
4143
4177
  }), !disabled && !readonly && jsx("button", {
4144
4178
  type: "button",
4145
4179
  title: "Remove tag",
@@ -4279,6 +4313,40 @@ function DisabledLink({
4279
4313
  });
4280
4314
  }
4281
4315
 
4316
+ function useFlushDebounce(func, additionalDeps = []) {
4317
+ const timeoutRef = useRef(null);
4318
+ const lastArgsRef = useRef(null);
4319
+ const config = useService('config', false);
4320
+ const debounce = config && config.debounce;
4321
+ const shouldDebounce = debounce !== false && debounce !== 0;
4322
+ const delay = typeof debounce === 'number' ? debounce : 300;
4323
+ const debounceFunc = useCallback((...args) => {
4324
+ if (!shouldDebounce) {
4325
+ func(...args);
4326
+ return;
4327
+ }
4328
+ lastArgsRef.current = args;
4329
+ if (timeoutRef.current) {
4330
+ clearTimeout(timeoutRef.current);
4331
+ }
4332
+ timeoutRef.current = setTimeout(() => {
4333
+ func(...lastArgsRef.current);
4334
+ lastArgsRef.current = null;
4335
+ }, delay);
4336
+ }, [func, delay, shouldDebounce, ...additionalDeps]);
4337
+ const flushFunc = useCallback(() => {
4338
+ if (timeoutRef.current) {
4339
+ clearTimeout(timeoutRef.current);
4340
+ if (lastArgsRef.current !== null) {
4341
+ func(...lastArgsRef.current);
4342
+ lastArgsRef.current = null;
4343
+ }
4344
+ timeoutRef.current = null;
4345
+ }
4346
+ }, [func, ...additionalDeps]);
4347
+ return [debounceFunc, flushFunc];
4348
+ }
4349
+
4282
4350
  const type$2 = 'textfield';
4283
4351
  function Textfield(props) {
4284
4352
  const {
@@ -4305,13 +4373,20 @@ function Textfield(props) {
4305
4373
  const {
4306
4374
  required
4307
4375
  } = validate;
4308
- const onChange = ({
4376
+ const [onInputChange, flushOnChange] = useFlushDebounce(({
4309
4377
  target
4310
4378
  }) => {
4311
4379
  props.onChange({
4312
4380
  field,
4313
4381
  value: target.value
4314
4382
  });
4383
+ }, [props.onChange]);
4384
+ const onInputBlur = () => {
4385
+ flushOnChange && flushOnChange();
4386
+ onBlur && onBlur();
4387
+ };
4388
+ const onInputFocus = () => {
4389
+ onFocus && onFocus();
4315
4390
  };
4316
4391
  return jsxs("div", {
4317
4392
  class: formFieldClasses(type$2, {
@@ -4333,9 +4408,9 @@ function Textfield(props) {
4333
4408
  disabled: disabled,
4334
4409
  readOnly: readonly,
4335
4410
  id: domId,
4336
- onInput: onChange,
4337
- onBlur: () => onBlur && onBlur(),
4338
- onFocus: () => onFocus && onFocus(),
4411
+ onInput: onInputChange,
4412
+ onBlur: onInputBlur,
4413
+ onFocus: onInputFocus,
4339
4414
  type: "text",
4340
4415
  value: value,
4341
4416
  "aria-describedby": errorMessageId
@@ -4394,13 +4469,20 @@ function Textarea(props) {
4394
4469
  required
4395
4470
  } = validate;
4396
4471
  const textareaRef = useRef();
4397
- const onInput = ({
4472
+ const [onInputChange, flushOnChange] = useFlushDebounce(({
4398
4473
  target
4399
4474
  }) => {
4400
4475
  props.onChange({
4401
4476
  field,
4402
4477
  value: target.value
4403
4478
  });
4479
+ }, [props.onChange]);
4480
+ const onInputBlur = () => {
4481
+ flushOnChange && flushOnChange();
4482
+ onBlur && onBlur();
4483
+ };
4484
+ const onInputFocus = () => {
4485
+ onFocus && onFocus();
4404
4486
  };
4405
4487
  useLayoutEffect(() => {
4406
4488
  autoSizeTextarea(textareaRef.current);
@@ -4423,9 +4505,9 @@ function Textarea(props) {
4423
4505
  disabled: disabled,
4424
4506
  readonly: readonly,
4425
4507
  id: domId,
4426
- onInput: onInput,
4427
- onBlur: () => onBlur && onBlur(),
4428
- onFocus: () => onFocus && onFocus(),
4508
+ onInput: onInputChange,
4509
+ onBlur: onInputBlur,
4510
+ onFocus: onInputFocus,
4429
4511
  value: value,
4430
4512
  ref: textareaRef,
4431
4513
  "aria-describedby": errorMessageId
@@ -7966,16 +8048,18 @@ class Form {
7966
8048
  */
7967
8049
  _createInjector(options, container) {
7968
8050
  const {
8051
+ modules = this._getModules(),
7969
8052
  additionalModules = [],
7970
- modules = this._getModules()
8053
+ ...config
7971
8054
  } = options;
7972
- const config = {
8055
+ const enrichedConfig = {
8056
+ ...config,
7973
8057
  renderer: {
7974
8058
  container
7975
8059
  }
7976
8060
  };
7977
8061
  return createInjector([{
7978
- config: ['value', config]
8062
+ config: ['value', enrichedConfig]
7979
8063
  }, {
7980
8064
  form: ['value', this]
7981
8065
  }, core, ...modules, ...additionalModules]);
@@ -8222,9 +8306,9 @@ function createForm(options) {
8222
8306
  const {
8223
8307
  data,
8224
8308
  schema,
8225
- ...rest
8309
+ ...formOptions
8226
8310
  } = options;
8227
- const form = new Form(rest);
8311
+ const form = new Form(formOptions);
8228
8312
  return form.importSchema(schema, data).then(function () {
8229
8313
  return form;
8230
8314
  });