@carbon/react 1.89.0-rc.1 → 1.89.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 (35) hide show
  1. package/.playwright/INTERNAL_AVT_REPORT_DO_NOT_USE.json +1099 -837
  2. package/es/components/ComposedModal/ComposedModal.js +2 -2
  3. package/es/components/DataTable/DataTable.js +10 -3
  4. package/es/components/Dialog/Dialog.d.ts +245 -0
  5. package/es/components/Dialog/Dialog.js +593 -0
  6. package/es/components/Dialog/index.d.ts +3 -251
  7. package/es/components/Dialog/index.js +1 -609
  8. package/es/components/FeatureFlags/index.d.ts +3 -1
  9. package/es/components/FeatureFlags/index.js +5 -2
  10. package/es/components/FileUploader/FileUploader.d.ts +28 -6
  11. package/es/components/FileUploader/FileUploader.js +152 -38
  12. package/es/components/Menu/MenuItem.js +2 -1
  13. package/es/components/Modal/Modal.js +2 -2
  14. package/es/components/StructuredList/StructuredList.js +4 -2
  15. package/es/components/Toggletip/index.js +1 -0
  16. package/es/index.d.ts +2 -1
  17. package/es/index.js +2 -0
  18. package/lib/components/ComposedModal/ComposedModal.js +2 -2
  19. package/lib/components/DataTable/DataTable.js +10 -3
  20. package/lib/components/Dialog/Dialog.d.ts +245 -0
  21. package/lib/components/Dialog/Dialog.js +602 -0
  22. package/lib/components/Dialog/index.d.ts +3 -251
  23. package/lib/components/Dialog/index.js +9 -614
  24. package/lib/components/FeatureFlags/index.d.ts +3 -1
  25. package/lib/components/FeatureFlags/index.js +5 -2
  26. package/lib/components/FileUploader/FileUploader.d.ts +28 -6
  27. package/lib/components/FileUploader/FileUploader.js +151 -37
  28. package/lib/components/Menu/MenuItem.js +2 -1
  29. package/lib/components/Modal/Modal.js +9 -9
  30. package/lib/components/StructuredList/StructuredList.js +4 -2
  31. package/lib/components/Toggletip/index.js +1 -0
  32. package/lib/index.d.ts +2 -1
  33. package/lib/index.js +60 -58
  34. package/package.json +13 -13
  35. package/telemetry.yml +2 -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');
@@ -301,7 +301,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
301
301
  }) : null;
302
302
  const modalButton = /*#__PURE__*/React.createElement("div", {
303
303
  className: `${prefix}--modal-close-button`
304
- }, /*#__PURE__*/React.createElement(index$3.IconButton, {
304
+ }, /*#__PURE__*/React.createElement(index$2.IconButton, {
305
305
  className: modalCloseButtonClass,
306
306
  label: closeButtonLabel,
307
307
  onClick: onRequestClose,
@@ -318,7 +318,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
318
318
  // alertdialog is the only permitted aria role for a native dialog element
319
319
  // https://www.w3.org/TR/html-aria/#docconformance:~:text=Role%3A-,alertdialog,-.%20(dialog%20is
320
320
  const isAlertDialog = alert && !passiveModal;
321
- const modalBody = enableDialogElement ? /*#__PURE__*/React.createElement(index$2.unstable__Dialog, {
321
+ const modalBody = enableDialogElement ? /*#__PURE__*/React.createElement(Dialog.Dialog, {
322
322
  open: open,
323
323
  focusAfterCloseRef: launcherButtonRef,
324
324
  modal: true,
@@ -341,7 +341,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
341
341
  className: `${prefix}--modal--inner__decorator`
342
342
  }, normalizedDecorator) : '', /*#__PURE__*/React.createElement("div", {
343
343
  className: `${prefix}--modal-close-button`
344
- }, /*#__PURE__*/React.createElement(index$3.IconButton, {
344
+ }, /*#__PURE__*/React.createElement(index$2.IconButton, {
345
345
  className: modalCloseButtonClass,
346
346
  label: closeButtonLabel,
347
347
  onClick: onRequestClose,
@@ -353,7 +353,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
353
353
  "aria-hidden": "true",
354
354
  tabIndex: "-1",
355
355
  className: `${modalCloseButtonClass}__icon`
356
- })))), /*#__PURE__*/React.createElement(index$4.Layer, _rollupPluginBabelHelpers.extends({
356
+ })))), /*#__PURE__*/React.createElement(index$3.Layer, _rollupPluginBabelHelpers.extends({
357
357
  ref: contentRef,
358
358
  id: modalBodyId,
359
359
  className: contentClasses
@@ -409,7 +409,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
409
409
  className: `${prefix}--modal-header__heading`
410
410
  }, modalHeading), slug ? normalizedDecorator : decorator ? /*#__PURE__*/React.createElement("div", {
411
411
  className: `${prefix}--modal--inner__decorator`
412
- }, normalizedDecorator) : '', !passiveModal && modalButton), /*#__PURE__*/React.createElement(index$4.Layer, _rollupPluginBabelHelpers.extends({
412
+ }, normalizedDecorator) : '', !passiveModal && modalButton), /*#__PURE__*/React.createElement(index$3.Layer, _rollupPluginBabelHelpers.extends({
413
413
  ref: contentRef,
414
414
  id: modalBodyId,
415
415
  className: contentClasses
@@ -446,7 +446,7 @@ const Modal = /*#__PURE__*/React.forwardRef(function Modal({
446
446
  role: "link",
447
447
  className: `${prefix}--visually-hidden`
448
448
  }, "Focus sentinel"));
449
- return /*#__PURE__*/React.createElement(index$4.Layer, _rollupPluginBabelHelpers.extends({}, rest, {
449
+ return /*#__PURE__*/React.createElement(index$3.Layer, _rollupPluginBabelHelpers.extends({}, rest, {
450
450
  level: 0,
451
451
  onKeyDown: handleKeyDown,
452
452
  onClick: events.composeEventHandlers([rest?.onClick, handleOnClick]),
@@ -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);
@@ -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)
package/lib/index.d.ts CHANGED
@@ -58,6 +58,7 @@ export * from './components/OrderedList';
58
58
  export * from './components/OverflowMenu';
59
59
  export * from './components/OverflowMenuItem';
60
60
  export * as unstable__PageHeader from './components/PageHeader';
61
+ export * as preview__Dialog from './components/Dialog';
61
62
  export * from './components/Pagination';
62
63
  export * from './components/Pagination/Pagination.Skeleton';
63
64
  export * from './components/PaginationNav';
@@ -185,7 +186,6 @@ export type { TableToolbarSearchProps } from './components/DataTable/TableToolba
185
186
  export type { DataTableSkeletonProps } from './components/DataTableSkeleton/DataTableSkeleton';
186
187
  export type { DatePickerProps } from './components/DatePicker/DatePicker';
187
188
  export type { DatePickerInputProps } from './components/DatePickerInput/DatePickerInput';
188
- export type { DialogProps } from './components/Dialog/index';
189
189
  export type { DropdownProps } from './components/Dropdown/Dropdown';
190
190
  export type { ErrorBoundaryProps } from './components/ErrorBoundary/ErrorBoundary';
191
191
  export type { FeatureFlagsProps } from './components/FeatureFlags/index';
@@ -267,6 +267,7 @@ export type { OrderedListProps } from './components/OrderedList/OrderedList';
267
267
  export type { OverflowMenuProps } from './components/OverflowMenu/OverflowMenu';
268
268
  export type { OverflowMenuItemProps } from './components/OverflowMenuItem/OverflowMenuItem';
269
269
  export type { PageHeaderProps, PageHeaderBreadcrumbBarProps, PageHeaderContentProps, PageHeaderHeroImageProps, PageHeaderTabBarProps, } from './components/PageHeader';
270
+ export type { DialogProps, DialogHeaderProps, DialogControlsProps, DialogCloseButtonProps, DialogTitleProps, DialogSubtitleProps, DialogBodyProps, DialogFooterProps, } from './components/Dialog';
270
271
  export type { PaginationProps } from './components/Pagination/Pagination';
271
272
  export type { PaginationSkeletonProps } from './components/Pagination/Pagination.Skeleton';
272
273
  export type { DirectionButtonProps } from './components/PaginationNav/PaginationNav';