@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.
- package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +956 -956
- package/es/components/Dropdown/Dropdown.js +4 -0
- package/es/components/Link/Link.js +1 -1
- package/es/components/MultiSelect/FilterableMultiSelect.js +1 -0
- package/es/components/MultiSelect/MultiSelect.js +1 -0
- package/es/components/NumberInput/NumberInput.d.ts +15 -2
- package/es/components/NumberInput/NumberInput.js +223 -43
- package/es/components/Popover/index.d.ts +1 -1
- package/es/components/Popover/index.js +27 -13
- package/es/components/Tabs/Tabs.js +1 -1
- package/es/components/TextArea/TextArea.js +14 -4
- package/es/components/TextInput/PasswordInput.js +3 -2
- package/lib/components/Dropdown/Dropdown.js +4 -0
- package/lib/components/Link/Link.js +1 -1
- package/lib/components/MultiSelect/FilterableMultiSelect.js +1 -0
- package/lib/components/MultiSelect/MultiSelect.js +1 -0
- package/lib/components/NumberInput/NumberInput.d.ts +15 -2
- package/lib/components/NumberInput/NumberInput.js +223 -42
- package/lib/components/Popover/index.d.ts +1 -1
- package/lib/components/Popover/index.js +27 -13
- package/lib/components/Tabs/Tabs.js +1 -1
- package/lib/components/TextArea/TextArea.js +14 -4
- package/lib/components/TextInput/PasswordInput.js +3 -2
- package/package.json +9 -10
- package/telemetry.yml +1 -0
|
@@ -45,8 +45,41 @@ const getSeparators = locale => {
|
|
|
45
45
|
const numberWithGroupAndDecimal = 1234567.89;
|
|
46
46
|
const formatted = new Intl.NumberFormat(locale).format(numberWithGroupAndDecimal);
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
|
|
48
|
+
// Comprehensive Unicode digit pattern that includes all common numeral systems
|
|
49
|
+
// supported by Intl.NumberFormat across different locales
|
|
50
|
+
const digitPattern = '[' + '\\u0030-\\u0039' +
|
|
51
|
+
// Western
|
|
52
|
+
'\\u0660-\\u0669' +
|
|
53
|
+
// Eastern Arabic
|
|
54
|
+
'\\u0966-\\u096F' +
|
|
55
|
+
// Devanagari
|
|
56
|
+
'\\u09E6-\\u09EF' +
|
|
57
|
+
// Bengali
|
|
58
|
+
'\\uFF10-\\uFF19' +
|
|
59
|
+
// Fullwidth Japanese 0-9
|
|
60
|
+
'一二三四五六七八九〇零' +
|
|
61
|
+
// Kanji digits
|
|
62
|
+
']';
|
|
63
|
+
|
|
64
|
+
// Non-digit pattern that excludes ALL digit types (not just ASCII 0-9)
|
|
65
|
+
const nonDigitPattern = '[^' + '\\u0030-\\u0039' +
|
|
66
|
+
// Western
|
|
67
|
+
'\\u0660-\\u0669' +
|
|
68
|
+
// Eastern Arabic
|
|
69
|
+
'\\u0966-\\u096F' +
|
|
70
|
+
// Devanagari
|
|
71
|
+
'\\u09E6-\\u09EF' +
|
|
72
|
+
// Bengali
|
|
73
|
+
'\\uFF10-\\uFF19' +
|
|
74
|
+
// Fullwidth Japanese 0-9
|
|
75
|
+
'一二三四五六七八九〇零' +
|
|
76
|
+
// Kanji digits
|
|
77
|
+
']+';
|
|
78
|
+
|
|
79
|
+
// Extract separators using regex that handles all numeral systems
|
|
80
|
+
// Use nonDigitPattern instead of \D+ to correctly identify separators
|
|
81
|
+
const regex = new RegExp(`(${nonDigitPattern})${digitPattern}{3}(${nonDigitPattern})${digitPattern}{2}$`);
|
|
82
|
+
const match = formatted.match(regex);
|
|
50
83
|
if (match) {
|
|
51
84
|
const groupSeparator = match[1];
|
|
52
85
|
const decimalSeparator = match[2];
|
|
@@ -61,11 +94,116 @@ const getSeparators = locale => {
|
|
|
61
94
|
};
|
|
62
95
|
}
|
|
63
96
|
};
|
|
97
|
+
|
|
98
|
+
// Normalizes all Unicode minus variants to ASCII hyphen-minus (-)
|
|
99
|
+
const normalizeMinus = value => value.replace(/[\u2212\u2012\u2013\u2014\uFE63\uFF0D]/g, '-');
|
|
100
|
+
const normalizeNumericInput = value => value
|
|
101
|
+
// Remove bidi / direction control characters (Arabic keyboards)
|
|
102
|
+
.replace(/[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g, '')
|
|
103
|
+
// Normalize Unicode minus variants to ASCII "-"
|
|
104
|
+
.replace(/[\u2212\u2012\u2013\u2014\uFE63\uFF0D]/g, '-');
|
|
105
|
+
/**
|
|
106
|
+
* Converts a string with any Unicode numeral system to a JavaScript number.
|
|
107
|
+
* Handles all numeral systems supported by Intl.NumberFormat.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} input - The input string with numerals in any Unicode system
|
|
110
|
+
* @param {string} locale - The locale for parsing separators
|
|
111
|
+
* @returns {number} The parsed number, or NaN if invalid
|
|
112
|
+
*/
|
|
113
|
+
const parseNumberWithLocale = (input, locale) => {
|
|
114
|
+
// Handle empty, null, or undefined inputs
|
|
115
|
+
if (input === '' || input === undefined || input === null) {
|
|
116
|
+
return NaN;
|
|
117
|
+
}
|
|
118
|
+
input = normalizeNumericInput(input);
|
|
119
|
+
const {
|
|
120
|
+
groupSeparator,
|
|
121
|
+
decimalSeparator
|
|
122
|
+
} = getSeparators(locale);
|
|
123
|
+
|
|
124
|
+
// Kanji digit map
|
|
125
|
+
const kanjiMap = {
|
|
126
|
+
零: '0',
|
|
127
|
+
〇: '0',
|
|
128
|
+
一: '1',
|
|
129
|
+
二: '2',
|
|
130
|
+
三: '3',
|
|
131
|
+
四: '4',
|
|
132
|
+
五: '5',
|
|
133
|
+
六: '6',
|
|
134
|
+
七: '7',
|
|
135
|
+
八: '8',
|
|
136
|
+
九: '9'
|
|
137
|
+
};
|
|
138
|
+
const digitRanges = [{
|
|
139
|
+
start: 0x0030,
|
|
140
|
+
end: 0x0039,
|
|
141
|
+
base: 0x0030
|
|
142
|
+
}, {
|
|
143
|
+
start: 0x0660,
|
|
144
|
+
end: 0x0669,
|
|
145
|
+
base: 0x0660
|
|
146
|
+
}, {
|
|
147
|
+
start: 0x0966,
|
|
148
|
+
end: 0x096f,
|
|
149
|
+
base: 0x0966
|
|
150
|
+
}, {
|
|
151
|
+
start: 0x09e6,
|
|
152
|
+
end: 0x09ef,
|
|
153
|
+
base: 0x09e6
|
|
154
|
+
}, {
|
|
155
|
+
start: 0xff10,
|
|
156
|
+
end: 0xff19,
|
|
157
|
+
base: 0xff10
|
|
158
|
+
}];
|
|
159
|
+
let normalized = Array.from(input).map(char => {
|
|
160
|
+
// Preserve scientific notation characters
|
|
161
|
+
if (char === 'e' || char === 'E' || char === '+' || char === '-') {
|
|
162
|
+
return char;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check Kanji first
|
|
166
|
+
if (kanjiMap[char] !== undefined) {
|
|
167
|
+
return kanjiMap[char];
|
|
168
|
+
}
|
|
169
|
+
const code = char.charCodeAt(0);
|
|
170
|
+
for (const range of digitRanges) {
|
|
171
|
+
if (code >= range.start && code <= range.end) {
|
|
172
|
+
return String(code - range.start);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return char;
|
|
176
|
+
}).join('');
|
|
177
|
+
|
|
178
|
+
// Remove grouping separators
|
|
179
|
+
if (groupSeparator) {
|
|
180
|
+
if (groupSeparator?.trim() === '') {
|
|
181
|
+
normalized = normalized?.replace(/[\u00A0\u202F\s]/g, '');
|
|
182
|
+
} else {
|
|
183
|
+
if (decimalSeparator !== ',' && decimalSeparator !== '٬') {
|
|
184
|
+
normalized = normalized?.replace(/[,٬]/g, '');
|
|
185
|
+
}
|
|
186
|
+
if (groupSeparator !== ',' && groupSeparator !== '٬') {
|
|
187
|
+
const escaped = groupSeparator?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
188
|
+
normalized = normalized?.replace(new RegExp(escaped, 'g'), '');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
normalized = normalized.replace(/٫/g, '.');
|
|
193
|
+
if (decimalSeparator && decimalSeparator !== '.' && decimalSeparator !== '٫') {
|
|
194
|
+
const escaped = decimalSeparator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
195
|
+
normalized = normalized.replace(new RegExp(escaped, 'g'), '.');
|
|
196
|
+
}
|
|
197
|
+
normalized = normalizeMinus(normalized);
|
|
198
|
+
return Number(normalized);
|
|
199
|
+
};
|
|
64
200
|
const validateNumberSeparators = (input, locale) => {
|
|
65
|
-
|
|
66
|
-
if (input === '' || Number.isNaN(input)) {
|
|
201
|
+
if (input === '') {
|
|
67
202
|
return true;
|
|
68
203
|
}
|
|
204
|
+
|
|
205
|
+
// Normalize bidi marks + minus signs FIRST
|
|
206
|
+
input = normalizeNumericInput(input);
|
|
69
207
|
const {
|
|
70
208
|
groupSeparator,
|
|
71
209
|
decimalSeparator
|
|
@@ -74,35 +212,54 @@ const validateNumberSeparators = (input, locale) => {
|
|
|
74
212
|
return !isNaN(Number(input));
|
|
75
213
|
}
|
|
76
214
|
const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
215
|
+
const digit = '[' + '\\u0030-\\u0039' + '\\u0660-\\u0669' + '\\u0966-\\u096F' + '\\u09E6-\\u09EF' + '\\uFF10-\\uFF19' + '一二三四五六七八九〇零' + ']';
|
|
216
|
+
|
|
217
|
+
// Group separator regex
|
|
77
218
|
let group = '';
|
|
78
219
|
if (groupSeparator) {
|
|
79
|
-
if (groupSeparator
|
|
80
|
-
group = '[\\u00A0\\u202F\\s]';
|
|
220
|
+
if (groupSeparator?.trim() === '') {
|
|
221
|
+
group = '[\\u00A0\\u202F\\s]';
|
|
222
|
+
} else if (groupSeparator === ',' || groupSeparator === '٬') {
|
|
223
|
+
group = '[,٬]';
|
|
81
224
|
} else {
|
|
82
225
|
group = esc(groupSeparator);
|
|
83
226
|
}
|
|
84
227
|
}
|
|
85
|
-
const decimal = esc(decimalSeparator);
|
|
86
228
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (!regex.test(input)) {
|
|
92
|
-
return false;
|
|
229
|
+
// Decimal separator regex
|
|
230
|
+
let decimal = esc(decimalSeparator);
|
|
231
|
+
if (decimalSeparator === '.' || decimalSeparator === '٫') {
|
|
232
|
+
decimal = '[.٫]';
|
|
93
233
|
}
|
|
234
|
+
const sign = '[\\-\\u2212]?';
|
|
235
|
+
const scientific = `([eE][+-]?${digit}+)?`;
|
|
94
236
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
237
|
+
// Detect if grouping is used AT ALL
|
|
238
|
+
const usesGrouping = group && (groupSeparator?.trim() === '' ? /[\u00A0\u202F\s]/.test(input) : groupSeparator === ',' || groupSeparator === '٬' ? /[,٬]/.test(input) : groupSeparator ? input.includes(groupSeparator) : false);
|
|
239
|
+
const scientificMatch = input?.match(/^([^eE]+)([eE][+-]?.*)?$/);
|
|
240
|
+
const baseNumber = scientificMatch ? scientificMatch[1] : input;
|
|
241
|
+
|
|
242
|
+
// Split integer part from the base number - handle both decimal separator variants
|
|
243
|
+
let integerPart;
|
|
244
|
+
if (decimalSeparator === '.' || decimalSeparator === '٫') {
|
|
245
|
+
// Split by either . or ٫
|
|
246
|
+
integerPart = baseNumber?.split(/[.,]/)[0];
|
|
247
|
+
} else {
|
|
248
|
+
integerPart = baseNumber?.split(decimalSeparator)[0];
|
|
103
249
|
}
|
|
104
|
-
|
|
105
|
-
|
|
250
|
+
|
|
251
|
+
// STEP 1: strict integer validation
|
|
252
|
+
// When grouping is used, we need to handle two cases:
|
|
253
|
+
// 1. Numbers with 1-3 digits (no separator required): 1, 12, 123
|
|
254
|
+
// 2. Numbers with 4+ digits (separator required): 1,234 or 12,345 or 123,456
|
|
255
|
+
const integerRegex = usesGrouping ? new RegExp(`^${sign}(${digit}{1,3}|${digit}{1,3}(${group}${digit}{3})+)$`) : new RegExp(`^${sign}${digit}+$`);
|
|
256
|
+
if (!integerRegex.test(integerPart)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// STEP 2: full number validation
|
|
261
|
+
const fullRegex = new RegExp(`^${sign}${digit}+` + (usesGrouping ? `(${group}${digit}{3})*` : '') + `(${decimal}${digit}+)?${scientific}$`);
|
|
262
|
+
return fullRegex.test(input);
|
|
106
263
|
};
|
|
107
264
|
|
|
108
265
|
// eslint-disable-next-line react/display-name -- https://github.com/carbon-design-system/carbon/issues/20452
|
|
@@ -128,6 +285,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
|
|
|
128
285
|
max,
|
|
129
286
|
min,
|
|
130
287
|
onBlur,
|
|
288
|
+
onStepperBlur,
|
|
131
289
|
onChange,
|
|
132
290
|
onClick,
|
|
133
291
|
onKeyUp,
|
|
@@ -293,8 +451,9 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
|
|
|
293
451
|
}
|
|
294
452
|
};
|
|
295
453
|
const outerElementClasses = cx(`${prefix}--form-item`, {
|
|
296
|
-
|
|
297
|
-
|
|
454
|
+
...(customClassName ? {
|
|
455
|
+
[customClassName]: true
|
|
456
|
+
} : {}),
|
|
298
457
|
[`${prefix}--number-input--fluid--invalid`]: isFluid && normalizedProps.invalid,
|
|
299
458
|
[`${prefix}--number-input--fluid--focus`]: isFluid && isFocused,
|
|
300
459
|
[`${prefix}--number-input--fluid--disabled`]: isFluid && disabled
|
|
@@ -428,6 +587,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
|
|
|
428
587
|
if (disableWheelProp) {
|
|
429
588
|
e.target.removeEventListener('wheel', disableWheel);
|
|
430
589
|
}
|
|
590
|
+
let parsedValueForBlur;
|
|
431
591
|
if (type === 'text') {
|
|
432
592
|
// When isControlled, the current inputValue needs re-parsed
|
|
433
593
|
// because the consumer's onChange hasn't been called yet and
|
|
@@ -443,6 +603,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
|
|
|
443
603
|
// causing the _numberValue to mismatch the formatted value in
|
|
444
604
|
// the input. To avoid this, formattedValue is re-parsed.
|
|
445
605
|
const parsedFormattedNewValue = numberParser.parse(formattedValue);
|
|
606
|
+
parsedValueForBlur = parsedFormattedNewValue;
|
|
446
607
|
if (onChange && isValid) {
|
|
447
608
|
const state = {
|
|
448
609
|
value: parsedFormattedNewValue,
|
|
@@ -468,7 +629,12 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
|
|
|
468
629
|
}
|
|
469
630
|
}
|
|
470
631
|
if (onBlur) {
|
|
471
|
-
|
|
632
|
+
if (type === 'number') {
|
|
633
|
+
onBlur(e, value);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const parsedTextValue = parsedValueForBlur ?? (isControlled ? numberParser.parse(inputValue) : numberValue);
|
|
637
|
+
onBlur(e, parsedTextValue);
|
|
472
638
|
}
|
|
473
639
|
},
|
|
474
640
|
pattern: pattern,
|
|
@@ -488,7 +654,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
|
|
|
488
654
|
className: `${prefix}--number__control-btn down-icon`,
|
|
489
655
|
disabled: disabled || readOnly,
|
|
490
656
|
onClick: event => handleStepperClick(event, 'down'),
|
|
491
|
-
onBlur:
|
|
657
|
+
onBlur: onStepperBlur,
|
|
492
658
|
tabIndex: -1,
|
|
493
659
|
title: decrementNumLabel || iconDescription,
|
|
494
660
|
type: "button"
|
|
@@ -501,7 +667,7 @@ const NumberInput = /*#__PURE__*/React.forwardRef((props, forwardRef) => {
|
|
|
501
667
|
className: `${prefix}--number__control-btn up-icon`,
|
|
502
668
|
disabled: disabled || readOnly,
|
|
503
669
|
onClick: event => handleStepperClick(event, 'up'),
|
|
504
|
-
onBlur:
|
|
670
|
+
onBlur: onStepperBlur,
|
|
505
671
|
tabIndex: -1,
|
|
506
672
|
title: incrementNumLabel || iconDescription,
|
|
507
673
|
type: "button"
|
|
@@ -612,10 +778,14 @@ NumberInput.propTypes = {
|
|
|
612
778
|
*/
|
|
613
779
|
stepStartValue: PropTypes.number,
|
|
614
780
|
/**
|
|
615
|
-
* Provide an optional handler that is called when the input
|
|
616
|
-
* buttons are blurred.
|
|
781
|
+
* Provide an optional handler that is called when the input is blurred.
|
|
617
782
|
*/
|
|
618
783
|
onBlur: PropTypes.func,
|
|
784
|
+
/**
|
|
785
|
+
* Provide an optional handler that is called when the stepper
|
|
786
|
+
* buttons are blurred.
|
|
787
|
+
*/
|
|
788
|
+
onStepperBlur: PropTypes.func,
|
|
619
789
|
/**
|
|
620
790
|
* Provide an optional handler that is called when the internal state of
|
|
621
791
|
* NumberInput changes. This handler is called with event and state info.
|
|
@@ -754,23 +924,33 @@ function getInputValidity({
|
|
|
754
924
|
validate,
|
|
755
925
|
locale
|
|
756
926
|
}) {
|
|
757
|
-
if (typeof validate === 'function') {
|
|
758
|
-
const result = validate(value, locale);
|
|
759
|
-
if (result === false) {
|
|
760
|
-
return false; // immediate invalid
|
|
761
|
-
}
|
|
762
|
-
// If true or undefined, continue to further validations
|
|
763
|
-
}
|
|
764
927
|
if (invalid) {
|
|
765
928
|
return false;
|
|
766
929
|
}
|
|
767
|
-
|
|
768
|
-
|
|
930
|
+
|
|
931
|
+
// Skip validation if value is empty and allowEmpty
|
|
932
|
+
if (value === '') return allowEmpty;
|
|
933
|
+
|
|
934
|
+
// Normalize the value
|
|
935
|
+
let numericValue;
|
|
936
|
+
if (typeof value === 'string') {
|
|
937
|
+
numericValue = parseNumberWithLocale(value, locale); // safe: handles Arabic, Kanji, etc.
|
|
938
|
+
} else {
|
|
939
|
+
numericValue = value;
|
|
769
940
|
}
|
|
770
|
-
|
|
771
|
-
|
|
941
|
+
|
|
942
|
+
// Use custom validate ONLY for formatting, not numeric comparison
|
|
943
|
+
if (validate && typeof value === 'string') {
|
|
944
|
+
const isFormatValid = validate(value, locale);
|
|
945
|
+
if (isFormatValid === false) {
|
|
946
|
+
return false; // invalid format
|
|
947
|
+
}
|
|
772
948
|
}
|
|
773
|
-
|
|
949
|
+
|
|
950
|
+
// Check min/max bounds
|
|
951
|
+
if (max !== undefined && numericValue > max) return false;
|
|
952
|
+
if (min !== undefined && numericValue < min) return false;
|
|
953
|
+
return true; // valid
|
|
774
954
|
}
|
|
775
955
|
|
|
776
956
|
/**
|
|
@@ -792,4 +972,5 @@ function disableWheel(e) {
|
|
|
792
972
|
}
|
|
793
973
|
|
|
794
974
|
exports.NumberInput = NumberInput;
|
|
975
|
+
exports.parseNumberWithLocale = parseNumberWithLocale;
|
|
795
976
|
exports.validateNumberSeparators = validateNumberSeparators;
|
|
@@ -62,27 +62,41 @@ forwardRef) {
|
|
|
62
62
|
const caretRef = React.useRef(null);
|
|
63
63
|
const popover = React.useRef(null);
|
|
64
64
|
const enableFloatingStyles = index.useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign;
|
|
65
|
+
const lastClickWasInsidePopoverContent = React.useRef(false);
|
|
65
66
|
let align = mapPopoverAlign.mapPopoverAlign(initialAlign);
|
|
66
67
|
|
|
68
|
+
// Tracks clicks inside PopoverContent to prevent it from closing when clicked, this handles an edge
|
|
69
|
+
// case where the popover will close when clicking non-focusable elements (e.g. text)
|
|
70
|
+
useEvent.useEvent(popover, 'mousedown', event => {
|
|
71
|
+
const target = event.target;
|
|
72
|
+
lastClickWasInsidePopoverContent.current = refs.floating.current?.contains(target) || false;
|
|
73
|
+
|
|
74
|
+
// reset flag
|
|
75
|
+
if (lastClickWasInsidePopoverContent.current) {
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
lastClickWasInsidePopoverContent.current = false;
|
|
78
|
+
}, 0);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
67
82
|
// The `Popover` should close whenever it and its children loses focus
|
|
68
83
|
useEvent.useEvent(popover, 'focusout', event => {
|
|
69
84
|
const relatedTarget = event.relatedTarget;
|
|
70
|
-
if (isTabTip) {
|
|
71
|
-
if (relatedTarget && !popover.current?.contains(relatedTarget)) {
|
|
72
|
-
onRequestClose?.();
|
|
73
|
-
}
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
85
|
if (!relatedTarget) {
|
|
86
|
+
// do not close if PopoverContent was clicked
|
|
87
|
+
if (lastClickWasInsidePopoverContent.current) {
|
|
88
|
+
lastClickWasInsidePopoverContent.current = false;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
77
91
|
onRequestClose?.();
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const isOutsideFloating = enableFloatingStyles && refs.floating.current ? !refs.floating.current.contains(relatedTarget) : true;
|
|
92
|
+
} else if (relatedTarget && !popover.current?.contains(relatedTarget)) {
|
|
93
|
+
const isOutsideFloating = enableFloatingStyles && refs.floating.current ? !refs.floating.current.contains(relatedTarget) : true;
|
|
94
|
+
const isFocusableWrapper = relatedTarget && popover.current && relatedTarget.contains(popover.current);
|
|
82
95
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
// Only close if focus moved outside both containers and not to an interactive parent wrapper
|
|
97
|
+
if (isOutsideFloating && !isFocusableWrapper) {
|
|
98
|
+
onRequestClose?.();
|
|
99
|
+
}
|
|
86
100
|
}
|
|
87
101
|
});
|
|
88
102
|
useEvent.useWindowEvent('click', ({
|
|
@@ -888,7 +888,7 @@ const Tab = /*#__PURE__*/React.forwardRef(({
|
|
|
888
888
|
})
|
|
889
889
|
}, /*#__PURE__*/React.createElement("button", {
|
|
890
890
|
type: "button",
|
|
891
|
-
tabIndex:
|
|
891
|
+
tabIndex: -1,
|
|
892
892
|
"aria-disabled": disabled,
|
|
893
893
|
"aria-hidden": selectedIndex === index$1 && dismissable ? 'false' : 'true',
|
|
894
894
|
disabled: disabled,
|
|
@@ -206,8 +206,10 @@ const TextArea = frFn((props, forwardRef) => {
|
|
|
206
206
|
}, labelText) : null;
|
|
207
207
|
const counter = enableCounter && maxCount && (counterMode === 'character' || counterMode === 'word') ? /*#__PURE__*/React.createElement(Text.Text, {
|
|
208
208
|
as: "div",
|
|
209
|
-
className: counterClasses
|
|
209
|
+
className: counterClasses,
|
|
210
|
+
"aria-hidden": "true"
|
|
210
211
|
}, `${textCount}/${maxCount}`) : null;
|
|
212
|
+
const counterDescriptionId = enableCounter && maxCount ? `${id}-counter-desc` : undefined;
|
|
211
213
|
const helperId = !helperText ? undefined : `text-area-helper-text-${textAreaInstanceId}`;
|
|
212
214
|
const helper = helperText ? /*#__PURE__*/React.createElement(Text.Text, {
|
|
213
215
|
as: "div",
|
|
@@ -238,8 +240,13 @@ const TextArea = frFn((props, forwardRef) => {
|
|
|
238
240
|
let ariaDescribedBy;
|
|
239
241
|
if (invalid) {
|
|
240
242
|
ariaDescribedBy = errorId;
|
|
241
|
-
} else if (
|
|
242
|
-
ariaDescribedBy =
|
|
243
|
+
} else if (warn && !isFluid) {
|
|
244
|
+
ariaDescribedBy = warnId;
|
|
245
|
+
} else {
|
|
246
|
+
const ids = [];
|
|
247
|
+
if (!isFluid && helperText && helperId) ids.push(helperId);
|
|
248
|
+
if (counterDescriptionId) ids.push(counterDescriptionId);
|
|
249
|
+
ariaDescribedBy = ids.length > 0 ? ids.join(' ') : undefined;
|
|
243
250
|
}
|
|
244
251
|
if (enableCounter) {
|
|
245
252
|
// handle different counter mode
|
|
@@ -296,7 +303,10 @@ const TextArea = frFn((props, forwardRef) => {
|
|
|
296
303
|
className: formItemClasses
|
|
297
304
|
}, /*#__PURE__*/React.createElement("div", {
|
|
298
305
|
className: `${prefix}--text-area__label-wrapper`
|
|
299
|
-
}, label, counter), /*#__PURE__*/React.createElement("
|
|
306
|
+
}, label, counter), enableCounter && maxCount && /*#__PURE__*/React.createElement("span", {
|
|
307
|
+
id: counterDescriptionId,
|
|
308
|
+
className: `${prefix}--visually-hidden`
|
|
309
|
+
}, counterMode === 'word' ? `Word limit ${maxCount}` : `Character limit ${maxCount}`), /*#__PURE__*/React.createElement("div", {
|
|
300
310
|
ref: wrapperRef,
|
|
301
311
|
className: textAreaWrapperClasses,
|
|
302
312
|
"data-invalid": invalid || null
|
|
@@ -139,6 +139,7 @@ const PasswordInput = /*#__PURE__*/React.forwardRef(({
|
|
|
139
139
|
[`${prefix}--tooltip--${tooltipPosition}`]: tooltipPosition,
|
|
140
140
|
[`${prefix}--tooltip--align-${tooltipAlignment}`]: tooltipAlignment
|
|
141
141
|
});
|
|
142
|
+
const tooltipClasses = cx(`${prefix}--toggle-password-tooltip`, `${prefix}--icon-tooltip`);
|
|
142
143
|
let align = undefined;
|
|
143
144
|
if (tooltipPosition === 'top' || tooltipPosition === 'bottom') {
|
|
144
145
|
if (tooltipAlignment === 'center') {
|
|
@@ -176,12 +177,12 @@ const PasswordInput = /*#__PURE__*/React.forwardRef(({
|
|
|
176
177
|
className: `${prefix}--text-input__divider`
|
|
177
178
|
}), /*#__PURE__*/React.createElement(Tooltip.Tooltip, {
|
|
178
179
|
align: align,
|
|
179
|
-
className:
|
|
180
|
+
className: tooltipClasses,
|
|
180
181
|
label: passwordIsVisible ? hidePasswordLabel : showPasswordLabel
|
|
181
182
|
}, /*#__PURE__*/React.createElement("button", {
|
|
182
183
|
type: "button",
|
|
183
184
|
className: passwordVisibilityToggleClasses,
|
|
184
|
-
disabled: disabled
|
|
185
|
+
disabled: disabled,
|
|
185
186
|
onClick: handleTogglePasswordVisibility
|
|
186
187
|
}, passwordVisibilityIcon)));
|
|
187
188
|
React.useEffect(() => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carbon/react",
|
|
3
3
|
"description": "React components for the Carbon Design System",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.99.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@babel/runtime": "^7.27.3",
|
|
55
55
|
"@carbon/feature-flags": ">=0.32.0",
|
|
56
|
-
"@carbon/icons-react": "^11.
|
|
57
|
-
"@carbon/layout": "^11.
|
|
58
|
-
"@carbon/styles": "^1.
|
|
56
|
+
"@carbon/icons-react": "^11.73.0",
|
|
57
|
+
"@carbon/layout": "^11.46.0",
|
|
58
|
+
"@carbon/styles": "^1.98.0",
|
|
59
59
|
"@carbon/utilities": "^0.14.0",
|
|
60
60
|
"@floating-ui/react": "^0.27.4",
|
|
61
61
|
"@ibm/telemetry-js": "^1.5.0",
|
|
@@ -67,7 +67,6 @@
|
|
|
67
67
|
"invariant": "^2.2.3",
|
|
68
68
|
"prop-types": "^15.8.1",
|
|
69
69
|
"react-fast-compare": "^3.2.2",
|
|
70
|
-
"storybook-addon-accessibility-checker": "^9.2.0-rc.3",
|
|
71
70
|
"tabbable": "^6.2.0"
|
|
72
71
|
},
|
|
73
72
|
"devDependencies": {
|
|
@@ -79,8 +78,8 @@
|
|
|
79
78
|
"@babel/preset-env": "^7.27.2",
|
|
80
79
|
"@babel/preset-react": "^7.27.1",
|
|
81
80
|
"@babel/preset-typescript": "^7.27.1",
|
|
82
|
-
"@carbon/test-utils": "^10.
|
|
83
|
-
"@carbon/themes": "^11.
|
|
81
|
+
"@carbon/test-utils": "^10.39.0",
|
|
82
|
+
"@carbon/themes": "^11.66.0",
|
|
84
83
|
"@figma/code-connect": "^1.3.5",
|
|
85
84
|
"@rollup/plugin-babel": "^6.0.0",
|
|
86
85
|
"@rollup/plugin-commonjs": "^28.0.3",
|
|
@@ -105,8 +104,8 @@
|
|
|
105
104
|
"fs-extra": "^11.0.0",
|
|
106
105
|
"process": "^0.11.10",
|
|
107
106
|
"prop-types": "^15.8.1",
|
|
108
|
-
"react": "^19.
|
|
109
|
-
"react-dom": "^19.
|
|
107
|
+
"react": "^19.2.3",
|
|
108
|
+
"react-dom": "^19.2.3",
|
|
110
109
|
"react-is": "^16.13.1 || ^17.0.2 || ^18.3.1 || ^19.0.0",
|
|
111
110
|
"remark-gfm": "^4.0.0",
|
|
112
111
|
"rimraf": "^6.0.1",
|
|
@@ -132,5 +131,5 @@
|
|
|
132
131
|
"**/*.scss",
|
|
133
132
|
"**/*.css"
|
|
134
133
|
],
|
|
135
|
-
"gitHead": "
|
|
134
|
+
"gitHead": "ce7846aab8a3a1afe9b03d2d07d267af6cdb6ac2"
|
|
136
135
|
}
|