@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>;
@@ -21,6 +21,7 @@ var match = require('../../internal/keyboard/match.js');
21
21
  var usePrefix = require('../../internal/usePrefix.js');
22
22
  require('../Text/index.js');
23
23
  var useId = require('../../internal/useId.js');
24
+ var index = require('../FeatureFlags/index.js');
24
25
  var Text = require('../Text/Text.js');
25
26
 
26
27
  const FileUploader = /*#__PURE__*/React.forwardRef(({
@@ -42,45 +43,149 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
42
43
  ...other
43
44
  }, ref) => {
44
45
  const fileUploaderInstanceId = useId.useId('file-uploader');
45
- const [state, updateState] = React.useState({
46
- fileNames: []
47
- });
48
- const nodes = [];
49
46
  const prefix = usePrefix.usePrefix();
50
- const handleChange = evt => {
47
+ const enhancedFileUploaderEnabled = index.useFeatureFlag('enable-enhanced-file-uploader');
48
+ const [fileItems, setFileItems] = React.useState([]);
49
+ const [legacyFileNames, setLegacyFileNames] = React.useState([]);
50
+ const [fileObjects, setFileObjects] = React.useState(new Map());
51
+ const nodes = [];
52
+ const createFileItem = file => ({
53
+ name: file.name,
54
+ uuid: `${fileUploaderInstanceId}-${Date.now()}-${Array.from(crypto.getRandomValues(new Uint8Array(8))).map(b => b.toString(36)).join('')}`,
55
+ file
56
+ });
57
+ const handleChange = React.useCallback(evt => {
51
58
  evt.stopPropagation();
52
- const filenames = Array.prototype.map.call(evt.target.files, file => file.name);
53
- updateState(prevState => ({
54
- fileNames: multiple ? [...new Set([...prevState.fileNames, ...filenames])] : filenames
55
- }));
56
- if (onChange) {
57
- onChange(evt);
59
+ const newFiles = Array.from(evt.target.files);
60
+ if (enhancedFileUploaderEnabled) {
61
+ const newFileItems = newFiles.map(createFileItem);
62
+ let updatedFileItems;
63
+ if (multiple) {
64
+ const existingNames = new Set(fileItems.map(item => item.name));
65
+ const uniqueNewItems = newFileItems.filter(item => !existingNames.has(item.name));
66
+ updatedFileItems = [...fileItems, ...uniqueNewItems];
67
+ } else {
68
+ updatedFileItems = newFileItems;
69
+ }
70
+ setFileItems(updatedFileItems);
71
+ if (onChange) {
72
+ const allFiles = updatedFileItems.map(item => item.file);
73
+ const enhancedEvent = {
74
+ ...evt,
75
+ target: {
76
+ ...evt.target,
77
+ files: Object.assign(allFiles, {
78
+ item: index => allFiles[index] || null
79
+ }),
80
+ addedFiles: newFileItems,
81
+ currentFiles: updatedFileItems,
82
+ action: 'add'
83
+ }
84
+ };
85
+ onChange(enhancedEvent);
86
+ }
87
+ } else {
88
+ const filenames = newFiles.map(file => file.name);
89
+ const updatedFileNames = multiple ? [...new Set([...legacyFileNames, ...filenames])] : filenames;
90
+ setLegacyFileNames(updatedFileNames);
91
+ setFileObjects(prevMap => {
92
+ const newMap = multiple ? new Map(prevMap) : new Map();
93
+ newFiles.forEach(file => {
94
+ newMap.set(file.name, file);
95
+ });
96
+ return newMap;
97
+ });
98
+ if (onChange) {
99
+ onChange(evt);
100
+ }
58
101
  }
59
- };
60
- const handleClick = (evt, {
102
+ }, [enhancedFileUploaderEnabled, fileItems, legacyFileNames, multiple, onChange]);
103
+ const handleClick = React.useCallback((evt, {
61
104
  index,
62
105
  filenameStatus
63
106
  }) => {
64
107
  if (filenameStatus === 'edit') {
65
108
  evt.stopPropagation();
66
- const filteredArray = state.fileNames.filter(filename => filename !== nodes[index]?.innerText?.trim());
67
- updateState({
68
- fileNames: filteredArray
69
- });
70
- if (onDelete) {
71
- onDelete(evt);
72
- uploaderButton.current?.focus?.();
109
+ if (enhancedFileUploaderEnabled) {
110
+ const deletedItem = fileItems[index];
111
+ if (!deletedItem) return;
112
+ const remainingItems = fileItems.filter((_, i) => i !== index);
113
+ setFileItems(remainingItems);
114
+ const remainingFiles = remainingItems.map(item => item.file);
115
+ const enhancedEvent = {
116
+ ...evt,
117
+ target: {
118
+ ...evt.target,
119
+ files: Object.assign(remainingFiles, {
120
+ item: index => remainingFiles[index] || null
121
+ }),
122
+ deletedFile: deletedItem,
123
+ deletedFileName: deletedItem.name,
124
+ remainingFiles: remainingItems,
125
+ currentFiles: remainingItems,
126
+ action: 'remove'
127
+ }
128
+ };
129
+ if (onDelete) {
130
+ onDelete(enhancedEvent);
131
+ }
132
+ if (onChange) {
133
+ onChange(enhancedEvent);
134
+ }
135
+ } else {
136
+ const deletedFileName = legacyFileNames[index];
137
+ const filteredArray = legacyFileNames.filter(filename => filename !== deletedFileName);
138
+ setLegacyFileNames(filteredArray);
139
+
140
+ // Update File objects
141
+ setFileObjects(prevMap => {
142
+ const newMap = new Map(prevMap);
143
+ if (deletedFileName) {
144
+ newMap.delete(deletedFileName);
145
+ }
146
+ return newMap;
147
+ });
148
+ if (onDelete) {
149
+ onDelete(evt);
150
+ }
151
+ }
152
+ if (onClick) {
153
+ onClick(evt);
73
154
  }
74
- onClick?.(evt);
155
+ uploaderButton.current?.focus?.();
75
156
  }
76
- };
157
+ }, [enhancedFileUploaderEnabled, fileItems, legacyFileNames, onDelete, onChange, onClick]);
77
158
  React.useImperativeHandle(ref, () => ({
78
159
  clearFiles() {
79
- updateState({
80
- fileNames: []
81
- });
82
- }
83
- }));
160
+ if (enhancedFileUploaderEnabled) {
161
+ const previousItems = [...fileItems];
162
+ setFileItems([]);
163
+ if (onChange && previousItems.length > 0) {
164
+ const enhancedEvent = {
165
+ target: {
166
+ files: Object.assign([], {
167
+ item: () => null
168
+ }),
169
+ clearedFiles: previousItems,
170
+ currentFiles: [],
171
+ action: 'clear'
172
+ },
173
+ preventDefault: () => {},
174
+ stopPropagation: () => {}
175
+ };
176
+ onChange(enhancedEvent);
177
+ }
178
+ } else {
179
+ setLegacyFileNames([]);
180
+ setFileObjects(new Map());
181
+ }
182
+ },
183
+ ...(enhancedFileUploaderEnabled && {
184
+ getCurrentFiles() {
185
+ return [...fileItems];
186
+ }
187
+ })
188
+ }), [enhancedFileUploaderEnabled, fileItems, onChange]);
84
189
  const uploaderButton = /*#__PURE__*/React.createRef();
85
190
  const classes = cx({
86
191
  [`${prefix}--form-item`]: true,
@@ -93,6 +198,15 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
93
198
  [`${prefix}--file__selected-file--md`]: size === 'field' || size === 'md',
94
199
  [`${prefix}--file__selected-file--sm`]: size === 'small' || size === 'sm'
95
200
  });
201
+ const displayFiles = enhancedFileUploaderEnabled ? fileItems.map((item, index) => ({
202
+ name: item.name,
203
+ key: item.uuid,
204
+ index
205
+ })) : legacyFileNames.map((name, index) => ({
206
+ name,
207
+ key: index,
208
+ index
209
+ }));
96
210
  return /*#__PURE__*/React.createElement("div", _rollupPluginBabelHelpers.extends({
97
211
  className: classes
98
212
  }, other), !labelTitle ? null : /*#__PURE__*/React.createElement(Text.Text, {
@@ -116,32 +230,32 @@ const FileUploader = /*#__PURE__*/React.forwardRef(({
116
230
  "aria-describedby": fileUploaderInstanceId
117
231
  }), /*#__PURE__*/React.createElement("div", {
118
232
  className: `${prefix}--file-container`
119
- }, state.fileNames.length === 0 ? null : state.fileNames.map((name, index) => /*#__PURE__*/React.createElement("span", _rollupPluginBabelHelpers.extends({
120
- key: index,
233
+ }, displayFiles.length === 0 ? null : displayFiles.map(file => /*#__PURE__*/React.createElement("span", _rollupPluginBabelHelpers.extends({
234
+ key: file.key,
121
235
  className: selectedFileClasses,
122
236
  ref: node => {
123
- nodes[index] = node;
124
- } // eslint-disable-line
237
+ nodes[file.index] = node;
238
+ }
125
239
  }, other), /*#__PURE__*/React.createElement(Text.Text, {
126
240
  as: "p",
127
241
  className: `${prefix}--file-filename`,
128
- id: name
129
- }, name), /*#__PURE__*/React.createElement("span", {
242
+ id: enhancedFileUploaderEnabled ? `${fileUploaderInstanceId}-file-${fileItems[file.index]?.uuid || file.index}` : `${fileUploaderInstanceId}-file-${file.index}`
243
+ }, file.name), /*#__PURE__*/React.createElement("span", {
130
244
  className: `${prefix}--file__state-container`
131
245
  }, /*#__PURE__*/React.createElement(Filename.default, {
132
- name: name,
246
+ name: file.name,
133
247
  iconDescription: iconDescription,
134
248
  status: filenameStatus,
135
249
  onKeyDown: evt => {
136
250
  if (match.matches(evt, [keys.Enter, keys.Space])) {
137
251
  handleClick(evt, {
138
- index,
252
+ index: file.index,
139
253
  filenameStatus
140
254
  });
141
255
  }
142
256
  },
143
257
  onClick: evt => handleClick(evt, {
144
- index,
258
+ index: file.index,
145
259
  filenameStatus
146
260
  })
147
261
  }))))));
@@ -211,7 +325,7 @@ FileUploader.propTypes = {
211
325
  * Specify the size of the FileUploaderButton, from a list of available
212
326
  * sizes.
213
327
  */
214
- size: PropTypes.oneOf(['sm', 'md', 'lg'])
328
+ size: PropTypes.oneOf(['sm', 'small', 'md', 'field', 'lg'])
215
329
  };
216
330
 
217
331
  exports.default = FileUploader;
@@ -51,7 +51,8 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem({
51
51
  middleware: [react.offset({
52
52
  mainAxis: -6,
53
53
  crossAxis: -6
54
- })]
54
+ })],
55
+ strategy: 'fixed'
55
56
  });
56
57
  const {
57
58
  getReferenceProps,
@@ -19,7 +19,7 @@ var Button = require('../Button/Button.js');
19
19
  require('../Button/Button.Skeleton.js');
20
20
  var ButtonSet = require('../ButtonSet/ButtonSet.js');
21
21
  var InlineLoading = require('../InlineLoading/InlineLoading.js');
22
- var index$4 = require('../Layer/index.js');
22
+ var index$3 = require('../Layer/index.js');
23
23
  var requiredIfGivenPropIsTruthy = require('../../prop-types/requiredIfGivenPropIsTruthy.js');
24
24
  var wrapFocus = require('../../internal/wrapFocus.js');
25
25
  var useIsomorphicEffect = require('../../internal/useIsomorphicEffect.js');
@@ -28,13 +28,13 @@ var usePrefix = require('../../internal/usePrefix.js');
28
28
  var usePreviousValue = require('../../internal/usePreviousValue.js');
29
29
  var keys = require('../../internal/keyboard/keys.js');
30
30
  var match = require('../../internal/keyboard/match.js');
31
- var index$3 = require('../IconButton/index.js');
31
+ var index$2 = require('../IconButton/index.js');
32
32
  var noopFn = require('../../internal/noopFn.js');
33
33
  require('../Text/index.js');
34
34
  var index = require('../FeatureFlags/index.js');
35
35
  var events = require('../../tools/events.js');
36
36
  var deprecate = require('../../prop-types/deprecate.js');
37
- var index$2 = require('../Dialog/index.js');
37
+ var Dialog = require('../Dialog/Dialog.js');
38
38
  var index$1 = require('../AILabel/index.js');
39
39
  var utils = require('../../internal/utils.js');
40
40
  var warning = require('../../internal/warning.js');
@@ -42,7 +42,7 @@ var debounce = require('../../node_modules/es-toolkit/dist/compat/function/debou
42
42
  var Text = require('../Text/Text.js');
43
43
 
44
44
  const ModalSizes = ['xs', 'sm', 'md', 'lg'];
45
- const invalidOutsideClickMessage = '`Modal`: `preventCloseOnClickOutside` should not be `false` when `passiveModal` is `false`. Non-passive `Modal`s should not be dismissible by clicking outside.';
45
+ 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';
46
46
  const Modal = /*#__PURE__*/React.forwardRef(function Modal({
47
47
  'aria-label': ariaLabelProp,
48
48
  children,
@@ -68,7 +68,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
68
68
  size,
69
69
  hasScrollingContent = false,
70
70
  closeButtonLabel = 'Close',
71
- preventCloseOnClickOutside = !passiveModal,
71
+ preventCloseOnClickOutside,
72
72
  isFullWidth,
73
73
  launcherButtonRef,
74
74
  loadingStatus = 'inactive',
@@ -100,9 +100,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
100
100
  const focusTrapWithoutSentinels = index.useFeatureFlag('enable-experimental-focus-wrap-without-sentinels');
101
101
  const enableDialogElement = index.useFeatureFlag('enable-dialog-element');
102
102
  process.env.NODE_ENV !== "production" ? warning.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;
103
- if (!passiveModal && preventCloseOnClickOutside === false) {
104
- console.error(invalidOutsideClickMessage);
105
- }
103
+ process.env.NODE_ENV !== "production" ? warning.warning(!(!passiveModal && preventCloseOnClickOutside === false), invalidOutsideClickMessage) : void 0;
106
104
  function isCloseButton(element) {
107
105
  return !onSecondarySubmit && element === secondaryButton.current || element.classList.contains(modalCloseButtonClass);
108
106
  }
@@ -129,7 +127,15 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
129
127
  target
130
128
  } = evt;
131
129
  evt.stopPropagation();
132
- if (!preventCloseOnClickOutside && target instanceof Node && !wrapFocus.elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && innerModal.current && !innerModal.current.contains(target)) {
130
+ const shouldCloseOnOutsideClick =
131
+ // Passive modals can close on clicks outside the modal when
132
+ // preventCloseOnClickOutside is undefined or explicitly set to false.
133
+ passiveModal && !preventCloseOnClickOutside ||
134
+ // Non-passive modals have to explicitly opt-in for close on outside
135
+ // behavior by explicitly setting preventCloseOnClickOutside to false,
136
+ // rather than just leaving it undefined.
137
+ !passiveModal && preventCloseOnClickOutside === false;
138
+ if (shouldCloseOnOutsideClick && target instanceof Node && !wrapFocus.elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) && innerModal.current && !innerModal.current.contains(target)) {
133
139
  onRequestClose(evt);
134
140
  }
135
141
  }
@@ -301,7 +307,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
301
307
  }) : null;
302
308
  const modalButton = /*#__PURE__*/React.createElement("div", {
303
309
  className: `${prefix}--modal-close-button`
304
- }, /*#__PURE__*/React.createElement(index$3.IconButton, {
310
+ }, /*#__PURE__*/React.createElement(index$2.IconButton, {
305
311
  className: modalCloseButtonClass,
306
312
  label: closeButtonLabel,
307
313
  onClick: onRequestClose,
@@ -318,7 +324,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
318
324
  // alertdialog is the only permitted aria role for a native dialog element
319
325
  // https://www.w3.org/TR/html-aria/#docconformance:~:text=Role%3A-,alertdialog,-.%20(dialog%20is
320
326
  const isAlertDialog = alert && !passiveModal;
321
- const modalBody = enableDialogElement ? /*#__PURE__*/React.createElement(index$2.unstable__Dialog, {
327
+ const modalBody = enableDialogElement ? /*#__PURE__*/React.createElement(Dialog.Dialog, {
322
328
  open: open,
323
329
  focusAfterCloseRef: launcherButtonRef,
324
330
  modal: true,
@@ -341,7 +347,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
341
347
  className: `${prefix}--modal--inner__decorator`
342
348
  }, normalizedDecorator) : '', /*#__PURE__*/React.createElement("div", {
343
349
  className: `${prefix}--modal-close-button`
344
- }, /*#__PURE__*/React.createElement(index$3.IconButton, {
350
+ }, /*#__PURE__*/React.createElement(index$2.IconButton, {
345
351
  className: modalCloseButtonClass,
346
352
  label: closeButtonLabel,
347
353
  onClick: onRequestClose,
@@ -353,7 +359,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
353
359
  "aria-hidden": "true",
354
360
  tabIndex: "-1",
355
361
  className: `${modalCloseButtonClass}__icon`
356
- })))), /*#__PURE__*/React.createElement(index$4.Layer, _rollupPluginBabelHelpers.extends({
362
+ })))), /*#__PURE__*/React.createElement(index$3.Layer, _rollupPluginBabelHelpers.extends({
357
363
  ref: contentRef,
358
364
  id: modalBodyId,
359
365
  className: contentClasses
@@ -409,7 +415,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
409
415
  className: `${prefix}--modal-header__heading`
410
416
  }, modalHeading), slug ? normalizedDecorator : decorator ? /*#__PURE__*/React.createElement("div", {
411
417
  className: `${prefix}--modal--inner__decorator`
412
- }, normalizedDecorator) : '', !passiveModal && modalButton), /*#__PURE__*/React.createElement(index$4.Layer, _rollupPluginBabelHelpers.extends({
418
+ }, normalizedDecorator) : '', !passiveModal && modalButton), /*#__PURE__*/React.createElement(index$3.Layer, _rollupPluginBabelHelpers.extends({
413
419
  ref: contentRef,
414
420
  id: modalBodyId,
415
421
  className: contentClasses
@@ -446,7 +452,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
446
452
  role: "link",
447
453
  className: `${prefix}--visually-hidden`
448
454
  }, "Focus sentinel"));
449
- return /*#__PURE__*/React.createElement(index$4.Layer, _rollupPluginBabelHelpers.extends({}, rest, {
455
+ return /*#__PURE__*/React.createElement(index$3.Layer, _rollupPluginBabelHelpers.extends({}, rest, {
450
456
  level: 0,
451
457
  onKeyDown: handleKeyDown,
452
458
  onClick: events.composeEventHandlers([rest?.onClick, handleOnClick]),
@@ -175,11 +175,16 @@ const NumberInput = /*#__PURE__*/React.forwardRef(function NumberInput(props, fo
175
175
  [`${prefix}--number__invalid`]: normalizedProps.invalid || normalizedProps.warn,
176
176
  [`${prefix}--number__invalid--warning`]: normalizedProps.warn
177
177
  });
178
- if (controlledValue !== prevControlledValue && !(isNaN(Number(controlledValue)) === isNaN(Number(prevControlledValue)))) {
179
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
180
- setValue(controlledValue);
181
- setPrevControlledValue(controlledValue);
182
- }
178
+ React.useEffect(() => {
179
+ if (type === 'number' && controlledValue !== undefined) {
180
+ if (allowEmpty && controlledValue === '') {
181
+ setValue('');
182
+ } else {
183
+ setValue(controlledValue);
184
+ }
185
+ setPrevControlledValue(controlledValue);
186
+ }
187
+ }, [controlledValue, type, allowEmpty]);
183
188
  let ariaDescribedBy = undefined;
184
189
  if (normalizedProps.invalid) {
185
190
  ariaDescribedBy = normalizedProps.invalidId;
@@ -63,8 +63,12 @@ forwardRef) {
63
63
  // The `Popover` should close whenever it and its children loses focus
64
64
  useEvent.useEvent(popover, 'focusout', event => {
65
65
  const relatedTarget = event.relatedTarget;
66
-
67
- // No relatedTarget, focus moved to nowhere, so close the popover
66
+ if (isTabTip) {
67
+ if (relatedTarget && !popover.current?.contains(relatedTarget)) {
68
+ onRequestClose?.();
69
+ }
70
+ return;
71
+ }
68
72
  if (!relatedTarget) {
69
73
  onRequestClose?.();
70
74
  return;
@@ -191,8 +191,10 @@ function StructuredListRow(props) {
191
191
  setHasFocusWithin(true);
192
192
  }
193
193
  },
194
- onFocus: () => {
195
- setHasFocusWithin(true);
194
+ onFocus: event => {
195
+ if (selection || event.currentTarget === event.target) {
196
+ setHasFocusWithin(true);
197
+ }
196
198
  },
197
199
  onBlur: () => {
198
200
  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
  */
@@ -41,6 +41,7 @@ const DismissibleTag = /*#__PURE__*/React.forwardRef(({
41
41
  text,
42
42
  tagTitle,
43
43
  type,
44
+ dismissTooltipAlignment = 'bottom',
44
45
  dismissTooltipLabel = 'Dismiss tag',
45
46
  ...other
46
47
  }, forwardRef) => {
@@ -92,7 +93,7 @@ const DismissibleTag = /*#__PURE__*/React.forwardRef(({
92
93
  className: `${prefix}--tag__decorator`
93
94
  }, normalizedDecorator) : '', /*#__PURE__*/React.createElement(Tooltip.Tooltip, {
94
95
  label: dismissActionLabel,
95
- align: "bottom",
96
+ align: dismissTooltipAlignment,
96
97
  className: tooltipClasses,
97
98
  leaveDelayMs: 0,
98
99
  closeOnActivation: true
@@ -117,6 +118,10 @@ DismissibleTag.propTypes = {
117
118
  * Specify if the `DismissibleTag` is disabled
118
119
  */
119
120
  disabled: PropTypes.bool,
121
+ /**
122
+ * Specify the tooltip alignment for the dismiss button
123
+ */
124
+ dismissTooltipAlignment: PropTypes.oneOf(['top', 'bottom', 'left', 'right', 'top-start', 'top-end', 'bottom-start', 'bottom-end', 'left-end', 'left-start', 'right-end', 'right-start']),
120
125
  /**
121
126
  * Provide a custom tooltip label for the dismiss button
122
127
  */
@@ -102,6 +102,7 @@ function Toggletip({
102
102
  };
103
103
  const onKeyDown = event => {
104
104
  if (open && match.match(event, keys.Escape)) {
105
+ event.stopPropagation();
105
106
  actions.close();
106
107
 
107
108
  // If the menu is closed while focus is still inside the menu, it should return to the trigger button (#12922)
@@ -128,13 +129,24 @@ function Toggletip({
128
129
  actions.close();
129
130
  }
130
131
  });
131
- useEvent.useWindowEvent('click', ({
132
- target
133
- }) => {
134
- if (open && target instanceof Node && !ref.current?.contains(target)) {
135
- actions.close();
136
- }
137
- });
132
+ React.useEffect(() => {
133
+ if (!ref.current) return;
134
+ const targetDocument = ref.current.ownerDocument || document;
135
+ const eventType = 'PointerEvent' in window ? 'pointerdown' : 'mousedown';
136
+ const handleOutsideClick = event => {
137
+ const node = event.target;
138
+ if (open && node && !ref.current.contains(node)) {
139
+ setOpen(false);
140
+ }
141
+ };
142
+ const options = {
143
+ capture: true
144
+ };
145
+ targetDocument.addEventListener(eventType, handleOutsideClick, options);
146
+ return () => {
147
+ targetDocument.removeEventListener(eventType, handleOutsideClick, options);
148
+ };
149
+ }, [open]);
138
150
  return /*#__PURE__*/React.createElement(ToggletipContext.Provider, {
139
151
  value: value
140
152
  }, /*#__PURE__*/React.createElement(index.Popover, _rollupPluginBabelHelpers.extends({