@carbon/react 1.89.0-rc.1 → 1.90.0-rc.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.
Files changed (68) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +1152 -849
  2. package/README.md +2 -2
  3. package/es/components/ComposedModal/ComposedModal.js +16 -5
  4. package/es/components/DataTable/DataTable.d.ts +3 -8
  5. package/es/components/DataTable/DataTable.js +10 -3
  6. package/es/components/DataTable/TableExpandRow.d.ts +33 -5
  7. package/es/components/DataTable/TableExpandRow.js +4 -2
  8. package/es/components/DataTable/TableHeader.d.ts +1 -2
  9. package/es/components/DataTable/TableHeader.js +1 -2
  10. package/es/components/DataTable/TableRow.d.ts +3 -6
  11. package/es/components/DataTable/TableRow.js +35 -22
  12. package/es/components/DataTable/state/sorting.d.ts +55 -14
  13. package/es/components/DataTable/state/sorting.js +40 -50
  14. package/es/components/DataTable/tools/sorting.js +4 -0
  15. package/es/components/Dialog/Dialog.d.ts +245 -0
  16. package/es/components/Dialog/Dialog.js +593 -0
  17. package/es/components/Dialog/index.d.ts +3 -251
  18. package/es/components/Dialog/index.js +1 -609
  19. package/es/components/FeatureFlags/index.d.ts +3 -1
  20. package/es/components/FeatureFlags/index.js +5 -2
  21. package/es/components/FileUploader/FileUploader.d.ts +28 -6
  22. package/es/components/FileUploader/FileUploader.js +152 -38
  23. package/es/components/Menu/MenuItem.js +2 -1
  24. package/es/components/Modal/Modal.js +14 -8
  25. package/es/components/NumberInput/NumberInput.js +11 -6
  26. package/es/components/Popover/index.js +6 -2
  27. package/es/components/StructuredList/StructuredList.js +4 -2
  28. package/es/components/Tag/DismissibleTag.d.ts +5 -0
  29. package/es/components/Tag/DismissibleTag.js +6 -1
  30. package/es/components/Toggletip/index.js +20 -8
  31. package/es/components/TreeView/TreeNode.d.ts +28 -0
  32. package/es/components/TreeView/TreeNode.js +6 -5
  33. package/es/index.d.ts +2 -1
  34. package/es/index.js +2 -0
  35. package/lib/components/ComposedModal/ComposedModal.js +16 -5
  36. package/lib/components/DataTable/DataTable.d.ts +3 -8
  37. package/lib/components/DataTable/DataTable.js +10 -3
  38. package/lib/components/DataTable/TableExpandRow.d.ts +33 -5
  39. package/lib/components/DataTable/TableExpandRow.js +4 -2
  40. package/lib/components/DataTable/TableHeader.d.ts +1 -2
  41. package/lib/components/DataTable/TableHeader.js +1 -2
  42. package/lib/components/DataTable/TableRow.d.ts +3 -6
  43. package/lib/components/DataTable/TableRow.js +34 -21
  44. package/lib/components/DataTable/state/sorting.d.ts +55 -14
  45. package/lib/components/DataTable/state/sorting.js +39 -50
  46. package/lib/components/DataTable/tools/sorting.js +4 -0
  47. package/lib/components/Dialog/Dialog.d.ts +245 -0
  48. package/lib/components/Dialog/Dialog.js +602 -0
  49. package/lib/components/Dialog/index.d.ts +3 -251
  50. package/lib/components/Dialog/index.js +9 -614
  51. package/lib/components/FeatureFlags/index.d.ts +3 -1
  52. package/lib/components/FeatureFlags/index.js +5 -2
  53. package/lib/components/FileUploader/FileUploader.d.ts +28 -6
  54. package/lib/components/FileUploader/FileUploader.js +151 -37
  55. package/lib/components/Menu/MenuItem.js +2 -1
  56. package/lib/components/Modal/Modal.js +21 -15
  57. package/lib/components/NumberInput/NumberInput.js +10 -5
  58. package/lib/components/Popover/index.js +6 -2
  59. package/lib/components/StructuredList/StructuredList.js +4 -2
  60. package/lib/components/Tag/DismissibleTag.d.ts +5 -0
  61. package/lib/components/Tag/DismissibleTag.js +6 -1
  62. package/lib/components/Toggletip/index.js +19 -7
  63. package/lib/components/TreeView/TreeNode.d.ts +28 -0
  64. package/lib/components/TreeView/TreeNode.js +6 -5
  65. package/lib/index.d.ts +2 -1
  66. package/lib/index.js +60 -58
  67. package/package.json +15 -15
  68. package/telemetry.yml +16 -0
@@ -5,6 +5,21 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import React, { type HTMLAttributes } from 'react';
8
+ interface FileItem {
9
+ name: string;
10
+ uuid: string;
11
+ file: File;
12
+ }
13
+ export interface FileChangeData {
14
+ addedFiles: FileItem[];
15
+ removedFiles: FileItem[];
16
+ currentFiles: FileItem[];
17
+ action: 'add' | 'remove' | 'clear';
18
+ }
19
+ export interface FileDeleteData {
20
+ deletedFile: FileItem;
21
+ remainingFiles: FileItem[];
22
+ }
8
23
  export interface FileUploaderProps extends HTMLAttributes<HTMLSpanElement> {
9
24
  /**
10
25
  * Specify the types of files that this input should be able to receive
@@ -52,20 +67,23 @@ export interface FileUploaderProps extends HTMLAttributes<HTMLSpanElement> {
52
67
  */
53
68
  name?: string;
54
69
  /**
55
- * Provide an optional `onChange` hook that is called each time the input is
56
- * changed
70
+ * Provide an optional `onChange` hook that is called each time the input is changed.
71
+ * When 'enable-enhanced-file-uploader' feature flag is enabled:
72
+ * - Also fires for file deletions and clearFiles operations
73
+ * - Event includes enhanced file information in event.target
57
74
  */
58
- onChange?: (event: any) => void;
75
+ onChange?: (event: any, data?: FileChangeData) => void;
59
76
  /**
60
77
  * Provide an optional `onClick` hook that is called each time the
61
78
  * FileUploader is clicked
62
79
  */
63
80
  onClick?: (event: any) => void;
64
81
  /**
65
- * Provide an optional `onDelete` hook that is called when an uploaded item
66
- * is removed
82
+ * Provide an optional `onDelete` hook that is called when an uploaded item is removed.
83
+ * When 'enable-enhanced-file-uploader' feature flag is enabled:
84
+ * - Event includes deleted file information in event.target
67
85
  */
68
- onDelete?: (event: any) => void;
86
+ onDelete?: (event: any, data?: FileDeleteData) => void;
69
87
  /**
70
88
  * Specify the size of the FileUploaderButton, from a list of available
71
89
  * sizes.
@@ -77,6 +95,10 @@ export interface FileUploaderHandle {
77
95
  * Clear internal state
78
96
  */
79
97
  clearFiles: () => void;
98
+ /**
99
+ * Get current files (only available when 'enable-enhanced-file-uploader' feature flag is enabled)
100
+ */
101
+ getCurrentFiles?: () => FileItem[];
80
102
  }
81
103
  declare const FileUploader: {
82
104
  <ItemType>(props: FileUploaderProps): React.ReactElement<any>;
@@ -8,7 +8,7 @@
8
8
  import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
9
9
  import cx from 'classnames';
10
10
  import PropTypes from 'prop-types';
11
- import React, { useState, useImperativeHandle } from 'react';
11
+ import React, { useState, useCallback, useImperativeHandle } from 'react';
12
12
  import Filename from './Filename.js';
13
13
  import FileUploaderButton from './FileUploaderButton.js';
14
14
  import { ButtonKinds } from '../Button/Button.js';
@@ -17,6 +17,7 @@ import { matches } from '../../internal/keyboard/match.js';
17
17
  import { usePrefix } from '../../internal/usePrefix.js';
18
18
  import '../Text/index.js';
19
19
  import { useId } from '../../internal/useId.js';
20
+ import { useFeatureFlag } from '../FeatureFlags/index.js';
20
21
  import { Text } from '../Text/Text.js';
21
22
 
22
23
  const FileUploader = /*#__PURE__*/React.forwardRef(({
@@ -38,45 +39,149 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
38
39
  ...other
39
40
  }, ref) => {
40
41
  const fileUploaderInstanceId = useId('file-uploader');
41
- const [state, updateState] = useState({
42
- fileNames: []
43
- });
44
- const nodes = [];
45
42
  const prefix = usePrefix();
46
- const handleChange = evt => {
43
+ const enhancedFileUploaderEnabled = useFeatureFlag('enable-enhanced-file-uploader');
44
+ const [fileItems, setFileItems] = useState([]);
45
+ const [legacyFileNames, setLegacyFileNames] = useState([]);
46
+ const [fileObjects, setFileObjects] = useState(new Map());
47
+ const nodes = [];
48
+ const createFileItem = file => ({
49
+ name: file.name,
50
+ uuid: `${fileUploaderInstanceId}-${Date.now()}-${Array.from(crypto.getRandomValues(new Uint8Array(8))).map(b => b.toString(36)).join('')}`,
51
+ file
52
+ });
53
+ const handleChange = useCallback(evt => {
47
54
  evt.stopPropagation();
48
- const filenames = Array.prototype.map.call(evt.target.files, file => file.name);
49
- updateState(prevState => ({
50
- fileNames: multiple ? [...new Set([...prevState.fileNames, ...filenames])] : filenames
51
- }));
52
- if (onChange) {
53
- onChange(evt);
55
+ const newFiles = Array.from(evt.target.files);
56
+ if (enhancedFileUploaderEnabled) {
57
+ const newFileItems = newFiles.map(createFileItem);
58
+ let updatedFileItems;
59
+ if (multiple) {
60
+ const existingNames = new Set(fileItems.map(item => item.name));
61
+ const uniqueNewItems = newFileItems.filter(item => !existingNames.has(item.name));
62
+ updatedFileItems = [...fileItems, ...uniqueNewItems];
63
+ } else {
64
+ updatedFileItems = newFileItems;
65
+ }
66
+ setFileItems(updatedFileItems);
67
+ if (onChange) {
68
+ const allFiles = updatedFileItems.map(item => item.file);
69
+ const enhancedEvent = {
70
+ ...evt,
71
+ target: {
72
+ ...evt.target,
73
+ files: Object.assign(allFiles, {
74
+ item: index => allFiles[index] || null
75
+ }),
76
+ addedFiles: newFileItems,
77
+ currentFiles: updatedFileItems,
78
+ action: 'add'
79
+ }
80
+ };
81
+ onChange(enhancedEvent);
82
+ }
83
+ } else {
84
+ const filenames = newFiles.map(file => file.name);
85
+ const updatedFileNames = multiple ? [...new Set([...legacyFileNames, ...filenames])] : filenames;
86
+ setLegacyFileNames(updatedFileNames);
87
+ setFileObjects(prevMap => {
88
+ const newMap = multiple ? new Map(prevMap) : new Map();
89
+ newFiles.forEach(file => {
90
+ newMap.set(file.name, file);
91
+ });
92
+ return newMap;
93
+ });
94
+ if (onChange) {
95
+ onChange(evt);
96
+ }
54
97
  }
55
- };
56
- const handleClick = (evt, {
98
+ }, [enhancedFileUploaderEnabled, fileItems, legacyFileNames, multiple, onChange]);
99
+ const handleClick = useCallback((evt, {
57
100
  index,
58
101
  filenameStatus
59
102
  }) => {
60
103
  if (filenameStatus === 'edit') {
61
104
  evt.stopPropagation();
62
- const filteredArray = state.fileNames.filter(filename => filename !== nodes[index]?.innerText?.trim());
63
- updateState({
64
- fileNames: filteredArray
65
- });
66
- if (onDelete) {
67
- onDelete(evt);
68
- uploaderButton.current?.focus?.();
105
+ if (enhancedFileUploaderEnabled) {
106
+ const deletedItem = fileItems[index];
107
+ if (!deletedItem) return;
108
+ const remainingItems = fileItems.filter((_, i) => i !== index);
109
+ setFileItems(remainingItems);
110
+ const remainingFiles = remainingItems.map(item => item.file);
111
+ const enhancedEvent = {
112
+ ...evt,
113
+ target: {
114
+ ...evt.target,
115
+ files: Object.assign(remainingFiles, {
116
+ item: index => remainingFiles[index] || null
117
+ }),
118
+ deletedFile: deletedItem,
119
+ deletedFileName: deletedItem.name,
120
+ remainingFiles: remainingItems,
121
+ currentFiles: remainingItems,
122
+ action: 'remove'
123
+ }
124
+ };
125
+ if (onDelete) {
126
+ onDelete(enhancedEvent);
127
+ }
128
+ if (onChange) {
129
+ onChange(enhancedEvent);
130
+ }
131
+ } else {
132
+ const deletedFileName = legacyFileNames[index];
133
+ const filteredArray = legacyFileNames.filter(filename => filename !== deletedFileName);
134
+ setLegacyFileNames(filteredArray);
135
+
136
+ // Update File objects
137
+ setFileObjects(prevMap => {
138
+ const newMap = new Map(prevMap);
139
+ if (deletedFileName) {
140
+ newMap.delete(deletedFileName);
141
+ }
142
+ return newMap;
143
+ });
144
+ if (onDelete) {
145
+ onDelete(evt);
146
+ }
147
+ }
148
+ if (onClick) {
149
+ onClick(evt);
69
150
  }
70
- onClick?.(evt);
151
+ uploaderButton.current?.focus?.();
71
152
  }
72
- };
153
+ }, [enhancedFileUploaderEnabled, fileItems, legacyFileNames, onDelete, onChange, onClick]);
73
154
  useImperativeHandle(ref, () => ({
74
155
  clearFiles() {
75
- updateState({
76
- fileNames: []
77
- });
78
- }
79
- }));
156
+ if (enhancedFileUploaderEnabled) {
157
+ const previousItems = [...fileItems];
158
+ setFileItems([]);
159
+ if (onChange && previousItems.length > 0) {
160
+ const enhancedEvent = {
161
+ target: {
162
+ files: Object.assign([], {
163
+ item: () => null
164
+ }),
165
+ clearedFiles: previousItems,
166
+ currentFiles: [],
167
+ action: 'clear'
168
+ },
169
+ preventDefault: () => {},
170
+ stopPropagation: () => {}
171
+ };
172
+ onChange(enhancedEvent);
173
+ }
174
+ } else {
175
+ setLegacyFileNames([]);
176
+ setFileObjects(new Map());
177
+ }
178
+ },
179
+ ...(enhancedFileUploaderEnabled && {
180
+ getCurrentFiles() {
181
+ return [...fileItems];
182
+ }
183
+ })
184
+ }), [enhancedFileUploaderEnabled, fileItems, onChange]);
80
185
  const uploaderButton = /*#__PURE__*/React.createRef();
81
186
  const classes = cx({
82
187
  [`${prefix}--form-item`]: true,
@@ -89,6 +194,15 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
89
194
  [`${prefix}--file__selected-file--md`]: size === 'field' || size === 'md',
90
195
  [`${prefix}--file__selected-file--sm`]: size === 'small' || size === 'sm'
91
196
  });
197
+ const displayFiles = enhancedFileUploaderEnabled ? fileItems.map((item, index) => ({
198
+ name: item.name,
199
+ key: item.uuid,
200
+ index
201
+ })) : legacyFileNames.map((name, index) => ({
202
+ name,
203
+ key: index,
204
+ index
205
+ }));
92
206
  return /*#__PURE__*/React.createElement("div", _extends({
93
207
  className: classes
94
208
  }, other), !labelTitle ? null : /*#__PURE__*/React.createElement(Text, {
@@ -112,32 +226,32 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
112
226
  "aria-describedby": fileUploaderInstanceId
113
227
  }), /*#__PURE__*/React.createElement("div", {
114
228
  className: `${prefix}--file-container`
115
- }, state.fileNames.length === 0 ? null : state.fileNames.map((name, index) => /*#__PURE__*/React.createElement("span", _extends({
116
- key: index,
229
+ }, displayFiles.length === 0 ? null : displayFiles.map(file => /*#__PURE__*/React.createElement("span", _extends({
230
+ key: file.key,
117
231
  className: selectedFileClasses,
118
232
  ref: node => {
119
- nodes[index] = node;
120
- } // eslint-disable-line
233
+ nodes[file.index] = node;
234
+ }
121
235
  }, other), /*#__PURE__*/React.createElement(Text, {
122
236
  as: "p",
123
237
  className: `${prefix}--file-filename`,
124
- id: name
125
- }, name), /*#__PURE__*/React.createElement("span", {
238
+ id: enhancedFileUploaderEnabled ? `${fileUploaderInstanceId}-file-${fileItems[file.index]?.uuid || file.index}` : `${fileUploaderInstanceId}-file-${file.index}`
239
+ }, file.name), /*#__PURE__*/React.createElement("span", {
126
240
  className: `${prefix}--file__state-container`
127
241
  }, /*#__PURE__*/React.createElement(Filename, {
128
- name: name,
242
+ name: file.name,
129
243
  iconDescription: iconDescription,
130
244
  status: filenameStatus,
131
245
  onKeyDown: evt => {
132
246
  if (matches(evt, [Enter, Space])) {
133
247
  handleClick(evt, {
134
- index,
248
+ index: file.index,
135
249
  filenameStatus
136
250
  });
137
251
  }
138
252
  },
139
253
  onClick: evt => handleClick(evt, {
140
- index,
254
+ index: file.index,
141
255
  filenameStatus
142
256
  })
143
257
  }))))));
@@ -207,7 +321,7 @@ FileUploader.propTypes = {
207
321
  * Specify the size of the FileUploaderButton, from a list of available
208
322
  * sizes.
209
323
  */
210
- size: PropTypes.oneOf(['sm', 'md', 'lg'])
324
+ size: PropTypes.oneOf(['sm', 'small', 'md', 'field', 'lg'])
211
325
  };
212
326
 
213
327
  export { FileUploader as default };
@@ -49,7 +49,8 @@ const MenuItem = /*#__PURE__*/forwardRef(function MenuItem({
49
49
  middleware: [offset({
50
50
  mainAxis: -6,
51
51
  crossAxis: -6
52
- })]
52
+ })],
53
+ strategy: 'fixed'
53
54
  });
54
55
  const {
55
56
  getReferenceProps,
@@ -30,7 +30,7 @@ import '../Text/index.js';
30
30
  import { useFeatureFlag } from '../FeatureFlags/index.js';
31
31
  import { composeEventHandlers } from '../../tools/events.js';
32
32
  import { deprecate } from '../../prop-types/deprecate.js';
33
- import { unstable__Dialog } from '../Dialog/index.js';
33
+ import { Dialog } from '../Dialog/Dialog.js';
34
34
  import { AILabel } from '../AILabel/index.js';
35
35
  import { isComponentElement } from '../../internal/utils.js';
36
36
  import { warning } from '../../internal/warning.js';
@@ -38,7 +38,7 @@ import { debounce } from '../../node_modules/es-toolkit/dist/compat/function/deb
38
38
  import { Text } from '../Text/Text.js';
39
39
 
40
40
  const ModalSizes = ['xs', 'sm', 'md', 'lg'];
41
- const invalidOutsideClickMessage = '`Modal`: `preventCloseOnClickOutside` should not be `false` when `passiveModal` is `false`. Non-passive `Modal`s should not be dismissible by clicking outside.';
41
+ const invalidOutsideClickMessage = '`<Modal>` prop `preventCloseOnClickOutside` should not be `false` when ' + '`passiveModal` is `false`. Transactional, non-passive Modals should ' + 'not be dissmissable by clicking outside. ' + 'See: https://carbondesignsystem.com/components/modal/usage/#transactional-modal';
42
42
  const Modal = /*#__PURE__*/React.forwardRef(function Modal({
43
43
  'aria-label': ariaLabelProp,
44
44
  children,
@@ -64,7 +64,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
64
64
  size,
65
65
  hasScrollingContent = false,
66
66
  closeButtonLabel = 'Close',
67
- preventCloseOnClickOutside = !passiveModal,
67
+ preventCloseOnClickOutside,
68
68
  isFullWidth,
69
69
  launcherButtonRef,
70
70
  loadingStatus = 'inactive',
@@ -96,9 +96,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
96
96
  const focusTrapWithoutSentinels = useFeatureFlag('enable-experimental-focus-wrap-without-sentinels');
97
97
  const enableDialogElement = useFeatureFlag('enable-dialog-element');
98
98
  process.env.NODE_ENV !== "production" ? warning(!(focusTrapWithoutSentinels && enableDialogElement), '`<Modal>` detected both `focusTrapWithoutSentinels` and ' + '`enableDialogElement` feature flags are enabled. The native dialog ' + 'element handles focus, so `enableDialogElement` must be off for ' + '`focusTrapWithoutSentinels` to have any effect.') : void 0;
99
- if (!passiveModal && preventCloseOnClickOutside === false) {
100
- console.error(invalidOutsideClickMessage);
101
- }
99
+ process.env.NODE_ENV !== "production" ? warning(!(!passiveModal && preventCloseOnClickOutside === false), invalidOutsideClickMessage) : void 0;
102
100
  function isCloseButton(element) {
103
101
  return !onSecondarySubmit && element === secondaryButton.current || element.classList.contains(modalCloseButtonClass);
104
102
  }
@@ -125,7 +123,15 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
125
123
  target
126
124
  } = evt;
127
125
  evt.stopPropagation();
128
- if (!preventCloseOnClickOutside && target instanceof Node && !elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && innerModal.current && !innerModal.current.contains(target)) {
126
+ const shouldCloseOnOutsideClick =
127
+ // Passive modals can close on clicks outside the modal when
128
+ // preventCloseOnClickOutside is undefined or explicitly set to false.
129
+ passiveModal && !preventCloseOnClickOutside ||
130
+ // Non-passive modals have to explicitly opt-in for close on outside
131
+ // behavior by explicitly setting preventCloseOnClickOutside to false,
132
+ // rather than just leaving it undefined.
133
+ !passiveModal && preventCloseOnClickOutside === false;
134
+ if (shouldCloseOnOutsideClick && target instanceof Node && !elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && innerModal.current && !innerModal.current.contains(target)) {
129
135
  onRequestClose(evt);
130
136
  }
131
137
  }
@@ -314,7 +320,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
314
320
  // alertdialog is the only permitted aria role for a native dialog element
315
321
  // https://www.w3.org/TR/html-aria/#docconformance:~:text=Role%3A-,alertdialog,-.%20(dialog%20is
316
322
  const isAlertDialog = alert && !passiveModal;
317
- const modalBody = enableDialogElement ? /*#__PURE__*/React.createElement(unstable__Dialog, {
323
+ const modalBody = enableDialogElement ? /*#__PURE__*/React.createElement(Dialog, {
318
324
  open: open,
319
325
  focusAfterCloseRef: launcherButtonRef,
320
326
  modal: true,
@@ -9,7 +9,7 @@ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js
9
9
  import { Subtract, Add } from '@carbon/icons-react';
10
10
  import cx from 'classnames';
11
11
  import PropTypes from 'prop-types';
12
- import React, { useContext, useState, useMemo, useCallback, useRef, cloneElement, useEffect } from 'react';
12
+ import React, { useContext, useState, useMemo, useCallback, useRef, useEffect, cloneElement } from 'react';
13
13
  import { useMergedRefs } from '../../internal/useMergedRefs.js';
14
14
  import { useNormalizedInputProps } from '../../internal/useNormalizedInputProps.js';
15
15
  import { usePrefix } from '../../internal/usePrefix.js';
@@ -173,11 +173,16 @@ const NumberInput = /*#__PURE__*/React.forwardRef(function NumberInput(props, fo
173
173
  [`${prefix}--number__invalid`]: normalizedProps.invalid || normalizedProps.warn,
174
174
  [`${prefix}--number__invalid--warning`]: normalizedProps.warn
175
175
  });
176
- if (controlledValue !== prevControlledValue && !(isNaN(Number(controlledValue)) === isNaN(Number(prevControlledValue)))) {
177
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
178
- setValue(controlledValue);
179
- setPrevControlledValue(controlledValue);
180
- }
176
+ useEffect(() => {
177
+ if (type === 'number' && controlledValue !== undefined) {
178
+ if (allowEmpty && controlledValue === '') {
179
+ setValue('');
180
+ } else {
181
+ setValue(controlledValue);
182
+ }
183
+ setPrevControlledValue(controlledValue);
184
+ }
185
+ }, [controlledValue, type, allowEmpty]);
181
186
  let ariaDescribedBy = undefined;
182
187
  if (normalizedProps.invalid) {
183
188
  ariaDescribedBy = normalizedProps.invalidId;
@@ -61,8 +61,12 @@ forwardRef) {
61
61
  // The `Popover` should close whenever it and its children loses focus
62
62
  useEvent(popover, 'focusout', event => {
63
63
  const relatedTarget = event.relatedTarget;
64
-
65
- // No relatedTarget, focus moved to nowhere, so close the popover
64
+ if (isTabTip) {
65
+ if (relatedTarget && !popover.current?.contains(relatedTarget)) {
66
+ onRequestClose?.();
67
+ }
68
+ return;
69
+ }
66
70
  if (!relatedTarget) {
67
71
  onRequestClose?.();
68
72
  return;
@@ -189,8 +189,10 @@ function StructuredListRow(props) {
189
189
  setHasFocusWithin(true);
190
190
  }
191
191
  },
192
- onFocus: () => {
193
- setHasFocusWithin(true);
192
+ onFocus: event => {
193
+ if (selection || event.currentTarget === event.target) {
194
+ setHasFocusWithin(true);
195
+ }
194
196
  },
195
197
  onBlur: () => {
196
198
  setHasFocusWithin(false);
@@ -7,6 +7,7 @@
7
7
  import React, { type ReactNode } from 'react';
8
8
  import { PolymorphicProps } from '../../types/common';
9
9
  import { SIZES, TYPES } from './Tag';
10
+ import { PopoverAlignment } from '../Popover';
10
11
  export interface DismissibleTagBaseProps {
11
12
  /**
12
13
  * Provide a custom className that is applied to the containing <span>
@@ -20,6 +21,10 @@ export interface DismissibleTagBaseProps {
20
21
  * Specify if the `DismissibleTag` is disabled
21
22
  */
22
23
  disabled?: boolean;
24
+ /**
25
+ * Specify the tooltip alignment for the dismiss button
26
+ */
27
+ dismissTooltipAlignment?: PopoverAlignment;
23
28
  /**
24
29
  * Provide a custom tooltip label for the dismiss button
25
30
  */
@@ -37,6 +37,7 @@ const DismissibleTag = /*#__PURE__*/forwardRef(({
37
37
  text,
38
38
  tagTitle,
39
39
  type,
40
+ dismissTooltipAlignment = 'bottom',
40
41
  dismissTooltipLabel = 'Dismiss tag',
41
42
  ...other
42
43
  }, forwardRef) => {
@@ -88,7 +89,7 @@ const DismissibleTag = /*#__PURE__*/forwardRef(({
88
89
  className: `${prefix}--tag__decorator`
89
90
  }, normalizedDecorator) : '', /*#__PURE__*/React.createElement(Tooltip, {
90
91
  label: dismissActionLabel,
91
- align: "bottom",
92
+ align: dismissTooltipAlignment,
92
93
  className: tooltipClasses,
93
94
  leaveDelayMs: 0,
94
95
  closeOnActivation: true
@@ -113,6 +114,10 @@ DismissibleTag.propTypes = {
113
114
  * Specify if the `DismissibleTag` is disabled
114
115
  */
115
116
  disabled: PropTypes.bool,
117
+ /**
118
+ * Specify the tooltip alignment for the dismiss button
119
+ */
120
+ dismissTooltipAlignment: PropTypes.oneOf(['top', 'bottom', 'left', 'right', 'top-start', 'top-end', 'bottom-start', 'bottom-end', 'left-end', 'left-start', 'right-end', 'right-start']),
116
121
  /**
117
122
  * Provide a custom tooltip label for the dismiss button
118
123
  */
@@ -8,7 +8,7 @@
8
8
  import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
9
9
  import cx from 'classnames';
10
10
  import PropTypes from 'prop-types';
11
- import React, { useContext, useRef, useState } from 'react';
11
+ import React, { useContext, useRef, useState, useEffect } from 'react';
12
12
  import { Popover, PopoverContent } from '../Popover/index.js';
13
13
  import { Escape } from '../../internal/keyboard/keys.js';
14
14
  import { match } from '../../internal/keyboard/match.js';
@@ -100,6 +100,7 @@ function Toggletip({
100
100
  };
101
101
  const onKeyDown = event => {
102
102
  if (open && match(event, Escape)) {
103
+ event.stopPropagation();
103
104
  actions.close();
104
105
 
105
106
  // If the menu is closed while focus is still inside the menu, it should return to the trigger button (#12922)
@@ -126,13 +127,24 @@ function Toggletip({
126
127
  actions.close();
127
128
  }
128
129
  });
129
- useWindowEvent('click', ({
130
- target
131
- }) => {
132
- if (open && target instanceof Node && !ref.current?.contains(target)) {
133
- actions.close();
134
- }
135
- });
130
+ useEffect(() => {
131
+ if (!ref.current) return;
132
+ const targetDocument = ref.current.ownerDocument || document;
133
+ const eventType = 'PointerEvent' in window ? 'pointerdown' : 'mousedown';
134
+ const handleOutsideClick = event => {
135
+ const node = event.target;
136
+ if (open && node && !ref.current.contains(node)) {
137
+ setOpen(false);
138
+ }
139
+ };
140
+ const options = {
141
+ capture: true
142
+ };
143
+ targetDocument.addEventListener(eventType, handleOutsideClick, options);
144
+ return () => {
145
+ targetDocument.removeEventListener(eventType, handleOutsideClick, options);
146
+ };
147
+ }, [open]);
136
148
  return /*#__PURE__*/React.createElement(ToggletipContext.Provider, {
137
149
  value: value
138
150
  }, /*#__PURE__*/React.createElement(Popover, _extends({
@@ -11,6 +11,9 @@ export type TreeNodeProps = {
11
11
  /**
12
12
  * **Note:** this is controlled by the parent TreeView component, do not set manually.
13
13
  * The ID of the active node in the tree
14
+ *
15
+ * @deprecated The `active` prop for `TreeNode` has
16
+ * been deprecated after the introduction of context. It will be removed in the next major release.
14
17
  */
15
18
  active?: string | number;
16
19
  /**
@@ -29,6 +32,9 @@ export type TreeNodeProps = {
29
32
  /**
30
33
  * **Note:** this is controlled by the parent TreeView component, do not set manually.
31
34
  * TreeNode depth to determine spacing
35
+ *
36
+ * @deprecated The `depth` prop for `TreeNode` has
37
+ * been deprecated after the introduction of context. It will be removed in the next major release.
32
38
  */
33
39
  depth?: number;
34
40
  /**
@@ -49,6 +55,9 @@ export type TreeNodeProps = {
49
55
  label: React.ReactNode;
50
56
  /**
51
57
  * Callback function for when the node receives or loses focus
58
+ *
59
+ * @deprecated The `onNodeFocusEvent` prop for `TreeNode` has
60
+ * been deprecated after the introduction of context. It will be removed in the next major release.
52
61
  */
53
62
  onNodeFocusEvent?: (event: React.FocusEvent<HTMLElement>) => void;
54
63
  /**
@@ -61,6 +70,9 @@ export type TreeNodeProps = {
61
70
  onToggle?: UncontrolledOnToggle | ControlledOnToggle;
62
71
  /**
63
72
  * Callback function for when any node in the tree is selected
73
+ *
74
+ * @deprecated The `onTreeSelect` prop for `TreeNode` has
75
+ * been deprecated after the introduction of context. It will be removed in the next major release.
64
76
  */
65
77
  onTreeSelect?: (event: React.MouseEvent | React.KeyboardEvent, node: Pick<TreeNodeProps, 'id' | 'label' | 'value'>) => void;
66
78
  /**
@@ -70,6 +82,8 @@ export type TreeNodeProps = {
70
82
  /**
71
83
  * **Note:** this is controlled by the parent TreeView component, do not set manually.
72
84
  * Array containing all selected node IDs in the tree
85
+ * @deprecated The `selected` prop for `TreeNode` has
86
+ * been deprecated after the introduction of context. It will be removed in the next major release.
73
87
  */
74
88
  selected?: Array<string | number>;
75
89
  /**
@@ -97,6 +111,9 @@ declare const TreeNode: React.ForwardRefExoticComponent<{
97
111
  /**
98
112
  * **Note:** this is controlled by the parent TreeView component, do not set manually.
99
113
  * The ID of the active node in the tree
114
+ *
115
+ * @deprecated The `active` prop for `TreeNode` has
116
+ * been deprecated after the introduction of context. It will be removed in the next major release.
100
117
  */
101
118
  active?: string | number;
102
119
  /**
@@ -115,6 +132,9 @@ declare const TreeNode: React.ForwardRefExoticComponent<{
115
132
  /**
116
133
  * **Note:** this is controlled by the parent TreeView component, do not set manually.
117
134
  * TreeNode depth to determine spacing
135
+ *
136
+ * @deprecated The `depth` prop for `TreeNode` has
137
+ * been deprecated after the introduction of context. It will be removed in the next major release.
118
138
  */
119
139
  depth?: number;
120
140
  /**
@@ -135,6 +155,9 @@ declare const TreeNode: React.ForwardRefExoticComponent<{
135
155
  label: React.ReactNode;
136
156
  /**
137
157
  * Callback function for when the node receives or loses focus
158
+ *
159
+ * @deprecated The `onNodeFocusEvent` prop for `TreeNode` has
160
+ * been deprecated after the introduction of context. It will be removed in the next major release.
138
161
  */
139
162
  onNodeFocusEvent?: (event: React.FocusEvent<HTMLElement>) => void;
140
163
  /**
@@ -147,6 +170,9 @@ declare const TreeNode: React.ForwardRefExoticComponent<{
147
170
  onToggle?: UncontrolledOnToggle | ControlledOnToggle;
148
171
  /**
149
172
  * Callback function for when any node in the tree is selected
173
+ *
174
+ * @deprecated The `onTreeSelect` prop for `TreeNode` has
175
+ * been deprecated after the introduction of context. It will be removed in the next major release.
150
176
  */
151
177
  onTreeSelect?: (event: React.MouseEvent | React.KeyboardEvent, node: Pick<TreeNodeProps, "id" | "label" | "value">) => void;
152
178
  /**
@@ -156,6 +182,8 @@ declare const TreeNode: React.ForwardRefExoticComponent<{
156
182
  /**
157
183
  * **Note:** this is controlled by the parent TreeView component, do not set manually.
158
184
  * Array containing all selected node IDs in the tree
185
+ * @deprecated The `selected` prop for `TreeNode` has
186
+ * been deprecated after the introduction of context. It will be removed in the next major release.
159
187
  */
160
188
  selected?: Array<string | number>;
161
189
  /**