@carbon/react 1.98.0 → 1.99.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.
@@ -342,7 +342,11 @@ const Dropdown = /*#__PURE__*/React.forwardRef(({
342
342
  size: size$1,
343
343
  className: className,
344
344
  invalid: normalizedProps.invalid,
345
+ invalidText: isFluid ? invalidText : undefined,
346
+ invalidTextId: normalizedProps.invalidId,
345
347
  warn: normalizedProps.warn,
348
+ warnText: isFluid ? warnText : undefined,
349
+ warnTextId: normalizedProps.warnId,
346
350
  light: light,
347
351
  isOpen: isOpen,
348
352
  ref: enableFloatingStyles || autoAlign ? refs.setReference : null,
@@ -67,7 +67,7 @@ const LinkBase = /*#__PURE__*/React.forwardRef(({
67
67
  ref: ref
68
68
  }, linkProps, rest, {
69
69
  onClick: handleOnClick
70
- }), children, !inline && Icon && /*#__PURE__*/React.createElement("div", {
70
+ }), children, !inline && Icon && /*#__PURE__*/React.createElement("span", {
71
71
  className: `${prefix}--link__icon`
72
72
  }, /*#__PURE__*/React.createElement(Icon, null)));
73
73
  });
@@ -724,6 +724,7 @@ const FilterableMultiSelect = /*#__PURE__*/forwardRef(function FilterableMultiSe
724
724
  return /*#__PURE__*/React.createElement(ListBox.MenuItem, _extends({
725
725
  key: itemProps.id,
726
726
  "aria-label": itemText,
727
+ "aria-checked": isIndeterminate ? 'mixed' : isChecked,
727
728
  isActive: isChecked && !item['isSelectAll'],
728
729
  isHighlighted: highlightedIndex === index,
729
730
  title: itemText,
@@ -509,6 +509,7 @@ const MultiSelect = /*#__PURE__*/React.forwardRef(({
509
509
  key: itemProps.id,
510
510
  isActive: isChecked && !item['isSelectAll'],
511
511
  "aria-label": itemText,
512
+ "aria-checked": isIndeterminate ? 'mixed' : isChecked,
512
513
  isHighlighted: highlightedIndex === index,
513
514
  title: itemText,
514
515
  disabled: itemProps['aria-disabled']
@@ -135,10 +135,14 @@ export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInp
135
135
  */
136
136
  min?: number;
137
137
  /**
138
- * Provide an optional handler that is called when the input or stepper
138
+ * Provide an optional handler that is called when the input is blurred.
139
+ */
140
+ onBlur?: (event: React.FocusEvent<HTMLInputElement>, value?: string | number) => void;
141
+ /**
142
+ * Provide an optional handler that is called when the stepper
139
143
  * buttons are blurred.
140
144
  */
141
- onBlur?: (event: React.FocusEvent<HTMLInputElement> | React.FocusEvent<HTMLButtonElement>) => void;
145
+ onStepperBlur?: (event: React.FocusEvent<HTMLButtonElement>) => void;
142
146
  /**
143
147
  * Provide an optional handler that is called when the internal state of
144
148
  * NumberInput changes. This handler is called with event and state info.
@@ -203,6 +207,15 @@ export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInp
203
207
  */
204
208
  warnText?: ReactNode;
205
209
  }
210
+ /**
211
+ * Converts a string with any Unicode numeral system to a JavaScript number.
212
+ * Handles all numeral systems supported by Intl.NumberFormat.
213
+ *
214
+ * @param {string} input - The input string with numerals in any Unicode system
215
+ * @param {string} locale - The locale for parsing separators
216
+ * @returns {number} The parsed number, or NaN if invalid
217
+ */
218
+ export declare const parseNumberWithLocale: (input: string, locale: string) => number;
206
219
  export declare const validateNumberSeparators: (input: string, locale: string) => boolean;
207
220
  declare const NumberInput: React.ForwardRefExoticComponent<NumberInputProps & React.RefAttributes<HTMLInputElement>>;
208
221
  export { NumberInput };
@@ -43,8 +43,41 @@ const getSeparators = locale => {
43
43
  const numberWithGroupAndDecimal = 1234567.89;
44
44
  const formatted = new Intl.NumberFormat(locale).format(numberWithGroupAndDecimal);
45
45
 
46
- // Extract separators using regex
47
- const match = formatted.match(/(\D+)\d{3}(\D+)\d{2}$/);
46
+ // Comprehensive Unicode digit pattern that includes all common numeral systems
47
+ // supported by Intl.NumberFormat across different locales
48
+ const digitPattern = '[' + '\\u0030-\\u0039' +
49
+ // Western
50
+ '\\u0660-\\u0669' +
51
+ // Eastern Arabic
52
+ '\\u0966-\\u096F' +
53
+ // Devanagari
54
+ '\\u09E6-\\u09EF' +
55
+ // Bengali
56
+ '\\uFF10-\\uFF19' +
57
+ // Fullwidth Japanese 0-9
58
+ '一二三四五六七八九〇零' +
59
+ // Kanji digits
60
+ ']';
61
+
62
+ // Non-digit pattern that excludes ALL digit types (not just ASCII 0-9)
63
+ const nonDigitPattern = '[^' + '\\u0030-\\u0039' +
64
+ // Western
65
+ '\\u0660-\\u0669' +
66
+ // Eastern Arabic
67
+ '\\u0966-\\u096F' +
68
+ // Devanagari
69
+ '\\u09E6-\\u09EF' +
70
+ // Bengali
71
+ '\\uFF10-\\uFF19' +
72
+ // Fullwidth Japanese 0-9
73
+ '一二三四五六七八九〇零' +
74
+ // Kanji digits
75
+ ']+';
76
+
77
+ // Extract separators using regex that handles all numeral systems
78
+ // Use nonDigitPattern instead of \D+ to correctly identify separators
79
+ const regex = new RegExp(`(${nonDigitPattern})${digitPattern}{3}(${nonDigitPattern})${digitPattern}{2}$`);
80
+ const match = formatted.match(regex);
48
81
  if (match) {
49
82
  const groupSeparator = match[1];
50
83
  const decimalSeparator = match[2];
@@ -59,11 +92,116 @@ const getSeparators = locale => {
59
92
  };
60
93
  }
61
94
  };
95
+
96
+ // Normalizes all Unicode minus variants to ASCII hyphen-minus (-)
97
+ const normalizeMinus = value => value.replace(/[\u2212\u2012\u2013\u2014\uFE63\uFF0D]/g, '-');
98
+ const normalizeNumericInput = value => value
99
+ // Remove bidi / direction control characters (Arabic keyboards)
100
+ .replace(/[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g, '')
101
+ // Normalize Unicode minus variants to ASCII "-"
102
+ .replace(/[\u2212\u2012\u2013\u2014\uFE63\uFF0D]/g, '-');
103
+ /**
104
+ * Converts a string with any Unicode numeral system to a JavaScript number.
105
+ * Handles all numeral systems supported by Intl.NumberFormat.
106
+ *
107
+ * @param {string} input - The input string with numerals in any Unicode system
108
+ * @param {string} locale - The locale for parsing separators
109
+ * @returns {number} The parsed number, or NaN if invalid
110
+ */
111
+ const parseNumberWithLocale = (input, locale) => {
112
+ // Handle empty, null, or undefined inputs
113
+ if (input === '' || input === undefined || input === null) {
114
+ return NaN;
115
+ }
116
+ input = normalizeNumericInput(input);
117
+ const {
118
+ groupSeparator,
119
+ decimalSeparator
120
+ } = getSeparators(locale);
121
+
122
+ // Kanji digit map
123
+ const kanjiMap = {
124
+ 零: '0',
125
+ 〇: '0',
126
+ 一: '1',
127
+ 二: '2',
128
+ 三: '3',
129
+ 四: '4',
130
+ 五: '5',
131
+ 六: '6',
132
+ 七: '7',
133
+ 八: '8',
134
+ 九: '9'
135
+ };
136
+ const digitRanges = [{
137
+ start: 0x0030,
138
+ end: 0x0039,
139
+ base: 0x0030
140
+ }, {
141
+ start: 0x0660,
142
+ end: 0x0669,
143
+ base: 0x0660
144
+ }, {
145
+ start: 0x0966,
146
+ end: 0x096f,
147
+ base: 0x0966
148
+ }, {
149
+ start: 0x09e6,
150
+ end: 0x09ef,
151
+ base: 0x09e6
152
+ }, {
153
+ start: 0xff10,
154
+ end: 0xff19,
155
+ base: 0xff10
156
+ }];
157
+ let normalized = Array.from(input).map(char => {
158
+ // Preserve scientific notation characters
159
+ if (char === 'e' || char === 'E' || char === '+' || char === '-') {
160
+ return char;
161
+ }
162
+
163
+ // Check Kanji first
164
+ if (kanjiMap[char] !== undefined) {
165
+ return kanjiMap[char];
166
+ }
167
+ const code = char.charCodeAt(0);
168
+ for (const range of digitRanges) {
169
+ if (code >= range.start && code <= range.end) {
170
+ return String(code - range.start);
171
+ }
172
+ }
173
+ return char;
174
+ }).join('');
175
+
176
+ // Remove grouping separators
177
+ if (groupSeparator) {
178
+ if (groupSeparator?.trim() === '') {
179
+ normalized = normalized?.replace(/[\u00A0\u202F\s]/g, '');
180
+ } else {
181
+ if (decimalSeparator !== ',' && decimalSeparator !== '٬') {
182
+ normalized = normalized?.replace(/[,٬]/g, '');
183
+ }
184
+ if (groupSeparator !== ',' && groupSeparator !== '٬') {
185
+ const escaped = groupSeparator?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
186
+ normalized = normalized?.replace(new RegExp(escaped, 'g'), '');
187
+ }
188
+ }
189
+ }
190
+ normalized = normalized.replace(/٫/g, '.');
191
+ if (decimalSeparator && decimalSeparator !== '.' && decimalSeparator !== '٫') {
192
+ const escaped = decimalSeparator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
193
+ normalized = normalized.replace(new RegExp(escaped, 'g'), '.');
194
+ }
195
+ normalized = normalizeMinus(normalized);
196
+ return Number(normalized);
197
+ };
62
198
  const validateNumberSeparators = (input, locale) => {
63
- // allow empty string
64
- if (input === '' || Number.isNaN(input)) {
199
+ if (input === '') {
65
200
  return true;
66
201
  }
202
+
203
+ // Normalize bidi marks + minus signs FIRST
204
+ input = normalizeNumericInput(input);
67
205
  const {
68
206
  groupSeparator,
69
207
  decimalSeparator
@@ -72,35 +210,54 @@ const validateNumberSeparators = (input, locale) => {
72
210
  return !isNaN(Number(input));
73
211
  }
74
212
  const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
213
+ const digit = '[' + '\\u0030-\\u0039' + '\\u0660-\\u0669' + '\\u0966-\\u096F' + '\\u09E6-\\u09EF' + '\\uFF10-\\uFF19' + '一二三四五六七八九〇零' + ']';
214
+
215
+ // Group separator regex
75
216
  let group = '';
76
217
  if (groupSeparator) {
77
- if (groupSeparator.trim() === '') {
78
- group = '[\\u00A0\\u202F\\s]'; // handle NBSP, narrow NBSP, space
218
+ if (groupSeparator?.trim() === '') {
219
+ group = '[\\u00A0\\u202F\\s]';
220
+ } else if (groupSeparator === ',' || groupSeparator === '٬') {
221
+ group = '[,٬]';
79
222
  } else {
80
223
  group = esc(groupSeparator);
81
224
  }
82
225
  }
83
- const decimal = esc(decimalSeparator);
84
226
 
85
- // Regex for:
86
- // - integers (with/without grouping)
87
- // - optional decimal with 0+ digits after separator
88
- const regex = new RegExp(`^-?\\d{1,3}(${group}\\d{3})*(${decimal}\\d*)?$|^-?\\d+(${decimal}\\d*)?$`);
89
- if (!regex.test(input)) {
90
- return false;
227
+ // Decimal separator regex
228
+ let decimal = esc(decimalSeparator);
229
+ if (decimalSeparator === '.' || decimalSeparator === '٫') {
230
+ decimal = '[.٫]';
91
231
  }
232
+ const sign = '[\\-\\u2212]?';
233
+ const scientific = `([eE][+-]?${digit}+)?`;
92
234
 
93
- // Normalize
94
- let normalized = input;
95
- if (groupSeparator) {
96
- if (groupSeparator.trim() === '') {
97
- normalized = normalized?.replace(/[\u00A0\u202F\s]/g, '');
98
- } else {
99
- normalized = normalized?.split(groupSeparator).join('');
100
- }
235
+ // Detect if grouping is used AT ALL
236
+ const usesGrouping = group && (groupSeparator?.trim() === '' ? /[\u00A0\u202F\s]/.test(input) : groupSeparator === ',' || groupSeparator === '٬' ? /[,٬]/.test(input) : groupSeparator ? input.includes(groupSeparator) : false);
237
+ const scientificMatch = input?.match(/^([^eE]+)([eE][+-]?.*)?$/);
238
+ const baseNumber = scientificMatch ? scientificMatch[1] : input;
239
+
240
+ // Split integer part from the base number - handle both decimal separator variants
241
+ let integerPart;
242
+ if (decimalSeparator === '.' || decimalSeparator === '٫') {
243
+ // Split by either . or ٫
244
+ integerPart = baseNumber?.split(/[.,]/)[0];
245
+ } else {
246
+ integerPart = baseNumber?.split(decimalSeparator)[0];
101
247
  }
102
- normalized = normalized?.replace(decimalSeparator, '.');
103
- return !isNaN(Number(normalized));
248
+
249
+ // STEP 1: strict integer validation
250
+ // When grouping is used, we need to handle two cases:
251
+ // 1. Numbers with 1-3 digits (no separator required): 1, 12, 123
252
+ // 2. Numbers with 4+ digits (separator required): 1,234 or 12,345 or 123,456
253
+ const integerRegex = usesGrouping ? new RegExp(`^${sign}(${digit}{1,3}|${digit}{1,3}(${group}${digit}{3})+)$`) : new RegExp(`^${sign}${digit}+$`);
254
+ if (!integerRegex.test(integerPart)) {
255
+ return false;
256
+ }
257
+
258
+ // STEP 2: full number validation
259
+ const fullRegex = new RegExp(`^${sign}${digit}+` + (usesGrouping ? `(${group}${digit}{3})*` : '') + `(${decimal}${digit}+)?${scientific}$`);
260
+ return fullRegex.test(input);
104
261
  };
105
262
 
106
263
  // eslint-disable-next-line react/display-name -- https://github.com/carbon-design-system/carbon/issues/20452
@@ -126,6 +283,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
126
283
  max,
127
284
  min,
128
285
  onBlur,
286
+ onStepperBlur,
129
287
  onChange,
130
288
  onClick,
131
289
  onKeyUp,
@@ -291,8 +449,9 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
291
449
  }
292
450
  };
293
451
  const outerElementClasses = cx(`${prefix}--form-item`, {
294
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
295
- [customClassName]: !!customClassName,
452
+ ...(customClassName ? {
453
+ [customClassName]: true
454
+ } : {}),
296
455
  [`${prefix}--number-input--fluid--invalid`]: isFluid && normalizedProps.invalid,
297
456
  [`${prefix}--number-input--fluid--focus`]: isFluid && isFocused,
298
457
  [`${prefix}--number-input--fluid--disabled`]: isFluid && disabled
@@ -426,6 +585,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
426
585
  if (disableWheelProp) {
427
586
  e.target.removeEventListener('wheel', disableWheel);
428
587
  }
588
+ let parsedValueForBlur;
429
589
  if (type === 'text') {
430
590
  // When isControlled, the current inputValue needs re-parsed
431
591
  // because the consumer's onChange hasn't been called yet and
@@ -441,6 +601,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
441
601
  // causing the _numberValue to mismatch the formatted value in
442
602
  // the input. To avoid this, formattedValue is re-parsed.
443
603
  const parsedFormattedNewValue = numberParser.parse(formattedValue);
604
+ parsedValueForBlur = parsedFormattedNewValue;
444
605
  if (onChange && isValid) {
445
606
  const state = {
446
607
  value: parsedFormattedNewValue,
@@ -466,7 +627,12 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
466
627
  }
467
628
  }
468
629
  if (onBlur) {
469
- onBlur(e);
630
+ if (type === 'number') {
631
+ onBlur(e, value);
632
+ return;
633
+ }
634
+ const parsedTextValue = parsedValueForBlur ?? (isControlled ? numberParser.parse(inputValue) : numberValue);
635
+ onBlur(e, parsedTextValue);
470
636
  }
471
637
  },
472
638
  pattern: pattern,
@@ -486,7 +652,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
486
652
  className: `${prefix}--number__control-btn down-icon`,
487
653
  disabled: disabled || readOnly,
488
654
  onClick: event => handleStepperClick(event, 'down'),
489
- onBlur: onBlur,
655
+ onBlur: onStepperBlur,
490
656
  tabIndex: -1,
491
657
  title: decrementNumLabel || iconDescription,
492
658
  type: "button"
@@ -499,7 +665,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
499
665
  className: `${prefix}--number__control-btn up-icon`,
500
666
  disabled: disabled || readOnly,
501
667
  onClick: event => handleStepperClick(event, 'up'),
502
- onBlur: onBlur,
668
+ onBlur: onStepperBlur,
503
669
  tabIndex: -1,
504
670
  title: incrementNumLabel || iconDescription,
505
671
  type: "button"
@@ -610,10 +776,14 @@ NumberInput.propTypes = {
610
776
  */
611
777
  stepStartValue: PropTypes.number,
612
778
  /**
613
- * Provide an optional handler that is called when the input or stepper
614
- * buttons are blurred.
779
+ * Provide an optional handler that is called when the input is blurred.
615
780
  */
616
781
  onBlur: PropTypes.func,
782
+ /**
783
+ * Provide an optional handler that is called when the stepper
784
+ * buttons are blurred.
785
+ */
786
+ onStepperBlur: PropTypes.func,
617
787
  /**
618
788
  * Provide an optional handler that is called when the internal state of
619
789
  * NumberInput changes. This handler is called with event and state info.
@@ -752,23 +922,33 @@ function getInputValidity({
752
922
  validate,
753
923
  locale
754
924
  }) {
755
- if (typeof validate === 'function') {
756
- const result = validate(value, locale);
757
- if (result === false) {
758
- return false; // immediate invalid
759
- }
760
- // If true or undefined, continue to further validations
761
- }
762
925
  if (invalid) {
763
926
  return false;
764
927
  }
765
- if (value === '') {
766
- return allowEmpty;
928
+
929
+ // Skip validation if value is empty and allowEmpty
930
+ if (value === '') return allowEmpty;
931
+
932
+ // Normalize the value
933
+ let numericValue;
934
+ if (typeof value === 'string') {
935
+ numericValue = parseNumberWithLocale(value, locale); // safe: handles Arabic, Kanji, etc.
936
+ } else {
937
+ numericValue = value;
767
938
  }
768
- if (value > max || value < min) {
769
- return false;
939
+
940
+ // Use custom validate ONLY for formatting, not numeric comparison
941
+ if (validate && typeof value === 'string') {
942
+ const isFormatValid = validate(value, locale);
943
+ if (isFormatValid === false) {
944
+ return false; // invalid format
945
+ }
770
946
  }
771
- return true;
947
+
948
+ // Check min/max bounds
949
+ if (max !== undefined && numericValue > max) return false;
950
+ if (min !== undefined && numericValue < min) return false;
951
+ return true; // valid
772
952
  }
773
953
 
774
954
  /**
@@ -789,4 +969,4 @@ function disableWheel(e) {
789
969
  e.preventDefault();
790
970
  }
791
971
 
792
- export { NumberInput, validateNumberSeparators };
972
+ export { NumberInput, parseNumberWithLocale, validateNumberSeparators };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright IBM Corp. 2016, 2025
2
+ * Copyright IBM Corp. 2016, 2026
3
3
  *
4
4
  * This source code is licensed under the Apache-2.0 license found in the
5
5
  * LICENSE file in the root directory of this source tree.
@@ -60,27 +60,41 @@ forwardRef) {
60
60
  const caretRef = useRef(null);
61
61
  const popover = useRef(null);
62
62
  const enableFloatingStyles = useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign;
63
+ const lastClickWasInsidePopoverContent = useRef(false);
63
64
  let align = mapPopoverAlign(initialAlign);
64
65
 
66
+ // Tracks clicks inside PopoverContent to prevent it from closing when clicked, this handles an edge
67
+ // case where the popover will close when clicking non-focusable elements (e.g. text)
68
+ useEvent(popover, 'mousedown', event => {
69
+ const target = event.target;
70
+ lastClickWasInsidePopoverContent.current = refs.floating.current?.contains(target) || false;
71
+
72
+ // reset flag
73
+ if (lastClickWasInsidePopoverContent.current) {
74
+ setTimeout(() => {
75
+ lastClickWasInsidePopoverContent.current = false;
76
+ }, 0);
77
+ }
78
+ });
79
+
65
80
  // The `Popover` should close whenever it and its children loses focus
66
81
  useEvent(popover, 'focusout', event => {
67
82
  const relatedTarget = event.relatedTarget;
68
- if (isTabTip) {
69
- if (relatedTarget && !popover.current?.contains(relatedTarget)) {
70
- onRequestClose?.();
71
- }
72
- return;
73
- }
74
83
  if (!relatedTarget) {
84
+ // do not close if PopoverContent was clicked
85
+ if (lastClickWasInsidePopoverContent.current) {
86
+ lastClickWasInsidePopoverContent.current = false;
87
+ return;
88
+ }
75
89
  onRequestClose?.();
76
- return;
77
- }
78
- const isOutsideMainContainer = !popover.current?.contains(relatedTarget);
79
- const isOutsideFloating = enableFloatingStyles && refs.floating.current ? !refs.floating.current.contains(relatedTarget) : true;
90
+ } else if (relatedTarget && !popover.current?.contains(relatedTarget)) {
91
+ const isOutsideFloating = enableFloatingStyles && refs.floating.current ? !refs.floating.current.contains(relatedTarget) : true;
92
+ const isFocusableWrapper = relatedTarget && popover.current && relatedTarget.contains(popover.current);
80
93
 
81
- // Only close if focus moved outside both containers
82
- if (isOutsideMainContainer && isOutsideFloating) {
83
- onRequestClose?.();
94
+ // Only close if focus moved outside both containers and not to an interactive parent wrapper
95
+ if (isOutsideFloating && !isFocusableWrapper) {
96
+ onRequestClose?.();
97
+ }
84
98
  }
85
99
  });
86
100
  useWindowEvent('click', ({
@@ -886,7 +886,7 @@ const Tab = /*#__PURE__*/forwardRef(({
886
886
  })
887
887
  }, /*#__PURE__*/React.createElement("button", {
888
888
  type: "button",
889
- tabIndex: selectedIndex === index && dismissable ? 0 : -1,
889
+ tabIndex: -1,
890
890
  "aria-disabled": disabled,
891
891
  "aria-hidden": selectedIndex === index && dismissable ? 'false' : 'true',
892
892
  disabled: disabled,
@@ -202,8 +202,10 @@ const TextArea = frFn((props, forwardRef) => {
202
202
  }, labelText) : null;
203
203
  const counter = enableCounter && maxCount && (counterMode === 'character' || counterMode === 'word') ? /*#__PURE__*/React.createElement(Text, {
204
204
  as: "div",
205
- className: counterClasses
205
+ className: counterClasses,
206
+ "aria-hidden": "true"
206
207
  }, `${textCount}/${maxCount}`) : null;
208
+ const counterDescriptionId = enableCounter && maxCount ? `${id}-counter-desc` : undefined;
207
209
  const helperId = !helperText ? undefined : `text-area-helper-text-${textAreaInstanceId}`;
208
210
  const helper = helperText ? /*#__PURE__*/React.createElement(Text, {
209
211
  as: "div",
@@ -234,8 +236,13 @@ const TextArea = frFn((props, forwardRef) => {
234
236
  let ariaDescribedBy;
235
237
  if (invalid) {
236
238
  ariaDescribedBy = errorId;
237
- } else if (!invalid && !warn && !isFluid && helperText) {
238
- ariaDescribedBy = helperId;
239
+ } else if (warn && !isFluid) {
240
+ ariaDescribedBy = warnId;
241
+ } else {
242
+ const ids = [];
243
+ if (!isFluid && helperText && helperId) ids.push(helperId);
244
+ if (counterDescriptionId) ids.push(counterDescriptionId);
245
+ ariaDescribedBy = ids.length > 0 ? ids.join(' ') : undefined;
239
246
  }
240
247
  if (enableCounter) {
241
248
  // handle different counter mode
@@ -292,7 +299,10 @@ const TextArea = frFn((props, forwardRef) => {
292
299
  className: formItemClasses
293
300
  }, /*#__PURE__*/React.createElement("div", {
294
301
  className: `${prefix}--text-area__label-wrapper`
295
- }, label, counter), /*#__PURE__*/React.createElement("div", {
302
+ }, label, counter), enableCounter && maxCount && /*#__PURE__*/React.createElement("span", {
303
+ id: counterDescriptionId,
304
+ className: `${prefix}--visually-hidden`
305
+ }, counterMode === 'word' ? `Word limit ${maxCount}` : `Character limit ${maxCount}`), /*#__PURE__*/React.createElement("div", {
296
306
  ref: wrapperRef,
297
307
  className: textAreaWrapperClasses,
298
308
  "data-invalid": invalid || null
@@ -135,6 +135,7 @@ const PasswordInput = /*#__PURE__*/React.forwardRef(({
135
135
  [`${prefix}--tooltip--${tooltipPosition}`]: tooltipPosition,
136
136
  [`${prefix}--tooltip--align-${tooltipAlignment}`]: tooltipAlignment
137
137
  });
138
+ const tooltipClasses = cx(`${prefix}--toggle-password-tooltip`, `${prefix}--icon-tooltip`);
138
139
  let align = undefined;
139
140
  if (tooltipPosition === 'top' || tooltipPosition === 'bottom') {
140
141
  if (tooltipAlignment === 'center') {
@@ -172,12 +173,12 @@ const PasswordInput = /*#__PURE__*/React.forwardRef(({
172
173
  className: `${prefix}--text-input__divider`
173
174
  }), /*#__PURE__*/React.createElement(Tooltip, {
174
175
  align: align,
175
- className: `${prefix}--toggle-password-tooltip`,
176
+ className: tooltipClasses,
176
177
  label: passwordIsVisible ? hidePasswordLabel : showPasswordLabel
177
178
  }, /*#__PURE__*/React.createElement("button", {
178
179
  type: "button",
179
180
  className: passwordVisibilityToggleClasses,
180
- disabled: disabled || readOnly,
181
+ disabled: disabled,
181
182
  onClick: handleTogglePasswordVisibility
182
183
  }, passwordVisibilityIcon)));
183
184
  useEffect(() => {
@@ -346,7 +346,11 @@ const Dropdown = /*#__PURE__*/React.forwardRef(({
346
346
  size: size,
347
347
  className: className,
348
348
  invalid: normalizedProps.invalid,
349
+ invalidText: isFluid ? invalidText : undefined,
350
+ invalidTextId: normalizedProps.invalidId,
349
351
  warn: normalizedProps.warn,
352
+ warnText: isFluid ? warnText : undefined,
353
+ warnTextId: normalizedProps.warnId,
350
354
  light: light,
351
355
  isOpen: isOpen,
352
356
  ref: enableFloatingStyles || autoAlign ? refs.setReference : null,
@@ -71,7 +71,7 @@ const LinkBase = /*#__PURE__*/React.forwardRef(({
71
71
  ref: ref
72
72
  }, linkProps, rest, {
73
73
  onClick: handleOnClick
74
- }), children, !inline && Icon && /*#__PURE__*/React.createElement("div", {
74
+ }), children, !inline && Icon && /*#__PURE__*/React.createElement("span", {
75
75
  className: `${prefix}--link__icon`
76
76
  }, /*#__PURE__*/React.createElement(Icon, null)));
77
77
  });
@@ -726,6 +726,7 @@ const FilterableMultiSelect = /*#__PURE__*/React.forwardRef(function FilterableM
726
726
  return /*#__PURE__*/React.createElement(index$1.default.MenuItem, _rollupPluginBabelHelpers.extends({
727
727
  key: itemProps.id,
728
728
  "aria-label": itemText,
729
+ "aria-checked": isIndeterminate ? 'mixed' : isChecked,
729
730
  isActive: isChecked && !item['isSelectAll'],
730
731
  isHighlighted: highlightedIndex === index,
731
732
  title: itemText,
@@ -511,6 +511,7 @@ const MultiSelect = /*#__PURE__*/React.forwardRef(({
511
511
  key: itemProps.id,
512
512
  isActive: isChecked && !item['isSelectAll'],
513
513
  "aria-label": itemText,
514
+ "aria-checked": isIndeterminate ? 'mixed' : isChecked,
514
515
  isHighlighted: highlightedIndex === index,
515
516
  title: itemText,
516
517
  disabled: itemProps['aria-disabled']
@@ -135,10 +135,14 @@ export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInp
135
135
  */
136
136
  min?: number;
137
137
  /**
138
- * Provide an optional handler that is called when the input or stepper
138
+ * Provide an optional handler that is called when the input is blurred.
139
+ */
140
+ onBlur?: (event: React.FocusEvent<HTMLInputElement>, value?: string | number) => void;
141
+ /**
142
+ * Provide an optional handler that is called when the stepper
139
143
  * buttons are blurred.
140
144
  */
141
- onBlur?: (event: React.FocusEvent<HTMLInputElement> | React.FocusEvent<HTMLButtonElement>) => void;
145
+ onStepperBlur?: (event: React.FocusEvent<HTMLButtonElement>) => void;
142
146
  /**
143
147
  * Provide an optional handler that is called when the internal state of
144
148
  * NumberInput changes. This handler is called with event and state info.
@@ -203,6 +207,15 @@ export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInp
203
207
  */
204
208
  warnText?: ReactNode;
205
209
  }
210
+ /**
211
+ * Converts a string with any Unicode numeral system to a JavaScript number.
212
+ * Handles all numeral systems supported by Intl.NumberFormat.
213
+ *
214
+ * @param {string} input - The input string with numerals in any Unicode system
215
+ * @param {string} locale - The locale for parsing separators
216
+ * @returns {number} The parsed number, or NaN if invalid
217
+ */
218
+ export declare const parseNumberWithLocale: (input: string, locale: string) => number;
206
219
  export declare const validateNumberSeparators: (input: string, locale: string) => boolean;
207
220
  declare const NumberInput: React.ForwardRefExoticComponent<NumberInputProps & React.RefAttributes<HTMLInputElement>>;
208
221
  export { NumberInput };