@bpmn-io/form-js-viewer 1.7.0 → 1.7.2

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.cjs CHANGED
@@ -792,20 +792,59 @@ function useCondition(condition) {
792
792
  }, [conditionChecker, condition, expressionContextInfo]);
793
793
  }
794
794
 
795
- // parses the options data from the provided form field and form data
796
- function getOptionsData(formField, formData) {
795
+ /**
796
+ * Returns the options data for the provided if they can be simply determined, ignoring expression defined options.
797
+ *
798
+ * @param {object} formField
799
+ * @param {object} formData
800
+ */
801
+ function getSimpleOptionsData(formField, formData) {
797
802
  const {
803
+ valuesExpression: optionsExpression,
798
804
  valuesKey: optionsKey,
799
805
  values: staticOptions
800
806
  } = formField;
807
+ if (optionsExpression) {
808
+ return null;
809
+ }
801
810
  return optionsKey ? minDash.get(formData, [optionsKey]) : staticOptions;
802
811
  }
803
812
 
804
- // transforms the provided options into a normalized format, trimming invalid options
813
+ /**
814
+ * Normalizes the provided options data to a format that can be used by the select components.
815
+ * If the options data is not valid, it is filtered out.
816
+ *
817
+ * @param {any[]} optionsData
818
+ *
819
+ * @returns {object[]}
820
+ */
805
821
  function normalizeOptionsData(optionsData) {
806
822
  return optionsData.filter(_isAllowedValue).map(_normalizeOption).filter(o => !minDash.isNil(o));
807
823
  }
808
824
 
825
+ /**
826
+ * Creates an options object with default values if no options are provided.
827
+ *
828
+ * @param {object} options
829
+ *
830
+ * @returns {object}
831
+ */
832
+ function createEmptyOptions(options = {}) {
833
+ const defaults = {};
834
+
835
+ // provide default options if valuesKey and valuesExpression are not set
836
+ if (!options.valuesKey && !options.valuesExpression) {
837
+ defaults.values = [{
838
+ label: 'Value',
839
+ value: 'value'
840
+ }];
841
+ }
842
+ return {
843
+ ...defaults,
844
+ ...options
845
+ };
846
+ }
847
+
809
848
  /**
810
849
  * Converts the provided option to a normalized format.
811
850
  * If the option is not valid, null is returned.
@@ -856,21 +895,6 @@ function _isAllowedValue(value) {
856
895
  }
857
896
  return _isAllowedPrimitive(value);
858
897
  }
859
- function createEmptyOptions(options = {}) {
860
- const defaults = {};
861
-
862
- // provide default options if valuesKey and valuesExpression are not set
863
- if (!options.valuesKey && !options.valuesExpression) {
864
- defaults.values = [{
865
- label: 'Value',
866
- value: 'value'
867
- }];
868
- }
869
- return {
870
- ...defaults,
871
- ...options
872
- };
873
- }
874
898
 
875
899
  /**
876
900
  * Evaluate a string reactively based on the expressionLanguage and form data.
@@ -891,29 +915,19 @@ function useExpressionEvaluation(value) {
891
915
  }, [expressionLanguage, expressionContextInfo, value]);
892
916
  }
893
917
 
894
- function usePrevious(value, defaultValue = null) {
895
- const ref = hooks.useRef(defaultValue);
896
- hooks.useEffect(() => ref.current = value, [value]);
897
- return ref.current;
898
- }
899
-
900
918
  /**
901
919
  * A custom hook to manage state changes with deep comparison.
902
920
  *
903
- * @param {any} value - The current value to manage.
904
- * @param {any} defaultValue - The initial default value for the state.
905
- * @returns {any} - Returns the current state.
921
+ * @template T
922
+ * @param {T} value - The current value to manage.
923
+ * @returns {T} - Returns the current state.
906
924
  */
907
- function useDeepCompareState(value, defaultValue) {
908
- const [state, setState] = hooks.useState(defaultValue);
909
- const previous = usePrevious(value, defaultValue);
910
- const changed = !isEqual(previous, value);
911
- hooks.useEffect(() => {
912
- if (changed) {
913
- setState(value);
914
- }
915
- }, [changed, value]);
916
- return state;
925
+ function useDeepCompareMemoize(value) {
926
+ const ref = hooks.useRef();
927
+ if (!isEqual(value, ref.current)) {
928
+ ref.current = value;
929
+ }
930
+ return ref.current;
917
931
  }
918
932
 
919
933
  /**
@@ -943,15 +957,10 @@ function useOptionsAsync(field) {
943
957
  valuesKey: optionsKey,
944
958
  values: staticOptions
945
959
  } = field;
946
- const [optionsGetter, setOptionsGetter] = hooks.useState({
947
- options: [],
948
- error: undefined,
949
- loadState: LOAD_STATES.LOADING
950
- });
951
960
  const initialData = useService('form')._getState().initialData;
952
961
  const expressionEvaluation = useExpressionEvaluation(optionsExpression);
953
- const evaluatedOptions = useDeepCompareState(expressionEvaluation || [], []);
954
- hooks.useEffect(() => {
962
+ const evaluatedOptions = useDeepCompareMemoize(expressionEvaluation || []);
963
+ const optionsGetter = hooks.useMemo(() => {
955
964
  let options = [];
956
965
 
957
966
  // dynamic options
@@ -966,18 +975,16 @@ function useOptionsAsync(field) {
966
975
  options = Array.isArray(staticOptions) ? staticOptions : [];
967
976
 
968
977
  // expression
969
- } else if (optionsExpression) {
970
- if (evaluatedOptions && Array.isArray(evaluatedOptions)) {
971
- options = evaluatedOptions;
972
- }
978
+ } else if (optionsExpression && evaluatedOptions && Array.isArray(evaluatedOptions)) {
979
+ options = evaluatedOptions;
980
+
981
+ // error case
973
982
  } else {
974
- setOptionsGetter(buildErrorState('No options source defined in the form definition'));
975
- return;
983
+ return buildErrorState('No options source defined in the form definition');
976
984
  }
977
985
 
978
986
  // normalize data to support primitives and partially defined objects
979
- options = normalizeOptionsData(options);
980
- setOptionsGetter(buildLoadedState(options));
987
+ return buildLoadedState(normalizeOptionsData(options));
981
988
  }, [optionsKey, staticOptions, initialData, optionsExpression, evaluatedOptions]);
982
989
  return optionsGetter;
983
990
  }
@@ -1022,14 +1029,14 @@ const getDOMPurifyConfig = sanitizeStyleTags => {
1022
1029
  };
1023
1030
  };
1024
1031
 
1025
- /**
1026
- * A custom hook to build up security attributes from form configuration.
1027
- *
1028
- * @param {Object} security - The security configuration.
1029
- * @returns {Array} - Returns a tuple with sandbox and allow attributes.
1032
+ /**
1033
+ * A custom hook to build up security attributes from form configuration.
1034
+ *
1035
+ * @param {Object} security - The security configuration.
1036
+ * @returns {Array} - Returns a tuple with sandbox and allow attributes.
1030
1037
  */
1031
1038
  function useSecurityAttributesMap(security) {
1032
- const securityMemoized = useDeepCompareState(security);
1039
+ const securityMemoized = useDeepCompareMemoize(security);
1033
1040
  const sandbox = hooks.useMemo(() => SECURITY_ATTRIBUTES_DEFINITIONS.filter(({
1034
1041
  attribute
1035
1042
  }) => attribute === SANDBOX_ATTRIBUTE).filter(({
@@ -1201,6 +1208,12 @@ function useReadonly(formField, properties = {}) {
1201
1208
  return readonly || false;
1202
1209
  }
1203
1210
 
1211
+ function usePrevious(value, defaultValue = null) {
1212
+ const ref = hooks.useRef(defaultValue);
1213
+ hooks.useEffect(() => ref.current = value, [value]);
1214
+ return ref.current;
1215
+ }
1216
+
1204
1217
  function useFlushDebounce(func) {
1205
1218
  const timeoutRef = hooks.useRef(null);
1206
1219
  const lastArgsRef = hooks.useRef(null);
@@ -1465,8 +1478,16 @@ function sanitizeSingleSelectValue(options) {
1465
1478
  data,
1466
1479
  value
1467
1480
  } = options;
1481
+ const {
1482
+ valuesExpression: optionsExpression
1483
+ } = formField;
1468
1484
  try {
1469
- const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1485
+ // if options are expression evaluated, we don't need to sanitize the value against the options
1486
+ // and defer to the field's internal validation
1487
+ if (optionsExpression) {
1488
+ return value;
1489
+ }
1490
+ const validValues = normalizeOptionsData(getSimpleOptionsData(formField, data)).map(v => v.value);
1470
1491
  return hasEqualValue(value, validValues) ? value : null;
1471
1492
  } catch (error) {
1472
1493
  // use default value in case of formatting error
@@ -1480,8 +1501,16 @@ function sanitizeMultiSelectValue(options) {
1480
1501
  data,
1481
1502
  value
1482
1503
  } = options;
1504
+ const {
1505
+ valuesExpression: optionsExpression
1506
+ } = formField;
1483
1507
  try {
1484
- const validValues = normalizeOptionsData(getOptionsData(formField, data)).map(v => v.value);
1508
+ // if options are expression evaluated, we don't need to sanitize the values against the options
1509
+ // and defer to the field's internal validation
1510
+ if (optionsExpression) {
1511
+ return value;
1512
+ }
1513
+ const validValues = normalizeOptionsData(getSimpleOptionsData(formField, data)).map(v => v.value);
1485
1514
  return value.filter(v => hasEqualValue(v, validValues));
1486
1515
  } catch (error) {
1487
1516
  // use default value in case of formatting error
@@ -1569,7 +1598,7 @@ function useCleanupMultiSelectValue(props) {
1569
1598
  onChange,
1570
1599
  values
1571
1600
  } = props;
1572
- const memoizedValues = useDeepCompareState(values, []);
1601
+ const memoizedValues = useDeepCompareMemoize(values || []);
1573
1602
 
1574
1603
  // ensures that the values are always a subset of the possible options
1575
1604
  hooks.useEffect(() => {
@@ -1589,7 +1618,8 @@ function useCleanupMultiSelectValue(props) {
1589
1618
 
1590
1619
  function Description(props) {
1591
1620
  const {
1592
- description
1621
+ description,
1622
+ id
1593
1623
  } = props;
1594
1624
  const evaluatedDescription = useSingleLineTemplateEvaluation(description || '', {
1595
1625
  debug: true
@@ -1598,6 +1628,7 @@ function Description(props) {
1598
1628
  return null;
1599
1629
  }
1600
1630
  return jsxRuntime.jsx("div", {
1631
+ id: id,
1601
1632
  class: "fjs-form-field-description",
1602
1633
  children: evaluatedDescription
1603
1634
  });
@@ -1628,6 +1659,7 @@ function Errors(props) {
1628
1659
  function Label(props) {
1629
1660
  const {
1630
1661
  id,
1662
+ htmlFor,
1631
1663
  label,
1632
1664
  collapseOnEmpty = true,
1633
1665
  required = false
@@ -1636,12 +1668,14 @@ function Label(props) {
1636
1668
  debug: true
1637
1669
  });
1638
1670
  return jsxRuntime.jsxs("label", {
1639
- for: id,
1671
+ id: id,
1672
+ for: htmlFor,
1640
1673
  class: classNames('fjs-form-field-label', {
1641
1674
  'fjs-incollapsible-label': !collapseOnEmpty
1642
1675
  }, props['class']),
1643
1676
  children: [props.children, evaluatedLabel, required && jsxRuntime.jsx("span", {
1644
1677
  class: "fjs-asterix",
1678
+ "aria-hidden": true,
1645
1679
  children: "*"
1646
1680
  })]
1647
1681
  });
@@ -1652,7 +1686,6 @@ function Checkbox(props) {
1652
1686
  const {
1653
1687
  disabled,
1654
1688
  errors = [],
1655
- errorMessageId,
1656
1689
  domId,
1657
1690
  onBlur,
1658
1691
  onFocus,
@@ -1676,6 +1709,8 @@ function Checkbox(props) {
1676
1709
  value: target.checked
1677
1710
  });
1678
1711
  };
1712
+ const descriptionId = `${domId}-description`;
1713
+ const errorMessageId = `${domId}-error-message`;
1679
1714
  return jsxRuntime.jsxs("div", {
1680
1715
  class: classNames(formFieldClasses(type$f, {
1681
1716
  errors,
@@ -1685,7 +1720,7 @@ function Checkbox(props) {
1685
1720
  'fjs-checked': value
1686
1721
  }),
1687
1722
  children: [jsxRuntime.jsx(Label, {
1688
- id: domId,
1723
+ htmlFor: domId,
1689
1724
  label: label,
1690
1725
  required: required,
1691
1726
  children: jsxRuntime.jsx("input", {
@@ -1698,13 +1733,16 @@ function Checkbox(props) {
1698
1733
  onChange: onChange,
1699
1734
  onBlur: () => onBlur && onBlur(),
1700
1735
  onFocus: () => onFocus && onFocus(),
1701
- "aria-describedby": errorMessageId
1736
+ required: required,
1737
+ "aria-invalid": errors.length > 0,
1738
+ "aria-describedby": [descriptionId, errorMessageId].join(' ')
1702
1739
  })
1703
1740
  }), jsxRuntime.jsx(Description, {
1741
+ id: descriptionId,
1704
1742
  description: description
1705
1743
  }), jsxRuntime.jsx(Errors, {
1706
- errors: errors,
1707
- id: errorMessageId
1744
+ id: errorMessageId,
1745
+ errors: errors
1708
1746
  })]
1709
1747
  });
1710
1748
  }
@@ -1727,7 +1765,6 @@ function Checklist(props) {
1727
1765
  const {
1728
1766
  disabled,
1729
1767
  errors = [],
1730
- errorMessageId,
1731
1768
  domId,
1732
1769
  onBlur,
1733
1770
  onFocus,
@@ -1774,6 +1811,8 @@ function Checklist(props) {
1774
1811
  values,
1775
1812
  onChange: props.onChange
1776
1813
  });
1814
+ const descriptionId = `${domId}-description`;
1815
+ const errorMessageId = `${domId}-error-message`;
1777
1816
  return jsxRuntime.jsxs("div", {
1778
1817
  class: classNames(formFieldClasses(type$e, {
1779
1818
  errors,
@@ -1788,7 +1827,7 @@ function Checklist(props) {
1788
1827
  const itemDomId = `${domId}-${index}`;
1789
1828
  const isChecked = hasEqualValue(o.value, values);
1790
1829
  return jsxRuntime.jsx(Label, {
1791
- id: itemDomId,
1830
+ htmlFor: itemDomId,
1792
1831
  label: o.label,
1793
1832
  class: classNames({
1794
1833
  'fjs-checked': isChecked
@@ -1804,14 +1843,17 @@ function Checklist(props) {
1804
1843
  onClick: () => toggleCheckbox(o.value),
1805
1844
  onBlur: onCheckboxBlur,
1806
1845
  onFocus: onCheckboxFocus,
1807
- "aria-describedby": errorMessageId
1846
+ required: required,
1847
+ "aria-invalid": errors.length > 0,
1848
+ "aria-describedby": [descriptionId, errorMessageId].join(' ')
1808
1849
  })
1809
1850
  });
1810
1851
  }), jsxRuntime.jsx(Description, {
1852
+ id: descriptionId,
1811
1853
  description: description
1812
1854
  }), jsxRuntime.jsx(Errors, {
1813
- errors: errors,
1814
- id: errorMessageId
1855
+ id: errorMessageId,
1856
+ errors: errors
1815
1857
  })]
1816
1858
  });
1817
1859
  }
@@ -1891,6 +1933,7 @@ function FormField(props) {
1891
1933
  }
1892
1934
  }, [viewerCommands, field, initialValue, initialValidationTrigger, indexes]);
1893
1935
  const onBlur = hooks.useCallback(() => {
1936
+ const value = minDash.get(data, valuePath);
1894
1937
  if (initialValidationTrigger) {
1895
1938
  setInitialValidationTrigger(false);
1896
1939
  viewerCommands.updateFieldValidation(field, value, indexes);
@@ -1898,7 +1941,7 @@ function FormField(props) {
1898
1941
  eventBus.fire('formField.blur', {
1899
1942
  formField: field
1900
1943
  });
1901
- }, [eventBus, field, indexes, value, viewerCommands, initialValidationTrigger]);
1944
+ }, [eventBus, field, indexes, viewerCommands, initialValidationTrigger, data, valuePath]);
1902
1945
  const onFocus = hooks.useCallback(() => {
1903
1946
  eventBus.fire('formField.focus', {
1904
1947
  formField: field
@@ -1922,7 +1965,6 @@ function FormField(props) {
1922
1965
  }
1923
1966
  const domId = `${prefixId(field.id, formId, indexes)}`;
1924
1967
  const fieldErrors = minDash.get(errors, [field.id, ...Object.values(indexes || {})]) || [];
1925
- const errorMessageId = errors.length === 0 ? undefined : `${domId}-error-message`;
1926
1968
  return jsxRuntime.jsx(Column, {
1927
1969
  field: field,
1928
1970
  class: gridColumnClasses(field),
@@ -1933,7 +1975,6 @@ function FormField(props) {
1933
1975
  ...props,
1934
1976
  disabled: disabled,
1935
1977
  errors: fieldErrors,
1936
- errorMessageId: errorMessageId,
1937
1978
  domId: domId,
1938
1979
  onChange: disabled || readonly ? noop$1 : onChangeIndexed,
1939
1980
  onBlur: disabled || readonly ? noop$1 : onBlur,
@@ -2245,7 +2286,7 @@ function Datepicker(props) {
2245
2286
  const [forceFocusCalendar, setForceFocusCalendar] = hooks.useState(false);
2246
2287
 
2247
2288
  // ensures we render based on date value instead of reference
2248
- const date = useDeepCompareState(dateObject, null);
2289
+ const date = useDeepCompareMemoize(dateObject);
2249
2290
 
2250
2291
  // shorts the date value back to the source
2251
2292
  hooks.useEffect(() => {
@@ -2336,7 +2377,7 @@ function Datepicker(props) {
2336
2377
  return jsxRuntime.jsxs("div", {
2337
2378
  class: "fjs-datetime-subsection",
2338
2379
  children: [jsxRuntime.jsx(Label, {
2339
- id: domId,
2380
+ htmlFor: domId,
2340
2381
  label: label,
2341
2382
  collapseOnEmpty: collapseLabelOnEmpty,
2342
2383
  required: required
@@ -2608,7 +2649,7 @@ function Timepicker(props) {
2608
2649
  return jsxRuntime.jsxs("div", {
2609
2650
  class: "fjs-datetime-subsection",
2610
2651
  children: [jsxRuntime.jsx(Label, {
2611
- id: domId,
2652
+ htmlFor: domId,
2612
2653
  label: label,
2613
2654
  collapseOnEmpty: collapseLabelOnEmpty,
2614
2655
  required: required
@@ -2795,6 +2836,7 @@ function Datetime(props) {
2795
2836
  });
2796
2837
  }, []);
2797
2838
  const errorMessageId = allErrors.length === 0 ? undefined : `${prefixId(id, formId)}-error-message`;
2839
+ const descriptionId = `${prefixId(id, formId)}-description`;
2798
2840
  const datePickerProps = {
2799
2841
  label: dateLabel,
2800
2842
  collapseLabelOnEmpty: !timeLabel,
@@ -2807,7 +2849,7 @@ function Datetime(props) {
2807
2849
  date: dateTime.date,
2808
2850
  readonly,
2809
2851
  setDate,
2810
- 'aria-describedby': errorMessageId
2852
+ 'aria-describedby': [descriptionId, errorMessageId].join(' ')
2811
2853
  };
2812
2854
  const timePickerProps = {
2813
2855
  label: timeLabel,
@@ -2822,7 +2864,7 @@ function Datetime(props) {
2822
2864
  timeInterval,
2823
2865
  time: dateTime.time,
2824
2866
  setTime,
2825
- 'aria-describedby': errorMessageId
2867
+ 'aria-describedby': [descriptionId, errorMessageId].join(' ')
2826
2868
  };
2827
2869
  return jsxRuntime.jsxs("div", {
2828
2870
  class: formFieldClasses(type$d, {
@@ -2841,6 +2883,7 @@ function Datetime(props) {
2841
2883
  ...timePickerProps
2842
2884
  })]
2843
2885
  }), jsxRuntime.jsx(Description, {
2886
+ id: descriptionId,
2844
2887
  description: description
2845
2888
  }), jsxRuntime.jsx(Errors, {
2846
2889
  errors: allErrors,
@@ -2944,7 +2987,7 @@ function IFrame(props) {
2944
2987
  readonly
2945
2988
  }),
2946
2989
  children: [jsxRuntime.jsx(Label, {
2947
- id: domId,
2990
+ htmlFor: domId,
2948
2991
  label: evaluatedLabel
2949
2992
  }), !evaluatedUrl && jsxRuntime.jsx(IFramePlaceholder, {
2950
2993
  text: "No content to show."
@@ -3490,7 +3533,6 @@ function Numberfield(props) {
3490
3533
  const {
3491
3534
  disabled,
3492
3535
  errors = [],
3493
- errorMessageId,
3494
3536
  domId,
3495
3537
  onBlur,
3496
3538
  onFocus,
@@ -3628,6 +3670,8 @@ function Numberfield(props) {
3628
3670
  e.preventDefault();
3629
3671
  }
3630
3672
  };
3673
+ const descriptionId = `${domId}-description`;
3674
+ const errorMessageId = `${domId}-error-message`;
3631
3675
  return jsxRuntime.jsxs("div", {
3632
3676
  class: formFieldClasses(type$a, {
3633
3677
  errors,
@@ -3635,7 +3679,7 @@ function Numberfield(props) {
3635
3679
  readonly
3636
3680
  }),
3637
3681
  children: [jsxRuntime.jsx(Label, {
3638
- id: domId,
3682
+ htmlFor: domId,
3639
3683
  label: label,
3640
3684
  required: required
3641
3685
  }), jsxRuntime.jsx(TemplatedInputAdorner, {
@@ -3669,15 +3713,17 @@ function Numberfield(props) {
3669
3713
  autoComplete: "off",
3670
3714
  step: incrementAmount,
3671
3715
  value: displayValue,
3672
- "aria-describedby": errorMessageId
3716
+ "aria-describedby": [descriptionId, errorMessageId].join(' '),
3717
+ required: required,
3718
+ "aria-invalid": errors.length > 0
3673
3719
  }), jsxRuntime.jsxs("div", {
3674
3720
  class: classNames('fjs-number-arrow-container', {
3675
3721
  'fjs-disabled': disabled,
3676
3722
  'fjs-readonly': readonly
3677
3723
  }),
3678
3724
  children: [jsxRuntime.jsx("button", {
3679
- class: "fjs-number-arrow-up",
3680
3725
  type: "button",
3726
+ class: "fjs-number-arrow-up",
3681
3727
  "aria-label": "Increment",
3682
3728
  onClick: () => increment(),
3683
3729
  tabIndex: -1,
@@ -3685,8 +3731,8 @@ function Numberfield(props) {
3685
3731
  }), jsxRuntime.jsx("div", {
3686
3732
  class: "fjs-number-arrow-separator"
3687
3733
  }), jsxRuntime.jsx("button", {
3688
- class: "fjs-number-arrow-down",
3689
3734
  type: "button",
3735
+ class: "fjs-number-arrow-down",
3690
3736
  "aria-label": "Decrement",
3691
3737
  onClick: () => decrement(),
3692
3738
  tabIndex: -1,
@@ -3695,10 +3741,11 @@ function Numberfield(props) {
3695
3741
  })]
3696
3742
  })
3697
3743
  }), jsxRuntime.jsx(Description, {
3744
+ id: descriptionId,
3698
3745
  description: description
3699
3746
  }), jsxRuntime.jsx(Errors, {
3700
- errors: errors,
3701
- id: errorMessageId
3747
+ id: errorMessageId,
3748
+ errors: errors
3702
3749
  })]
3703
3750
  });
3704
3751
  }
@@ -3728,7 +3775,6 @@ function Radio(props) {
3728
3775
  const {
3729
3776
  disabled,
3730
3777
  errors = [],
3731
- errorMessageId,
3732
3778
  domId,
3733
3779
  onBlur,
3734
3780
  onFocus,
@@ -3774,6 +3820,8 @@ function Radio(props) {
3774
3820
  value,
3775
3821
  onChange: props.onChange
3776
3822
  });
3823
+ const descriptionId = `${domId}-description`;
3824
+ const errorMessageId = `${domId}-error-message`;
3777
3825
  return jsxRuntime.jsxs("div", {
3778
3826
  class: formFieldClasses(type$9, {
3779
3827
  errors,
@@ -3788,7 +3836,7 @@ function Radio(props) {
3788
3836
  const itemDomId = `${domId}-${index}`;
3789
3837
  const isChecked = isEqual(option.value, value);
3790
3838
  return jsxRuntime.jsx(Label, {
3791
- id: itemDomId,
3839
+ htmlFor: itemDomId,
3792
3840
  label: option.label,
3793
3841
  class: classNames({
3794
3842
  'fjs-checked': isChecked
@@ -3804,14 +3852,17 @@ function Radio(props) {
3804
3852
  onClick: () => onChange(option.value),
3805
3853
  onBlur: onRadioBlur,
3806
3854
  onFocus: onRadioFocus,
3807
- "aria-describedby": errorMessageId
3855
+ "aria-describedby": [descriptionId, errorMessageId].join(' '),
3856
+ required: required,
3857
+ "aria-invalid": errors.length > 0
3808
3858
  })
3809
3859
  }, index);
3810
3860
  }), jsxRuntime.jsx(Description, {
3861
+ id: descriptionId,
3811
3862
  description: description
3812
3863
  }), jsxRuntime.jsx(Errors, {
3813
- errors: errors,
3814
- id: errorMessageId
3864
+ id: errorMessageId,
3865
+ errors: errors
3815
3866
  })]
3816
3867
  });
3817
3868
  }
@@ -3876,7 +3927,7 @@ function SearchableSelect(props) {
3876
3927
 
3877
3928
  // whenever we change the underlying value, set the label to it
3878
3929
  hooks.useEffect(() => {
3879
- setFilter(label);
3930
+ setFilter(label || '');
3880
3931
  }, [label]);
3881
3932
  const filteredOptions = hooks.useMemo(() => {
3882
3933
  if (loadState !== LOAD_STATES.LOADED) {
@@ -3956,7 +4007,7 @@ function SearchableSelect(props) {
3956
4007
  }, [onFocus]);
3957
4008
  const onInputBlur = hooks.useCallback(() => {
3958
4009
  setIsDropdownExpanded(false);
3959
- setFilter(label);
4010
+ setFilter(label || '');
3960
4011
  onBlur && onBlur();
3961
4012
  }, [onBlur, label]);
3962
4013
  return jsxRuntime.jsxs(jsxRuntime.Fragment, {
@@ -4052,6 +4103,9 @@ function SimpleSelect(props) {
4052
4103
  }, [disabled, isDropdownExpanded, loadState, readonly, value]);
4053
4104
  const onMouseDown = hooks.useCallback(e => {
4054
4105
  const input = inputRef.current;
4106
+ if (disabled || !input) {
4107
+ return;
4108
+ }
4055
4109
  setIsDropdownExpanded(!isDropdownExpanded);
4056
4110
  if (isDropdownExpanded) {
4057
4111
  input.blur();
@@ -4059,7 +4113,7 @@ function SimpleSelect(props) {
4059
4113
  input.focus();
4060
4114
  }
4061
4115
  e.preventDefault();
4062
- }, [isDropdownExpanded]);
4116
+ }, [disabled, isDropdownExpanded]);
4063
4117
  const initialFocusIndex = hooks.useMemo(() => value && minDash.findIndex(options, o => o.value === value) || 0, [options, value]);
4064
4118
  const onInputFocus = hooks.useCallback(() => {
4065
4119
  if (!readonly) {
@@ -4131,7 +4185,6 @@ function Select(props) {
4131
4185
  const {
4132
4186
  disabled,
4133
4187
  errors = [],
4134
- errorMessageId,
4135
4188
  domId,
4136
4189
  onBlur,
4137
4190
  onFocus,
@@ -4149,6 +4202,8 @@ function Select(props) {
4149
4202
  const {
4150
4203
  required
4151
4204
  } = validate;
4205
+ const descriptionId = `${domId}-description`;
4206
+ const errorMessageId = `${domId}-error-message`;
4152
4207
  const selectProps = {
4153
4208
  domId,
4154
4209
  disabled,
@@ -4159,7 +4214,9 @@ function Select(props) {
4159
4214
  value,
4160
4215
  onChange,
4161
4216
  readonly,
4162
- 'aria-describedby': errorMessageId
4217
+ required,
4218
+ 'aria-invalid': errors.length > 0,
4219
+ 'aria-describedby': [descriptionId, errorMessageId].join(' ')
4163
4220
  };
4164
4221
  return jsxRuntime.jsxs("div", {
4165
4222
  class: formFieldClasses(type$8, {
@@ -4174,7 +4231,7 @@ function Select(props) {
4174
4231
  }
4175
4232
  },
4176
4233
  children: [jsxRuntime.jsx(Label, {
4177
- id: domId,
4234
+ htmlFor: domId,
4178
4235
  label: label,
4179
4236
  required: required
4180
4237
  }), searchable ? jsxRuntime.jsx(SearchableSelect, {
@@ -4182,10 +4239,11 @@ function Select(props) {
4182
4239
  }) : jsxRuntime.jsx(SimpleSelect, {
4183
4240
  ...selectProps
4184
4241
  }), jsxRuntime.jsx(Description, {
4242
+ id: descriptionId,
4185
4243
  description: description
4186
4244
  }), jsxRuntime.jsx(Errors, {
4187
- errors: errors,
4188
- id: errorMessageId
4245
+ id: errorMessageId,
4246
+ errors: errors
4189
4247
  })]
4190
4248
  });
4191
4249
  }
@@ -4318,7 +4376,6 @@ function Taglist(props) {
4318
4376
  const {
4319
4377
  disabled,
4320
4378
  errors = [],
4321
- errorMessageId,
4322
4379
  onFocus,
4323
4380
  domId,
4324
4381
  onBlur,
@@ -4346,7 +4403,7 @@ function Taglist(props) {
4346
4403
  } = useOptionsAsync(field);
4347
4404
 
4348
4405
  // ensures we render based on array content instead of reference
4349
- const values = useDeepCompareState(value || [], []);
4406
+ const values = useDeepCompareMemoize(value || []);
4350
4407
  useCleanupMultiSelectValue({
4351
4408
  field,
4352
4409
  loadState,
@@ -4454,6 +4511,8 @@ function Taglist(props) {
4454
4511
  inputRef.current.focus();
4455
4512
  };
4456
4513
  const shouldDisplayDropdown = hooks.useMemo(() => !disabled && loadState === LOAD_STATES.LOADED && isDropdownExpanded && !isEscapeClosed, [disabled, isDropdownExpanded, isEscapeClosed, loadState]);
4514
+ const descriptionId = `${domId}-description`;
4515
+ const errorMessageId = `${domId}-error-message`;
4457
4516
  return jsxRuntime.jsxs("div", {
4458
4517
  ref: focusScopeRef,
4459
4518
  class: formFieldClasses(type$5, {
@@ -4470,7 +4529,7 @@ function Taglist(props) {
4470
4529
  children: [jsxRuntime.jsx(Label, {
4471
4530
  label: label,
4472
4531
  required: required,
4473
- id: domId
4532
+ htmlFor: domId
4474
4533
  }), !disabled && !readonly && !!values.length && jsxRuntime.jsx(SkipLink, {
4475
4534
  className: "fjs-taglist-skip-link",
4476
4535
  label: "Skip to search",
@@ -4518,7 +4577,9 @@ function Taglist(props) {
4518
4577
  onMouseDown: () => setIsEscapeClose(false),
4519
4578
  onFocus: onInputFocus,
4520
4579
  onBlur: onInputBlur,
4521
- "aria-describedby": errorMessageId
4580
+ "aria-describedby": [descriptionId, errorMessageId].join(' '),
4581
+ required: required,
4582
+ "aria-invalid": errors.length > 0
4522
4583
  })]
4523
4584
  }), jsxRuntime.jsx("div", {
4524
4585
  class: "fjs-taglist-anchor",
@@ -4530,10 +4591,11 @@ function Taglist(props) {
4530
4591
  listenerElement: inputRef.current
4531
4592
  })
4532
4593
  }), jsxRuntime.jsx(Description, {
4594
+ id: descriptionId,
4533
4595
  description: description
4534
4596
  }), jsxRuntime.jsx(Errors, {
4535
- errors: errors,
4536
- id: errorMessageId
4597
+ id: errorMessageId,
4598
+ errors: errors
4537
4599
  })]
4538
4600
  });
4539
4601
  }
@@ -4850,7 +4912,6 @@ function Textfield(props) {
4850
4912
  const {
4851
4913
  disabled,
4852
4914
  errors = [],
4853
- errorMessageId,
4854
4915
  domId,
4855
4916
  onBlur,
4856
4917
  onFocus,
@@ -4886,6 +4947,8 @@ function Textfield(props) {
4886
4947
  const onInputFocus = () => {
4887
4948
  onFocus && onFocus();
4888
4949
  };
4950
+ const descriptionId = `${domId}-description`;
4951
+ const errorMessageId = `${domId}-error-message`;
4889
4952
  return jsxRuntime.jsxs("div", {
4890
4953
  class: formFieldClasses(type$2, {
4891
4954
  errors,
@@ -4893,7 +4956,7 @@ function Textfield(props) {
4893
4956
  readonly
4894
4957
  }),
4895
4958
  children: [jsxRuntime.jsx(Label, {
4896
- id: domId,
4959
+ htmlFor: domId,
4897
4960
  label: label,
4898
4961
  required: required
4899
4962
  }), jsxRuntime.jsx(TemplatedInputAdorner, {
@@ -4911,13 +4974,16 @@ function Textfield(props) {
4911
4974
  onFocus: onInputFocus,
4912
4975
  type: "text",
4913
4976
  value: value,
4914
- "aria-describedby": errorMessageId
4977
+ "aria-describedby": [descriptionId, errorMessageId].join(' '),
4978
+ required: required,
4979
+ "aria-invalid": errors.length > 0
4915
4980
  })
4916
4981
  }), jsxRuntime.jsx(Description, {
4982
+ id: descriptionId,
4917
4983
  description: description
4918
4984
  }), jsxRuntime.jsx(Errors, {
4919
- errors: errors,
4920
- id: errorMessageId
4985
+ id: errorMessageId,
4986
+ errors: errors
4921
4987
  })]
4922
4988
  });
4923
4989
  }
@@ -4950,7 +5016,6 @@ function Textarea(props) {
4950
5016
  const {
4951
5017
  disabled,
4952
5018
  errors = [],
4953
- errorMessageId,
4954
5019
  domId,
4955
5020
  onBlur,
4956
5021
  onFocus,
@@ -4994,6 +5059,8 @@ function Textarea(props) {
4994
5059
  hooks.useEffect(() => {
4995
5060
  autoSizeTextarea(textareaRef.current);
4996
5061
  }, []);
5062
+ const descriptionId = `${domId}-description`;
5063
+ const errorMessageId = `${domId}-error-message`;
4997
5064
  return jsxRuntime.jsxs("div", {
4998
5065
  class: formFieldClasses(type$1, {
4999
5066
  errors,
@@ -5001,7 +5068,7 @@ function Textarea(props) {
5001
5068
  readonly
5002
5069
  }),
5003
5070
  children: [jsxRuntime.jsx(Label, {
5004
- id: domId,
5071
+ htmlFor: domId,
5005
5072
  label: label,
5006
5073
  required: required
5007
5074
  }), jsxRuntime.jsx("textarea", {
@@ -5014,12 +5081,15 @@ function Textarea(props) {
5014
5081
  onFocus: onInputFocus,
5015
5082
  value: value,
5016
5083
  ref: textareaRef,
5017
- "aria-describedby": errorMessageId
5084
+ "aria-describedby": [descriptionId, errorMessageId].join(' '),
5085
+ required: required,
5086
+ "aria-invalid": errors.length > 0
5018
5087
  }), jsxRuntime.jsx(Description, {
5088
+ id: descriptionId,
5019
5089
  description: description
5020
5090
  }), jsxRuntime.jsx(Errors, {
5021
- errors: errors,
5022
- id: errorMessageId
5091
+ id: errorMessageId,
5092
+ errors: errors
5023
5093
  })]
5024
5094
  });
5025
5095
  }
@@ -5188,7 +5258,7 @@ function Table(props) {
5188
5258
  return jsxRuntime.jsxs("div", {
5189
5259
  class: formFieldClasses(type),
5190
5260
  children: [jsxRuntime.jsx(Label, {
5191
- id: prefixId(id),
5261
+ htmlFor: prefixId(id),
5192
5262
  label: label
5193
5263
  }), jsxRuntime.jsxs("div", {
5194
5264
  class: classNames('fjs-table-middle-container', {
@@ -5588,7 +5658,7 @@ class FormFields {
5588
5658
  }
5589
5659
 
5590
5660
  const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression', 'url', 'dataSource', 'columnsExpression'];
5591
- const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text', 'url'];
5661
+ const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text', 'content', 'url'];
5592
5662
 
5593
5663
  /**
5594
5664
  * @typedef { import('../types').Schema } Schema
@@ -6574,47 +6644,17 @@ class RepeatRenderManager {
6574
6644
  };
6575
6645
  const parentExpressionContextInfo = hooks.useContext(LocalExpressionContext);
6576
6646
  return jsxRuntime.jsx(jsxRuntime.Fragment, {
6577
- children: displayValues.map((value, index) => {
6578
- const elementProps = hooks.useMemo(() => ({
6579
- ...restProps,
6580
- indexes: {
6581
- ...(indexes || {}),
6582
- [repeaterField.id]: index
6583
- }
6584
- }), [index]);
6585
- const localExpressionContextInfo = hooks.useMemo(() => ({
6586
- data: parentExpressionContextInfo.data,
6587
- this: value,
6588
- parent: buildExpressionContext(parentExpressionContextInfo),
6589
- i: [...parentExpressionContextInfo.i, index + 1]
6590
- }), [index, value]);
6591
- return !showRemove ? jsxRuntime.jsx(LocalExpressionContext.Provider, {
6592
- value: localExpressionContextInfo,
6593
- children: jsxRuntime.jsx(RowsRenderer, {
6594
- ...elementProps
6595
- })
6596
- }) : jsxRuntime.jsxs("div", {
6597
- class: "fjs-repeat-row-container",
6598
- children: [jsxRuntime.jsx("div", {
6599
- class: "fjs-repeat-row-rows",
6600
- children: jsxRuntime.jsx(LocalExpressionContext.Provider, {
6601
- value: localExpressionContextInfo,
6602
- children: jsxRuntime.jsx(RowsRenderer, {
6603
- ...elementProps
6604
- })
6605
- })
6606
- }), jsxRuntime.jsx("button", {
6607
- class: "fjs-repeat-row-remove",
6608
- type: "button",
6609
- "aria-label": `Remove list item ${index + 1}`,
6610
- onClick: () => onDeleteItem(index),
6611
- children: jsxRuntime.jsx("div", {
6612
- class: "fjs-repeat-row-remove-icon-container",
6613
- children: jsxRuntime.jsx(DeleteSvg, {})
6614
- })
6615
- })]
6616
- });
6617
- })
6647
+ children: displayValues.map((value, index) => jsxRuntime.jsx(RepetitionScaffold, {
6648
+ index: index,
6649
+ value: value,
6650
+ parentExpressionContextInfo: parentExpressionContextInfo,
6651
+ repeaterField: repeaterField,
6652
+ RowsRenderer: RowsRenderer,
6653
+ indexes: indexes,
6654
+ onDeleteItem: onDeleteItem,
6655
+ showRemove: showRemove,
6656
+ ...restProps
6657
+ }, index))
6618
6658
  });
6619
6659
  }
6620
6660
  RepeatFooter(props) {
@@ -6677,18 +6717,18 @@ class RepeatRenderManager {
6677
6717
  'fjs-remove-allowed': repeaterField.allowAddRemove
6678
6718
  }),
6679
6719
  children: [showAdd ? jsxRuntime.jsx("button", {
6720
+ type: "button",
6680
6721
  readOnly: readonly,
6681
6722
  disabled: disabled || readonly,
6682
6723
  class: "fjs-repeat-render-add",
6683
- type: "button",
6684
6724
  ref: addButtonRef,
6685
6725
  onClick: onAddItem,
6686
6726
  children: jsxRuntime.jsxs(jsxRuntime.Fragment, {
6687
6727
  children: [jsxRuntime.jsx(AddSvg, {}), " ", 'Add new']
6688
6728
  })
6689
6729
  }) : null, collapseEnabled ? jsxRuntime.jsx("button", {
6690
- class: "fjs-repeat-render-collapse",
6691
6730
  type: "button",
6731
+ class: "fjs-repeat-render-collapse",
6692
6732
  onClick: toggle,
6693
6733
  children: isCollapsed ? jsxRuntime.jsxs(jsxRuntime.Fragment, {
6694
6734
  children: [jsxRuntime.jsx(ExpandSvg, {}), " ", `Expand all (${values.length})`]
@@ -6706,6 +6746,73 @@ class RepeatRenderManager {
6706
6746
  return nonCollapsedItems ? nonCollapsedItems : DEFAULT_NON_COLLAPSED_ITEMS;
6707
6747
  }
6708
6748
  }
6749
+
6750
+ /**
6751
+ * Individual repetition of a repeated field and context scaffolding.
6752
+ *
6753
+ * @param {Object} props
6754
+ * @param {number} props.index
6755
+ * @param {Object} props.value
6756
+ * @param {Object} props.parentExpressionContextInfo
6757
+ * @param {Object} props.repeaterField
6758
+ * @param {Function} props.RowsRenderer
6759
+ * @param {Object} props.indexes
6760
+ * @param {Function} props.onDeleteItem
6761
+ * @param {boolean} props.showRemove
6762
+ */
6763
+
6764
+ const RepetitionScaffold = props => {
6765
+ const {
6766
+ index,
6767
+ value,
6768
+ parentExpressionContextInfo,
6769
+ repeaterField,
6770
+ RowsRenderer,
6771
+ indexes,
6772
+ onDeleteItem,
6773
+ showRemove,
6774
+ ...restProps
6775
+ } = props;
6776
+ const elementProps = hooks.useMemo(() => ({
6777
+ ...restProps,
6778
+ indexes: {
6779
+ ...(indexes || {}),
6780
+ [repeaterField.id]: index
6781
+ }
6782
+ }), [index, indexes, repeaterField.id, restProps]);
6783
+ const localExpressionContextInfo = hooks.useMemo(() => ({
6784
+ data: parentExpressionContextInfo.data,
6785
+ this: value,
6786
+ parent: buildExpressionContext(parentExpressionContextInfo),
6787
+ i: [...parentExpressionContextInfo.i, index + 1]
6788
+ }), [index, parentExpressionContextInfo, value]);
6789
+ return !showRemove ? jsxRuntime.jsx(LocalExpressionContext.Provider, {
6790
+ value: localExpressionContextInfo,
6791
+ children: jsxRuntime.jsx(RowsRenderer, {
6792
+ ...elementProps
6793
+ })
6794
+ }) : jsxRuntime.jsxs("div", {
6795
+ class: "fjs-repeat-row-container",
6796
+ children: [jsxRuntime.jsx("div", {
6797
+ class: "fjs-repeat-row-rows",
6798
+ children: jsxRuntime.jsx(LocalExpressionContext.Provider, {
6799
+ value: localExpressionContextInfo,
6800
+ children: jsxRuntime.jsx(RowsRenderer, {
6801
+ ...elementProps
6802
+ })
6803
+ })
6804
+ }), jsxRuntime.jsx("button", {
6805
+ type: "button",
6806
+ class: "fjs-repeat-row-remove",
6807
+ "aria-label": `Remove list item ${index + 1}`,
6808
+ onClick: () => onDeleteItem(index),
6809
+ children: jsxRuntime.jsx("div", {
6810
+ class: "fjs-repeat-row-remove-icon-container",
6811
+ children: jsxRuntime.jsx(DeleteSvg, {})
6812
+ })
6813
+ })]
6814
+ });
6815
+ };
6709
6816
  RepeatRenderManager.$inject = ['form', 'formFields', 'formFieldRegistry', 'pathRegistry'];
6710
6817
 
6711
6818
  const RepeatRenderModule = {