@aws-amplify/ui-react-storage 3.15.0 → 3.16.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 (80) hide show
  1. package/dist/browser.js +1 -1
  2. package/dist/{createStorageBrowser-CG-6mXiT.js → createStorageBrowser-B-J76Lyp.js} +624 -105
  3. package/dist/esm/components/StorageBrowser/ErrorBoundary/ErrorBoundary.mjs +0 -4
  4. package/dist/esm/components/StorageBrowser/actions/configs/defaults.mjs +13 -3
  5. package/dist/esm/components/StorageBrowser/actions/handlers/delete.mjs +39 -8
  6. package/dist/esm/components/StorageBrowser/components/ComponentsProvider.mjs +0 -4
  7. package/dist/esm/components/StorageBrowser/components/base/preview/DownloadButton.mjs +0 -4
  8. package/dist/esm/components/StorageBrowser/components/composables/ActionConfirmationModal.mjs +34 -0
  9. package/dist/esm/components/StorageBrowser/components/composables/defaults.mjs +2 -0
  10. package/dist/esm/components/StorageBrowser/components/elements/definitions.mjs +2 -2
  11. package/dist/esm/components/StorageBrowser/controls/ActionConfirmationModalControl.mjs +12 -0
  12. package/dist/esm/components/StorageBrowser/controls/DataTableControl.mjs +0 -4
  13. package/dist/esm/components/StorageBrowser/controls/hooks/useActionConfirmationModal.mjs +17 -0
  14. package/dist/esm/components/StorageBrowser/createStorageBrowser/StorageBrowserDefault.mjs +8 -4
  15. package/dist/esm/components/StorageBrowser/createStorageBrowser/createStorageBrowser.mjs +9 -5
  16. package/dist/esm/components/StorageBrowser/displayText/libraries/en/deleteView.mjs +117 -5
  17. package/dist/esm/components/StorageBrowser/displayText/libraries/en/locationDetailView.mjs +1 -0
  18. package/dist/esm/components/StorageBrowser/displayText/libraries/en/shared.mjs +1 -0
  19. package/dist/esm/components/StorageBrowser/locationItems/context.mjs +17 -14
  20. package/dist/esm/components/StorageBrowser/locationItems/utils.mjs +38 -0
  21. package/dist/esm/components/StorageBrowser/store/validateStoreProps.mjs +1 -1
  22. package/dist/esm/components/StorageBrowser/tasks/useProcessTasks.mjs +7 -4
  23. package/dist/esm/components/StorageBrowser/useAction/utils.mjs +0 -5
  24. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyView.mjs +0 -4
  25. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/CopyViewProvider.mjs +4 -4
  26. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/FoldersMessageControl.mjs +0 -4
  27. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/useCopyView.mjs +0 -4
  28. package/dist/esm/components/StorageBrowser/views/LocationActionView/CopyView/useFolders.mjs +0 -4
  29. package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/CreateFolderView.mjs +0 -4
  30. package/dist/esm/components/StorageBrowser/views/LocationActionView/CreateFolderView/useCreateFolderView.mjs +0 -4
  31. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/DeleteView.mjs +5 -6
  32. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/DeleteViewProvider.mjs +7 -6
  33. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/useDeleteView.mjs +69 -7
  34. package/dist/esm/components/StorageBrowser/views/LocationActionView/DeleteView/utils.mjs +87 -0
  35. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadView.mjs +0 -4
  36. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/DownloadViewProvider.mjs +9 -0
  37. package/dist/esm/components/StorageBrowser/views/LocationActionView/DownloadView/useDownloadView.mjs +0 -4
  38. package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.mjs +1 -5
  39. package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/UploadViewProvider.mjs +3 -0
  40. package/dist/esm/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.mjs +0 -4
  41. package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailView.mjs +0 -4
  42. package/dist/esm/components/StorageBrowser/views/LocationDetailView/LocationDetailViewProvider.mjs +2 -1
  43. package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getFolderRowContent.mjs +38 -27
  44. package/dist/esm/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.mjs +8 -1
  45. package/dist/esm/components/StorageBrowser/views/LocationDetailView/useLocationDetailView.mjs +10 -9
  46. package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsView.mjs +0 -4
  47. package/dist/esm/components/StorageBrowser/views/LocationsView/LocationsViewProvider.mjs +0 -4
  48. package/dist/esm/components/StorageBrowser/views/context/actionViews.mjs +7 -4
  49. package/dist/esm/components/StorageBrowser/views/context/primaryViews.mjs +8 -4
  50. package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/constants.mjs +10 -1
  51. package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/deleteResolvers.mjs +123 -13
  52. package/dist/esm/components/StorageBrowser/views/utils/tableResolvers/utils.mjs +3 -3
  53. package/dist/esm/version.mjs +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/styles.css +83 -1
  56. package/dist/types/components/StorageBrowser/actions/handlers/delete.d.ts +5 -3
  57. package/dist/types/components/StorageBrowser/actions/handlers/types.d.ts +15 -1
  58. package/dist/types/components/StorageBrowser/components/composables/ActionConfirmationModal.d.ts +12 -0
  59. package/dist/types/components/StorageBrowser/components/composables/types.d.ts +2 -0
  60. package/dist/types/components/StorageBrowser/controls/ActionConfirmationModalControl.d.ts +2 -0
  61. package/dist/types/components/StorageBrowser/controls/hooks/useActionConfirmationModal.d.ts +2 -0
  62. package/dist/types/components/StorageBrowser/controls/index.d.ts +1 -0
  63. package/dist/types/components/StorageBrowser/controls/types.d.ts +4 -0
  64. package/dist/types/components/StorageBrowser/displayText/types.d.ts +7 -0
  65. package/dist/types/components/StorageBrowser/locationItems/context.d.ts +12 -3
  66. package/dist/types/components/StorageBrowser/locationItems/index.d.ts +1 -0
  67. package/dist/types/components/StorageBrowser/locationItems/utils.d.ts +27 -0
  68. package/dist/types/components/StorageBrowser/tasks/types.d.ts +7 -2
  69. package/dist/types/components/StorageBrowser/views/LocationActionView/DeleteView/types.d.ts +5 -0
  70. package/dist/types/components/StorageBrowser/views/LocationActionView/DeleteView/utils.d.ts +26 -0
  71. package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getFolderRowContent.d.ts +4 -1
  72. package/dist/types/components/StorageBrowser/views/LocationDetailView/getLocationDetailViewTableData/getLocationDetailViewTableData.d.ts +3 -2
  73. package/dist/types/components/StorageBrowser/views/LocationDetailView/types.d.ts +8 -1
  74. package/dist/types/components/StorageBrowser/views/utils/index.d.ts +1 -1
  75. package/dist/types/components/StorageBrowser/views/utils/tableResolvers/constants.d.ts +1 -0
  76. package/dist/types/components/StorageBrowser/views/utils/tableResolvers/deleteResolvers.d.ts +5 -2
  77. package/dist/types/components/StorageBrowser/views/utils/tableResolvers/index.d.ts +1 -1
  78. package/dist/types/components/StorageBrowser/views/utils/tableResolvers/types.d.ts +4 -0
  79. package/dist/types/version.d.ts +1 -1
  80. package/package.json +7 -7
@@ -36,7 +36,7 @@ function _interopNamespace(e) {
36
36
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
37
37
  var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
38
38
 
39
- const VERSION = '3.15.0';
39
+ const VERSION = '3.16.0';
40
40
 
41
41
  const DEFAULT_CHECKSUM_ALGORITHM = 'crc-32';
42
42
  // 5MiB for multipart upload
@@ -422,27 +422,58 @@ const createFolderHandler = (input) => {
422
422
  };
423
423
  };
424
424
 
425
- const deleteHandler = ({ config, data, }) => {
425
+ const deleteHandler = ({ config, data, options, }) => {
426
426
  const { key } = data;
427
427
  const { accountId, credentials, customEndpoint } = config;
428
- const result = internals.remove({
428
+ const { onProgress } = options ?? {};
429
+ let cumulativeSuccessCount = 0;
430
+ let cumulativeFailureCount = 0;
431
+ let operationCancel = () => {
432
+ // noop
433
+ };
434
+ const cancel = () => {
435
+ operationCancel?.();
436
+ };
437
+ const operation = internals.remove({
429
438
  path: key,
430
439
  options: {
431
440
  bucket: constructBucket$1(config),
432
441
  locationCredentialsProvider: credentials,
433
442
  expectedBucketOwner: accountId,
434
443
  customEndpoint,
444
+ onProgress: (progress) => {
445
+ if (!progress) {
446
+ return;
447
+ }
448
+ const batchSuccessCount = progress?.deleted?.length ?? 0;
449
+ const batchFailureCount = progress?.failed?.length ?? 0;
450
+ cumulativeSuccessCount += batchSuccessCount;
451
+ cumulativeFailureCount += batchFailureCount;
452
+ onProgress?.(data, {
453
+ successCount: cumulativeSuccessCount,
454
+ failureCount: cumulativeFailureCount,
455
+ });
456
+ },
435
457
  },
458
+ });
459
+ operationCancel = operation?.cancel ? operation?.cancel : () => { };
460
+ const operationPromise = operation?.result ?? operation;
461
+ const result = operationPromise
462
+ ?.then?.(({ path }) => {
463
+ return {
464
+ status: 'COMPLETE',
465
+ value: {
466
+ key: path,
467
+ successCount: cumulativeSuccessCount,
468
+ failureCount: cumulativeFailureCount,
469
+ },
470
+ };
436
471
  })
437
- .then(({ path }) => ({
438
- status: 'COMPLETE',
439
- value: { key: path },
440
- }))
441
- .catch((error) => {
472
+ ?.catch?.((error) => {
442
473
  const { message } = error;
443
474
  return { error, message, status: 'FAILED' };
444
475
  });
445
- return { result };
476
+ return { result, cancel };
446
477
  };
447
478
 
448
479
  const zipProgressManager = ({ dataMap, onZipProgress, }) => {
@@ -792,10 +823,50 @@ const defaultValue$7 = {
792
823
  };
793
824
  const { useActionConfigs, ActionConfigsProvider } = uiReactCore.createContextUtilities({ contextName: 'ActionConfigs', defaultValue: defaultValue$7 });
794
825
 
826
+ /**
827
+ * Selection utility functions for LocationItems
828
+ */
829
+ /**
830
+ * Get selected files from dataItems
831
+ */
832
+ const getSelectedFiles = (dataItems) => {
833
+ return dataItems?.filter((item) => item.type === 'FILE') ?? [];
834
+ };
835
+ /**
836
+ * Get selected folders from dataItems
837
+ */
838
+ const getSelectedFolders = (dataItems) => {
839
+ return dataItems?.filter((item) => item.type === 'FOLDER') ?? [];
840
+ };
841
+ /**
842
+ * Check if selection contains folders
843
+ */
844
+ const hasSelectedFolders = (dataItems) => {
845
+ return dataItems?.some((item) => item.type === 'FOLDER') ?? false;
846
+ };
847
+ /**
848
+ * Get selection summary
849
+ */
850
+ const getSelectionSummary = (dataItems) => {
851
+ const files = getSelectedFiles(dataItems);
852
+ const folders = getSelectedFolders(dataItems);
853
+ return {
854
+ total: dataItems?.length ?? 0,
855
+ files: files.length,
856
+ folders: folders.length,
857
+ hasFiles: files.length > 0,
858
+ hasFolders: folders.length > 0,
859
+ isMixed: files.length > 0 && folders.length > 0,
860
+ };
861
+ };
862
+
795
863
  const copyActionConfig = {
796
864
  viewName: 'CopyView',
797
865
  actionListItem: {
798
- disable: (selected) => !selected || selected.length === 0,
866
+ disable: (selected) => {
867
+ const hasNoSelection = !selected || selected.length === 0;
868
+ return hasNoSelection || hasSelectedFolders(selected);
869
+ },
799
870
  hide: (permissions) => !permissions.includes('write'),
800
871
  icon: 'copy-file',
801
872
  label: 'Copy',
@@ -805,7 +876,10 @@ const copyActionConfig = {
805
876
  const deleteActionConfig = {
806
877
  viewName: 'DeleteView',
807
878
  actionListItem: {
808
- disable: (selected) => !selected || selected.length === 0,
879
+ disable: (selected) => {
880
+ const hasNoSelection = !selected || selected.length === 0;
881
+ return hasNoSelection;
882
+ },
809
883
  hide: (permissions) => !permissions.includes('delete'),
810
884
  icon: 'delete-file',
811
885
  label: 'Delete',
@@ -833,7 +907,10 @@ const uploadActionConfig = {
833
907
  const downloadActionConfig = {
834
908
  viewName: 'DownloadView',
835
909
  actionListItem: {
836
- disable: (selected) => !selected || selected.length === 0,
910
+ disable: (selected) => {
911
+ const hasNoSelection = !selected || selected.length === 0;
912
+ return hasNoSelection || hasSelectedFolders(selected);
913
+ },
837
914
  hide: (permissions) => !permissions.includes('get'),
838
915
  icon: 'download',
839
916
  label: 'Download',
@@ -1038,7 +1115,7 @@ const OrderedListElement = elements.defineBaseElement({
1038
1115
  type: 'ol',
1039
1116
  displayName: 'OrderedList',
1040
1117
  });
1041
- elements.defineBaseElement({
1118
+ const UnorderedListElement = elements.defineBaseElement({
1042
1119
  type: 'ul',
1043
1120
  displayName: 'UnorderedList',
1044
1121
  });
@@ -1178,6 +1255,34 @@ const { useComposables, ComposablesProvider } = uiReactCore.createContextUtiliti
1178
1255
 
1179
1256
  const ActionCancel = ({ onCancel, isDisabled, label, }) => (React__namespace["default"].createElement(ButtonElement, { variant: "cancel", className: `${STORAGE_BROWSER_BLOCK}__cancel`, onClick: onCancel, disabled: isDisabled }, label));
1180
1257
 
1258
+ const ActionConfirmationModal = ({ isOpen = false, title, message, content, onConfirm, onCancel, confirmLabel, cancelLabel, }) => {
1259
+ const modalRef = React__namespace["default"].useRef(null);
1260
+ React__namespace["default"].useEffect(() => {
1261
+ if (isOpen && modalRef.current) {
1262
+ modalRef.current.focus();
1263
+ }
1264
+ }, [isOpen]);
1265
+ const handleKeyDown = (event) => {
1266
+ if (event.key === 'Escape') {
1267
+ onCancel?.();
1268
+ }
1269
+ };
1270
+ if (!isOpen)
1271
+ return null;
1272
+ return (React__namespace["default"].createElement("div", { ref: modalRef, className: "amplify-modal__overlay", role: "dialog", "aria-modal": "true", tabIndex: -1, onKeyDown: handleKeyDown, onClick: (e) => {
1273
+ if (e.target === e.currentTarget) {
1274
+ onCancel?.();
1275
+ }
1276
+ } },
1277
+ React__namespace["default"].createElement(ViewElement, { className: "amplify-modal__content", role: "document" },
1278
+ React__namespace["default"].createElement(HeadingElement, { className: "amplify-modal__title" }, title),
1279
+ message && (React__namespace["default"].createElement(TextElement, { className: "amplify-modal__body" }, message)),
1280
+ content && (React__namespace["default"].createElement(ViewElement, { className: "amplify-modal__body" }, content)),
1281
+ React__namespace["default"].createElement(ViewElement, { className: "amplify-modal__footer" },
1282
+ React__namespace["default"].createElement(ButtonElement, { className: "amplify-modal__cancel", onClick: onCancel, variant: "cancel" }, cancelLabel),
1283
+ React__namespace["default"].createElement(ButtonElement, { className: "amplify-modal__confirm", onClick: onConfirm, variant: "primary" }, confirmLabel)))));
1284
+ };
1285
+
1181
1286
  const ActionDestination$1 = ({ isNavigable, items, label, }) => {
1182
1287
  if (!items.length) {
1183
1288
  return null;
@@ -1804,7 +1909,7 @@ const DEPRECATED_PROP_KEYS = ['actionType', 'location', 'path'];
1804
1909
  const template = (key, index, values) => `\`${key}\`${index < values.length - 1 ? ', ' : ''}`;
1805
1910
  const getMissingLocationKeys = (location) => location === null
1806
1911
  ? []
1807
- : REQUIRED_LOCATION_KEYS.filter((key) => !location[key]);
1912
+ : REQUIRED_LOCATION_KEYS.filter((key) => location[key] === undefined || location[key] === null);
1808
1913
  let didWarnConflictingBehavior = false;
1809
1914
  let didWarnDeprecatedAndConflictingProps = false;
1810
1915
  let didWarnDeprecatedProps = false;
@@ -2065,16 +2170,19 @@ function useProcessTasks(handler, options) {
2065
2170
  const getTask = () => tasksRef.current.get(data.id);
2066
2171
  const { options } = _input;
2067
2172
  const { onProgress: _onProgress } = options ?? {};
2068
- const onProgress = ({ id }, progress, status = 'PENDING') => {
2173
+ const onProgress = ({ id }, progressDetails, status = 'PENDING') => {
2174
+ const isNumber = typeof progressDetails === 'number';
2069
2175
  const task = updateTask(id, {
2070
- progress,
2176
+ progress: isNumber ? progressDetails : progressDetails.progress,
2177
+ failureCount: isNumber ? undefined : progressDetails.failureCount,
2178
+ successCount: isNumber ? undefined : progressDetails.successCount,
2071
2179
  status,
2072
2180
  });
2073
2181
  if (task && ui.isFunction(onTaskProgress)) {
2074
- onTaskProgress(task, progress);
2182
+ onTaskProgress(task, progressDetails);
2075
2183
  }
2076
2184
  if (task && ui.isFunction(_onProgress)) {
2077
- _onProgress(data, progress, status);
2185
+ _onProgress(data, progressDetails, status);
2078
2186
  }
2079
2187
  };
2080
2188
  const input = { ..._input, data, all, options: { ...options, onProgress } };
@@ -2284,6 +2392,7 @@ const DEFAULT_ACTION_VIEW_DISPLAY_TEXT = {
2284
2392
  tableColumnNameHeader: 'Name',
2285
2393
  tableColumnTypeHeader: 'Type',
2286
2394
  tableColumnSizeHeader: 'Size',
2395
+ tableColumnProgressHeader: 'Progress',
2287
2396
  };
2288
2397
  const DEFAULT_LIST_VIEW_DISPLAY_TEXT = {
2289
2398
  loadingIndicatorLabel: 'Loading',
@@ -2376,21 +2485,133 @@ const DEFAULT_COPY_VIEW_DISPLAY_TEXT = {
2376
2485
  searchClearLabel: 'Clear search',
2377
2486
  };
2378
2487
 
2488
+ const pluralize = (count, word) => count === 1 ? word : `${word}s`;
2489
+ const formatCount = (count, word) => `${count === 1 ? '' : 'All '}${count} ${pluralize(count, word)}`;
2379
2490
  const DEFAULT_DELETE_VIEW_DISPLAY_TEXT = {
2380
2491
  ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
2381
2492
  title: 'Delete',
2382
2493
  actionStartLabel: 'Delete',
2494
+ confirmationModalTitle: 'Confirm Deletion',
2495
+ confirmationModalConfirmLabel: 'Delete',
2496
+ confirmationModalCancelLabel: 'Cancel',
2497
+ confirmationModalMessage: 'The items that will be deleted contain {count} folder{plural}',
2498
+ confirmationModalFolderListTitle: 'Folder list:',
2383
2499
  getActionCompleteMessage: (data) => {
2384
- const { counts } = data ?? {};
2385
- const { COMPLETE, FAILED, TOTAL } = counts ?? {};
2500
+ const { counts, tasks } = data ?? {};
2501
+ const { COMPLETE = 0, FAILED = 0, TOTAL = 0 } = counts ?? {};
2502
+ if (!TOTAL || TOTAL === 0) {
2503
+ return { content: 'No items to delete.', type: 'info' };
2504
+ }
2505
+ if (tasks && tasks.length > 0) {
2506
+ const folderTasks = tasks.filter((task) => task.data.type === 'FOLDER');
2507
+ const fileTasks = tasks.filter((task) => task.data.type === 'FILE');
2508
+ const completeFolders = folderTasks.filter((task) => task.status === 'COMPLETE').length;
2509
+ const failedFolders = folderTasks.filter((task) => task.status === 'FAILED').length;
2510
+ const completeFiles = fileTasks.filter((task) => task.status === 'COMPLETE').length;
2511
+ const failedFiles = fileTasks.filter((task) => task.status === 'FAILED').length;
2512
+ const hasFolders = folderTasks.length > 0;
2513
+ const hasFiles = fileTasks.length > 0;
2514
+ const isMixed = hasFolders && hasFiles;
2515
+ // All successful
2516
+ if (COMPLETE === TOTAL) {
2517
+ if (isMixed) {
2518
+ return {
2519
+ content: `${formatCount(completeFolders, 'folder')} and ${completeFiles} ${pluralize(completeFiles, 'file')} deleted successfully.`,
2520
+ type: 'success',
2521
+ };
2522
+ }
2523
+ else if (hasFolders) {
2524
+ return {
2525
+ content: `${formatCount(completeFolders, 'folder')} deleted successfully.`,
2526
+ type: 'success',
2527
+ };
2528
+ }
2529
+ else {
2530
+ return {
2531
+ content: `${formatCount(completeFiles, 'file')} deleted successfully.`,
2532
+ type: 'success',
2533
+ };
2534
+ }
2535
+ }
2536
+ // Complete failure
2537
+ if (FAILED === TOTAL) {
2538
+ if (isMixed) {
2539
+ return {
2540
+ content: `Failed to delete ${failedFolders} ${pluralize(failedFolders, 'folder')} and ${failedFiles} ${pluralize(failedFiles, 'file')}. Some contents may have been deleted.`,
2541
+ type: 'error',
2542
+ };
2543
+ }
2544
+ else if (hasFolders) {
2545
+ return {
2546
+ content: `Failed to delete ${failedFolders} ${pluralize(failedFolders, 'folder')}. Some items may have been deleted.`,
2547
+ type: 'error',
2548
+ };
2549
+ }
2550
+ else {
2551
+ return {
2552
+ content: `Failed to delete ${failedFiles} ${pluralize(failedFiles, 'file')}.`,
2553
+ type: 'error',
2554
+ };
2555
+ }
2556
+ }
2557
+ // Partial failure
2558
+ if (isMixed) {
2559
+ const messages = [];
2560
+ if (completeFiles > 0) {
2561
+ messages.push(`${completeFiles} ${pluralize(completeFiles, 'file')} deleted`);
2562
+ }
2563
+ if (completeFolders > 0) {
2564
+ messages.push(`${completeFolders} ${pluralize(completeFolders, 'folder')} deleted`);
2565
+ }
2566
+ if (failedFiles > 0) {
2567
+ messages.push(`${failedFiles} ${pluralize(failedFiles, 'file')} failed`);
2568
+ }
2569
+ if (failedFolders > 0) {
2570
+ messages.push(`${failedFolders} ${pluralize(failedFolders, 'folder')} failed`);
2571
+ }
2572
+ return {
2573
+ content: messages.join(', ') + '. Some items may have been deleted.',
2574
+ type: 'error',
2575
+ };
2576
+ }
2577
+ else if (hasFolders) {
2578
+ // Folders only partial failure
2579
+ if (completeFolders > 0) {
2580
+ return {
2581
+ content: `${completeFolders} ${pluralize(completeFolders, 'folder')} deleted, ${failedFolders} ${pluralize(failedFolders, 'folder')} failed. Some items may have been deleted.`,
2582
+ type: 'error',
2583
+ };
2584
+ }
2585
+ else {
2586
+ return {
2587
+ content: `Failed to delete ${failedFolders} ${pluralize(failedFolders, 'folder')}. Some items may have been deleted.`,
2588
+ type: 'error',
2589
+ };
2590
+ }
2591
+ }
2592
+ else {
2593
+ // Files only partial failure
2594
+ return {
2595
+ content: `${completeFiles} ${pluralize(completeFiles, 'file')} deleted, ${failedFiles} ${pluralize(failedFiles, 'file')} failed.`,
2596
+ type: 'error',
2597
+ };
2598
+ }
2599
+ }
2600
+ // Fallback to generic messaging if tasks not available
2386
2601
  if (COMPLETE === TOTAL) {
2387
- return { content: 'All files deleted.', type: 'success' };
2602
+ return {
2603
+ content: `${formatCount(TOTAL, 'item')} deleted successfully.`,
2604
+ type: 'success',
2605
+ };
2388
2606
  }
2389
2607
  if (FAILED === TOTAL) {
2390
- return { content: 'All files failed to delete.', type: 'error' };
2608
+ return {
2609
+ content: `Failed to delete ${formatCount(TOTAL, 'item')}.`,
2610
+ type: 'error',
2611
+ };
2391
2612
  }
2392
2613
  return {
2393
- content: `${COMPLETE} files deleted, ${FAILED} files failed to delete.`,
2614
+ content: `${COMPLETE} ${pluralize(COMPLETE, 'item')} deleted, ${FAILED} ${pluralize(FAILED, 'item')} failed to delete.`,
2394
2615
  type: 'error',
2395
2616
  };
2396
2617
  },
@@ -2465,6 +2686,7 @@ const DEFAULT_LOCATION_DETAIL_VIEW_DISPLAY_TEXT = {
2465
2686
  tableColumnSizeHeader: 'Size',
2466
2687
  tableColumnTypeHeader: 'Type',
2467
2688
  selectFileLabel: 'Select file',
2689
+ selectFolderLabel: 'Select folder',
2468
2690
  selectAllFilesLabel: 'Select all files',
2469
2691
  getActionListItemLabel: (key = '') => {
2470
2692
  switch (key) {
@@ -3287,6 +3509,7 @@ const Title$1 = ({ title }) => (React__namespace["default"].createElement(Headin
3287
3509
 
3288
3510
  const DEFAULT_COMPOSABLES = {
3289
3511
  ActionCancel,
3512
+ ActionConfirmationModal,
3290
3513
  ActionDestination: ActionDestination$1,
3291
3514
  ActionExit,
3292
3515
  ActionStart,
@@ -3477,6 +3700,26 @@ const ActionCancelControl = () => {
3477
3700
  return React__namespace["default"].createElement(Resolved, { ...props });
3478
3701
  };
3479
3702
 
3703
+ const useActionConfirmationModal = () => {
3704
+ const { data: { confirmationModal = {} }, onConfirmationModalConfirm = () => { }, onConfirmationModalCancel = () => { }, } = useControlsContext();
3705
+ return {
3706
+ isOpen: false,
3707
+ title: 'Confirm Action',
3708
+ message: '',
3709
+ confirmLabel: 'Confirm',
3710
+ cancelLabel: 'Cancel',
3711
+ ...confirmationModal,
3712
+ onConfirm: onConfirmationModalConfirm,
3713
+ onCancel: onConfirmationModalCancel,
3714
+ };
3715
+ };
3716
+
3717
+ const ActionConfirmationModalControl = () => {
3718
+ const props = useActionConfirmationModal();
3719
+ const Resolved = useResolvedComposable(ActionConfirmationModal, 'ActionConfirmationModal');
3720
+ return React__namespace["default"].createElement(Resolved, { ...props });
3721
+ };
3722
+
3480
3723
  const getNavigationItems = ({ destinationParts, location, onNavigate, }) => {
3481
3724
  const { bucket, permissions, prefix = '', type } = location;
3482
3725
  const destinationSubpaths = [];
@@ -4011,9 +4254,18 @@ const FILE_DATA_ITEM_TABLE_KEYS = [
4011
4254
  'status',
4012
4255
  'cancel',
4013
4256
  ];
4257
+ const DELETE_TABLE_KEYS = [
4258
+ 'name',
4259
+ 'folder',
4260
+ 'type',
4261
+ 'size',
4262
+ 'status',
4263
+ 'progress',
4264
+ 'cancel',
4265
+ ];
4014
4266
 
4015
- const getFileType = (value, fallback = '') => value.lastIndexOf('.') !== -1
4016
- ? value.slice(value.lastIndexOf('.') + 1)
4267
+ const getFileType = (value, fallback = '') => value?.lastIndexOf?.('.') !== -1
4268
+ ? value?.slice(value?.lastIndexOf?.('.') + 1)
4017
4269
  : fallback;
4018
4270
  const getCellName = (value) =>
4019
4271
  // `value.split` always returns an array with at least one entry
@@ -4028,7 +4280,7 @@ const getFileDataCellFolder = (task) => {
4028
4280
  ? task.data.sourceKey
4029
4281
  : task.data.key;
4030
4282
  const { fileKey } = task.data;
4031
- return targetKey.slice(0, -fileKey.length);
4283
+ return targetKey.slice(0, -fileKey?.length);
4032
4284
  };
4033
4285
  const getUploadCellProgress = ({ progress, status, }) => {
4034
4286
  // prefer `progress` if available, 1 if status is complete, default 0
@@ -4073,31 +4325,31 @@ const getFileDataCancelCellContent = (data) => {
4073
4325
  * Generates a unique key for a table cell based on the key and item id
4074
4326
  */
4075
4327
  const getFileDataCellKey = ({ key, item, }) => `${key}-${item.data.id}`;
4076
- const name$2 = (data) => {
4328
+ const name$3 = (data) => {
4077
4329
  const key = getFileDataCellKey(data);
4078
4330
  const { item } = data;
4079
4331
  const text = item.data.fileKey;
4080
4332
  const icon = STATUS_ICONS[item.status];
4081
4333
  return { key, type: 'text', content: { icon, text } };
4082
4334
  };
4083
- const folder$2 = (data) => {
4335
+ const folder$3 = (data) => {
4084
4336
  const key = getFileDataCellKey(data);
4085
4337
  const text = getFileDataCellFolder(data.item);
4086
4338
  return { key, type: 'text', content: { text } };
4087
4339
  };
4088
- const type$2 = (data) => {
4340
+ const type$3 = (data) => {
4089
4341
  const key = getFileDataCellKey(data);
4090
4342
  const { fileKey } = data.item.data;
4091
4343
  const text = getFileType(fileKey);
4092
4344
  return { key, type: 'text', content: { text } };
4093
4345
  };
4094
- const size$2 = (data) => {
4346
+ const size$3 = (data) => {
4095
4347
  const key = getFileDataCellKey(data);
4096
4348
  const { size: value } = data.item.data;
4097
4349
  const displayValue = getFileSize(value);
4098
4350
  return { key, type: 'number', content: { value, displayValue } };
4099
4351
  };
4100
- const cancel$2 = (data) => {
4352
+ const cancel$3 = (data) => {
4101
4353
  const key = getFileDataCellKey(data);
4102
4354
  const content = getFileDataCancelCellContent(data);
4103
4355
  return { key, type: 'button', content };
@@ -4113,12 +4365,12 @@ const status$3 = (data) => {
4113
4365
  return { key, type: 'text', content: { text } };
4114
4366
  };
4115
4367
  const COPY_CELL_RESOLVERS = {
4116
- name: name$2,
4117
- folder: folder$2,
4118
- type: type$2,
4119
- size: size$2,
4368
+ name: name$3,
4369
+ folder: folder$3,
4370
+ type: type$3,
4371
+ size: size$3,
4120
4372
  status: status$3,
4121
- cancel: cancel$2,
4373
+ cancel: cancel$3,
4122
4374
  /**
4123
4375
  * @deprecated
4124
4376
  *
@@ -4140,8 +4392,160 @@ const COPY_TABLE_RESOLVERS = {
4140
4392
  getRowKey: ({ item }) => item.data.id,
4141
4393
  };
4142
4394
 
4395
+ const getFolderName = (folderKey) => {
4396
+ return folderKey.replace(/\/$/, '').split('/').pop() ?? folderKey;
4397
+ };
4398
+ /**
4399
+ * Creates JSX content showing a list of folders to be deleted
4400
+ */
4401
+ const createFolderListContent = (folders, folderListTitle) => {
4402
+ return (React__namespace["default"].createElement(React__namespace["default"].Fragment, null,
4403
+ React__namespace["default"].createElement(TextElement, { className: "amplify-modal__list-title" },
4404
+ React__namespace["default"].createElement("strong", null, folderListTitle)),
4405
+ React__namespace["default"].createElement(UnorderedListElement, { className: "amplify-modal__list" }, folders.map((folder) => (React__namespace["default"].createElement(ListItemElement, { key: folder.id, className: "amplify-modal__list-item" }, getFolderName(folder.key)))))));
4406
+ };
4407
+ const createDeleteConfirmationModalProps = ({ items, showConfirmation, displayText, }) => {
4408
+ const folders = getSelectedFolders(items);
4409
+ const folderCount = folders.length;
4410
+ return {
4411
+ isOpen: showConfirmation,
4412
+ title: displayText.confirmationModalTitle,
4413
+ message: displayText.confirmationModalMessage
4414
+ .replace('{count}', folderCount.toString())
4415
+ .replace('{plural}', folderCount !== 1 ? 's' : ''),
4416
+ confirmLabel: displayText.confirmationModalConfirmLabel,
4417
+ cancelLabel: displayText.confirmationModalCancelLabel,
4418
+ content: createFolderListContent(folders, displayText.confirmationModalFolderListTitle),
4419
+ };
4420
+ };
4421
+ /**
4422
+ * Maximum number of files to count before showing "+" notation
4423
+ * This prevents expensive operations on very large folders
4424
+ */
4425
+ const MAX_FILE_COUNT_LIMIT = 5000;
4426
+ const LIST_PAGE_SIZE = 1000;
4427
+ /**
4428
+ * Count the total number of files in a folder with pagination and limits
4429
+ * @param folderKey - The folder path to count files in
4430
+ * @param config - Storage configuration
4431
+ * @returns Promise<number | string> - File count or "5000+" if exceeds limit
4432
+ */
4433
+ const countFilesInFolder = async (folderKey, config) => {
4434
+ try {
4435
+ const { accountId, credentials, customEndpoint } = config;
4436
+ const bucket = constructBucket$1(config);
4437
+ let fileCount = 0;
4438
+ let nextToken;
4439
+ let hasMoreItems = true;
4440
+ while (hasMoreItems && fileCount < MAX_FILE_COUNT_LIMIT) {
4441
+ const { items, nextToken: listNextToken } = await internals.list({
4442
+ path: folderKey,
4443
+ options: {
4444
+ bucket,
4445
+ locationCredentialsProvider: credentials,
4446
+ expectedBucketOwner: accountId,
4447
+ customEndpoint,
4448
+ pageSize: LIST_PAGE_SIZE,
4449
+ nextToken,
4450
+ },
4451
+ });
4452
+ const batchFileCount = items.filter((item) => !item.path.endsWith('/')).length;
4453
+ fileCount += batchFileCount;
4454
+ nextToken = listNextToken;
4455
+ hasMoreItems = !!nextToken;
4456
+ if (fileCount >= MAX_FILE_COUNT_LIMIT) {
4457
+ return `${MAX_FILE_COUNT_LIMIT}+`;
4458
+ }
4459
+ }
4460
+ if (hasMoreItems) {
4461
+ return `${fileCount}+`;
4462
+ }
4463
+ return fileCount;
4464
+ }
4465
+ catch (error) {
4466
+ return 0;
4467
+ }
4468
+ };
4469
+
4470
+ const getDeleteCellKey = (data) => `${data.key}-${data.item.data.id}`;
4471
+ const getCellDataFolder = (data) => {
4472
+ const { type, key } = data;
4473
+ const fileKey = getFileKey(key);
4474
+ const targetKey = data.key;
4475
+ if (type === 'FOLDER') {
4476
+ const pathWithoutTrailingSlash = targetKey.replace(/\/$/, '');
4477
+ const lastSlashIndex = pathWithoutTrailingSlash.lastIndexOf('/');
4478
+ return lastSlashIndex >= 0
4479
+ ? pathWithoutTrailingSlash.slice(0, lastSlashIndex + 1)
4480
+ : '';
4481
+ }
4482
+ return targetKey.slice(0, -fileKey?.length);
4483
+ };
4484
+ const name$2 = (data) => {
4485
+ const key = getDeleteCellKey(data);
4486
+ const { item } = data;
4487
+ let text;
4488
+ if (item.data.type === 'FOLDER') {
4489
+ text = `${getFolderName(item.data.key)}/`;
4490
+ }
4491
+ else {
4492
+ text = item.data.fileKey ?? getCellName(item.data.key);
4493
+ }
4494
+ const icon = STATUS_ICONS[item.status];
4495
+ return { key, type: 'text', content: { icon, text } };
4496
+ };
4497
+ const folder$2 = (data) => {
4498
+ const key = getDeleteCellKey(data);
4499
+ const text = getCellDataFolder(data.item.data);
4500
+ return { key, type: 'text', content: { text } };
4501
+ };
4502
+ const type$2 = (data) => {
4503
+ const key = getDeleteCellKey(data);
4504
+ if (data.item.data.type === 'FOLDER') {
4505
+ return { key, type: 'text', content: { text: 'Folder' } };
4506
+ }
4507
+ const text = getFileType(data.item.data.key);
4508
+ return { key, type: 'text', content: { text } };
4509
+ };
4510
+ const size$2 = (data) => {
4511
+ const key = getDeleteCellKey(data);
4512
+ const itemData = data.item.data;
4513
+ if (data.item.data.type === 'FOLDER') {
4514
+ return { key, type: 'text', content: { text: '-' } };
4515
+ }
4516
+ const value = 'size' in itemData ? itemData.size : 0;
4517
+ const displayValue = getFileSize(value);
4518
+ return { key, type: 'number', content: { value, displayValue } };
4519
+ };
4520
+ const getCancelCellContent = (data) => {
4521
+ const { item, props } = data;
4522
+ const { cancel, status } = item;
4523
+ const { isProcessing, onTaskRemove } = props;
4524
+ const isQueued = status === 'QUEUED';
4525
+ const isRemovable = isQueued && !isProcessing;
4526
+ const isCancelable = isProcessing && !!cancel;
4527
+ const itemAriaValue = getCellName(item.data.fileKey ?? item.data.key);
4528
+ const ariaLabel = `${isRemovable ? 'Remove' : 'Cancel'} item: ${itemAriaValue}`;
4529
+ const isDisabled = !isRemovable && !isCancelable;
4530
+ const onClick = !isCancelable && !isRemovable
4531
+ ? undefined
4532
+ : () => {
4533
+ if (isRemovable) {
4534
+ onTaskRemove?.(item);
4535
+ return;
4536
+ }
4537
+ if (isCancelable)
4538
+ cancel();
4539
+ };
4540
+ return { ariaLabel, isDisabled, onClick, icon: 'cancel' };
4541
+ };
4542
+ const cancel$2 = (data) => {
4543
+ const key = getDeleteCellKey(data);
4544
+ const content = getCancelCellContent(data);
4545
+ return { key, type: 'button', content };
4546
+ };
4143
4547
  const status$2 = (data) => {
4144
- const key = getFileDataCellKey(data);
4548
+ const key = getDeleteCellKey(data);
4145
4549
  const { item: { status }, props: { displayText }, } = data;
4146
4550
  const statusLabelKey = STATUS_LABELS[status];
4147
4551
  const text = isDeleteViewDisplayTextKey(statusLabelKey)
@@ -4149,21 +4553,47 @@ const status$2 = (data) => {
4149
4553
  : '';
4150
4554
  return { key, type: 'text', content: { text } };
4151
4555
  };
4556
+ const progress$2 = (data) => {
4557
+ const key = getDeleteCellKey(data);
4558
+ const { item } = data;
4559
+ const itemIsFile = item.data.type === 'FILE';
4560
+ if (itemIsFile) {
4561
+ const text = item.status === 'COMPLETE' ? 'Deleted' : '-';
4562
+ return { key, type: 'text', content: { text } };
4563
+ }
4564
+ if ((item.status === 'PENDING' ||
4565
+ item.status === 'FAILED' ||
4566
+ item.status === 'CANCELED') &&
4567
+ item.data.totalCount !== undefined) {
4568
+ const countDisplay = item.data.totalCount ?? '?';
4569
+ const text = `${item.successCount ?? 0}/${countDisplay} files`;
4570
+ return { key, type: 'text', content: { text } };
4571
+ }
4572
+ else if (item.status === 'COMPLETE') {
4573
+ if (item.data.totalCount === null) {
4574
+ const text = `${item.successCount ?? 0} files deleted`;
4575
+ return { key, type: 'text', content: { text } };
4576
+ }
4577
+ const text = `${item.data.totalCount} files deleted`;
4578
+ return { key, type: 'text', content: { text } };
4579
+ }
4580
+ else {
4581
+ const text = item.data.totalCount === null
4582
+ ? 'Count failed'
4583
+ : item.data.totalCount !== undefined
4584
+ ? `${item.data.totalCount} files`
4585
+ : 'Calculating...';
4586
+ return { key, type: 'text', content: { text } };
4587
+ }
4588
+ };
4152
4589
  const DELETE_CELL_RESOLVERS = {
4153
4590
  name: name$2,
4154
4591
  folder: folder$2,
4155
4592
  type: type$2,
4156
4593
  size: size$2,
4157
4594
  status: status$2,
4595
+ progress: progress$2,
4158
4596
  cancel: cancel$2,
4159
- /**
4160
- * @deprecated
4161
- *
4162
- * non-upload view tables do not include "progress" headers but include here to
4163
- * keep TS happy as "progress" headers were included in display text interfaces
4164
- * and cannot be removed from the tables without a breaking change
4165
- */
4166
- progress: ui.noop,
4167
4597
  };
4168
4598
  const DELETE_TABLE_RESOLVERS = {
4169
4599
  getCell: (data) => DELETE_CELL_RESOLVERS[data.key](data),
@@ -4920,6 +5350,7 @@ function CopyViewProvider({ children, ...props }) {
4920
5350
  }
4921
5351
 
4922
5352
  const DEFAULT_STATE = {
5353
+ dataItems: undefined,
4923
5354
  fileDataItems: undefined,
4924
5355
  };
4925
5356
  const locationItemsReducer = (prevState, event) => {
@@ -4928,27 +5359,29 @@ const locationItemsReducer = (prevState, event) => {
4928
5359
  const { items } = event;
4929
5360
  if (!items?.length)
4930
5361
  return prevState;
4931
- if (!prevState.fileDataItems?.length) {
4932
- return { fileDataItems: items.map(createFileDataItem) };
4933
- }
4934
- const nextFileDataItems = items?.reduce((fileDataItems, data) => prevState.fileDataItems?.some(({ id }) => id === data.id)
4935
- ? fileDataItems
4936
- : fileDataItems.concat(createFileDataItem(data)), []);
4937
- if (!nextFileDataItems?.length)
4938
- return prevState;
5362
+ const nextDataItems = !prevState.dataItems?.length
5363
+ ? items
5364
+ : prevState.dataItems.concat(items.filter((data) => !prevState.dataItems?.some(({ id }) => id === data.id)));
5365
+ const fileItems = items.filter((item) => item.type === 'FILE');
5366
+ const nextFileDataItems = !prevState.fileDataItems?.length
5367
+ ? fileItems.map(createFileDataItem)
5368
+ : prevState.fileDataItems.concat(fileItems
5369
+ .filter((data) => !prevState.fileDataItems?.some(({ id }) => id === data.id))
5370
+ .map(createFileDataItem));
4939
5371
  return {
4940
- fileDataItems: prevState.fileDataItems.concat(nextFileDataItems),
5372
+ dataItems: nextDataItems,
5373
+ fileDataItems: nextFileDataItems,
4941
5374
  };
4942
5375
  }
4943
5376
  case 'REMOVE_LOCATION_ITEM': {
4944
5377
  const { id } = event;
4945
- if (!prevState.fileDataItems)
4946
- return prevState;
4947
- const fileDataItems = prevState.fileDataItems.filter((item) => item.id !== id);
4948
- if (fileDataItems.length === prevState.fileDataItems.length) {
5378
+ const dataItems = prevState.dataItems?.filter((item) => item.id !== id);
5379
+ const fileDataItems = prevState.fileDataItems?.filter((item) => item.id !== id);
5380
+ if (dataItems?.length === prevState.dataItems?.length &&
5381
+ fileDataItems?.length === prevState.fileDataItems?.length) {
4949
5382
  return prevState;
4950
5383
  }
4951
- return { fileDataItems };
5384
+ return { dataItems, fileDataItems };
4952
5385
  }
4953
5386
  case 'RESET_LOCATION_ITEMS': {
4954
5387
  return DEFAULT_STATE;
@@ -5184,12 +5617,12 @@ CopyView.Title = TitleControl;
5184
5617
  function DeleteViewProvider({ children, ...props }) {
5185
5618
  const { DeleteView: displayText } = useDisplayText();
5186
5619
  const { actionCancelLabel, actionExitLabel, actionStartLabel, title, statusDisplayCanceledLabel, statusDisplayCompletedLabel, statusDisplayFailedLabel, statusDisplayQueuedLabel, getActionCompleteMessage, } = displayText;
5187
- const { isProcessing, isProcessingComplete, statusCounts, tasks: items, onActionCancel, onActionStart, onActionExit, onTaskRemove, } = props;
5620
+ const { isProcessing, isProcessingComplete, statusCounts, tasks, confirmationModal, onActionCancel, onActionStart, onActionExit, onTaskRemove, onConfirmDelete, onCancelConfirmation, } = props;
5188
5621
  const message = isProcessingComplete
5189
- ? getActionCompleteMessage({ counts: statusCounts })
5622
+ ? getActionCompleteMessage({ counts: statusCounts, tasks })
5190
5623
  : undefined;
5191
- const tableData = useResolveTableData(FILE_DATA_ITEM_TABLE_KEYS, DELETE_TABLE_RESOLVERS, {
5192
- items,
5624
+ const tableData = useResolveTableData(DELETE_TABLE_KEYS, DELETE_TABLE_RESOLVERS, {
5625
+ items: tasks,
5193
5626
  props: { displayText, isProcessing, onTaskRemove },
5194
5627
  });
5195
5628
  return (React__namespace["default"].createElement(ControlsContextProvider, { data: {
@@ -5207,24 +5640,80 @@ function DeleteViewProvider({ children, ...props }) {
5207
5640
  tableData,
5208
5641
  title,
5209
5642
  message,
5210
- }, onActionStart: onActionStart, onActionExit: onActionExit, onActionCancel: onActionCancel }, children));
5643
+ confirmationModal,
5644
+ }, onActionStart: onActionStart, onActionExit: onActionExit, onActionCancel: onActionCancel, onConfirmationModalConfirm: onConfirmDelete, onConfirmationModalCancel: onCancelConfirmation }, children));
5211
5645
  }
5212
5646
 
5213
5647
  // assign to constant to ensure referential equality
5214
5648
  const EMPTY_ITEMS$1 = [];
5215
5649
  const useDeleteView = (options) => {
5216
5650
  const { onExit: _onExit } = options ?? {};
5651
+ const { DeleteView: displayText } = useDisplayText();
5217
5652
  const [{ location }, storeDispatch] = useStore();
5218
5653
  const [locationItems, locationItemsDispatch] = useLocationItems();
5219
5654
  const { current } = location;
5220
- const { fileDataItems: items = EMPTY_ITEMS$1 } = locationItems;
5221
- const [processState, handleProcess] = useAction('delete', { items });
5655
+ const { dataItems = EMPTY_ITEMS$1 } = locationItems;
5656
+ const getConfig = useGetActionInput();
5657
+ const [itemsWithCount, setItemsWithCount] = React__namespace["default"].useState(dataItems);
5658
+ const folderCountsRef = React__namespace["default"].useRef(new Map());
5659
+ // Sync itemsWithCount with dataItems when dataItems changes (e.g., item removal)
5660
+ React__namespace["default"].useEffect(() => {
5661
+ const itemsWithAppliedCounts = dataItems.map((item) => ({
5662
+ ...item,
5663
+ totalCount: folderCountsRef.current.get(item.id),
5664
+ }));
5665
+ setItemsWithCount(itemsWithAppliedCounts);
5666
+ }, [dataItems]);
5667
+ const [processState, handleProcess] = useAction('delete', {
5668
+ items: itemsWithCount,
5669
+ });
5670
+ const [showConfirmation, setShowConfirmation] = React__namespace["default"].useState(false);
5222
5671
  const { isProcessing, isProcessingComplete, statusCounts, tasks } = processState;
5672
+ const selectionSummary = getSelectionSummary(dataItems);
5673
+ const { hasFolders } = selectionSummary;
5674
+ React__namespace["default"].useEffect(() => {
5675
+ const initializeFolderCounts = async () => {
5676
+ if (!selectionSummary.hasFolders || !current) {
5677
+ return;
5678
+ }
5679
+ const config = getConfig(current);
5680
+ const foldersToCount = dataItems.filter((item) => item.type === 'FOLDER' && !folderCountsRef.current.has(item.id));
5681
+ if (foldersToCount.length === 0) {
5682
+ return;
5683
+ }
5684
+ await Promise.all(foldersToCount.map(async (folder) => {
5685
+ try {
5686
+ const totalCount = await countFilesInFolder(folder.key, config);
5687
+ folderCountsRef.current.set(folder.id, totalCount);
5688
+ }
5689
+ catch (error) {
5690
+ folderCountsRef.current.set(folder.id, null);
5691
+ }
5692
+ }));
5693
+ setItemsWithCount((currentItems) => currentItems.map((item) => ({
5694
+ ...item,
5695
+ totalCount: folderCountsRef.current.get(item.id),
5696
+ })));
5697
+ };
5698
+ initializeFolderCounts();
5699
+ }, [current, getConfig, selectionSummary.hasFolders, dataItems]);
5223
5700
  const onActionStart = () => {
5224
5701
  if (!current)
5225
5702
  return;
5703
+ if (hasFolders) {
5704
+ setShowConfirmation(true);
5705
+ }
5706
+ else {
5707
+ handleProcess();
5708
+ }
5709
+ };
5710
+ const onConfirmDelete = () => {
5711
+ setShowConfirmation(false);
5226
5712
  handleProcess();
5227
5713
  };
5714
+ const onCancelConfirmation = () => {
5715
+ setShowConfirmation(false);
5716
+ };
5228
5717
  const onActionCancel = () => {
5229
5718
  tasks.forEach((task) => {
5230
5719
  // @TODO Fixme, calling cancel on task doesn't currently work
@@ -5243,6 +5732,11 @@ const useDeleteView = (options) => {
5243
5732
  const onTaskRemove = React__namespace["default"].useCallback(({ data }) => {
5244
5733
  locationItemsDispatch({ type: 'REMOVE_LOCATION_ITEM', id: data.id });
5245
5734
  }, [locationItemsDispatch]);
5735
+ const confirmationModal = React__namespace["default"].useMemo(() => createDeleteConfirmationModalProps({
5736
+ items: dataItems,
5737
+ showConfirmation,
5738
+ displayText,
5739
+ }), [dataItems, showConfirmation, displayText]);
5246
5740
  return {
5247
5741
  isProcessing,
5248
5742
  isProcessingComplete,
@@ -5253,6 +5747,9 @@ const useDeleteView = (options) => {
5253
5747
  onActionExit,
5254
5748
  onActionStart,
5255
5749
  onTaskRemove,
5750
+ onConfirmDelete,
5751
+ onCancelConfirmation,
5752
+ confirmationModal,
5256
5753
  };
5257
5754
  };
5258
5755
 
@@ -5271,11 +5768,13 @@ const DeleteView = ({ className, ...props }) => {
5271
5768
  React__namespace["default"].createElement(MessageControl, null)),
5272
5769
  React__namespace["default"].createElement(ViewElement, { className: `${STORAGE_BROWSER_BLOCK}__buttons` },
5273
5770
  React__namespace["default"].createElement(ActionCancelControl, null),
5274
- React__namespace["default"].createElement(ActionStartControl, null))))));
5771
+ React__namespace["default"].createElement(ActionStartControl, null))),
5772
+ React__namespace["default"].createElement(ActionConfirmationModalControl, null))));
5275
5773
  };
5276
5774
  DeleteView.displayName = 'DeleteView';
5277
5775
  DeleteView.Provider = DeleteViewProvider;
5278
5776
  DeleteView.Cancel = ActionCancelControl;
5777
+ DeleteView.ConfirmationModal = ActionConfirmationModalControl;
5279
5778
  DeleteView.Exit = ActionExitControl;
5280
5779
  DeleteView.Message = MessageControl;
5281
5780
  DeleteView.Start = ActionStartControl;
@@ -5489,34 +5988,45 @@ const getFileRowContent = ({ filePreviewEnabled, permissions, isSelected, itemLo
5489
5988
  }
5490
5989
  });
5491
5990
 
5492
- const getFolderRowContent = ({ itemSubPath, rowId, onNavigate, }) => LOCATION_DETAIL_VIEW_HEADERS.map((columnKey) => {
5493
- const key = `${columnKey}-${rowId}`;
5494
- switch (columnKey) {
5495
- case 'checkbox': {
5496
- return { key, type: 'text', content: { text: '' } };
5497
- }
5498
- case 'name': {
5499
- return {
5500
- key,
5501
- type: 'button',
5502
- content: {
5503
- icon: 'folder',
5504
- ariaLabel: itemSubPath,
5505
- label: itemSubPath,
5506
- onClick: onNavigate,
5507
- },
5508
- };
5509
- }
5510
- case 'type': {
5511
- return { key, type: 'text', content: { text: 'Folder' } };
5512
- }
5513
- case 'last-modified':
5514
- case 'size':
5515
- case 'download': {
5516
- return { key, type: 'text', content: { text: '' } };
5991
+ const getFolderRowContent = ({ itemSubPath, rowId, isSelected, selectFolderLabel, onNavigate, onSelect, }) => {
5992
+ return LOCATION_DETAIL_VIEW_HEADERS.map((columnKey) => {
5993
+ const key = `${columnKey}-${rowId}`;
5994
+ switch (columnKey) {
5995
+ case 'checkbox': {
5996
+ return {
5997
+ key,
5998
+ type: 'checkbox',
5999
+ content: {
6000
+ checked: isSelected,
6001
+ id: key,
6002
+ label: `${selectFolderLabel} ${itemSubPath}`,
6003
+ onSelect,
6004
+ },
6005
+ };
6006
+ }
6007
+ case 'name': {
6008
+ return {
6009
+ key,
6010
+ type: 'button',
6011
+ content: {
6012
+ icon: 'folder',
6013
+ ariaLabel: itemSubPath,
6014
+ label: itemSubPath,
6015
+ onClick: onNavigate,
6016
+ },
6017
+ };
6018
+ }
6019
+ case 'type': {
6020
+ return { key, type: 'text', content: { text: 'Folder' } };
6021
+ }
6022
+ case 'last-modified':
6023
+ case 'size':
6024
+ case 'download': {
6025
+ return { key, type: 'text', content: { text: '' } };
6026
+ }
5517
6027
  }
5518
- }
5519
- });
6028
+ });
6029
+ };
5520
6030
 
5521
6031
  const getHeaders$1 = ({ tableColumnLastModifiedHeader, tableColumnNameHeader, tableColumnSizeHeader, tableColumnTypeHeader, areAllFilesSelected, selectAllFilesLabel, onSelectAll, hasFiles, }) => LOCATION_DETAIL_VIEW_HEADERS.map((key) => {
5522
6032
  switch (key) {
@@ -5583,7 +6093,7 @@ const getHeaders$1 = ({ tableColumnLastModifiedHeader, tableColumnNameHeader, ta
5583
6093
  }
5584
6094
  });
5585
6095
 
5586
- const getLocationDetailViewTableData = ({ filePreviewEnabled, activeFile, onSelectActiveFile, areAllFilesSelected, displayText, location, fileDataItems, hasFiles, pageItems, selectFileLabel, selectAllFilesLabel, getDateDisplayValue, onDownload, onNavigate, onSelect, onSelectAll, }) => {
6096
+ const getLocationDetailViewTableData = ({ filePreviewEnabled, activeFile, onSelectActiveFile, areAllFilesSelected, displayText, location, fileDataItems, dataItems, hasFiles, pageItems, selectFileLabel, selectAllFilesLabel, getDateDisplayValue, onDownload, onNavigate, onSelect, onSelectAll, }) => {
5587
6097
  const { tableColumnLastModifiedHeader, tableColumnNameHeader, tableColumnSizeHeader, tableColumnTypeHeader, } = displayText;
5588
6098
  const headers = getHeaders$1({
5589
6099
  areAllFilesSelected,
@@ -5641,6 +6151,10 @@ const getLocationDetailViewTableData = ({ filePreviewEnabled, activeFile, onSele
5641
6151
  }
5642
6152
  onNavigate({ ...current, id }, itemLocationPath);
5643
6153
  };
6154
+ const isSelected = dataItems?.some((item) => item.id === id) ?? false;
6155
+ const onFolderSelect = () => {
6156
+ onSelect(isSelected, locationItem);
6157
+ };
5644
6158
  return {
5645
6159
  key: id,
5646
6160
  active: false,
@@ -5648,6 +6162,9 @@ const getLocationDetailViewTableData = ({ filePreviewEnabled, activeFile, onSele
5648
6162
  itemSubPath,
5649
6163
  rowId: id,
5650
6164
  onNavigate: onFolderNavigate,
6165
+ selectFolderLabel: selectFileLabel,
6166
+ isSelected,
6167
+ onSelect: onFolderSelect,
5651
6168
  }),
5652
6169
  };
5653
6170
  }
@@ -5659,7 +6176,7 @@ const getLocationDetailViewTableData = ({ filePreviewEnabled, activeFile, onSele
5659
6176
  function LocationDetailViewProvider({ children, ...props }) {
5660
6177
  const { LocationDetailView: displayText } = useDisplayText();
5661
6178
  const { LocationDetailView: { loadingIndicatorLabel, searchSubfoldersToggleLabel, selectFileLabel, selectAllFilesLabel, searchPlaceholder, searchSubmitLabel, searchClearLabel, getActionListItemLabel, getDateDisplayValue, getTitle, getListItemsResultMessage, }, } = useDisplayText();
5662
- const { actionItems, activeFile, activeFileHasNext, activeFileHasPrev, page, pageItems, hasNextPage, highestPageVisited, isLoading, isSearchSubfoldersEnabled, location, fileDataItems, hasError, hasDownloadError, message, downloadErrorMessage, searchQuery, hasExhaustedSearch, onActionSelect, onDropFiles, onRefresh, onPaginate, onDownload, onNavigate, onNavigateHome, onSelect, onSelectActiveFile, onToggleSelectAll, onSearch, onSearchQueryChange, onSearchClear, onToggleSearchSubfolders, filePreviewState, filePreviewEnabled, onRetryFilePreview, } = props;
6179
+ const { actionItems, activeFile, activeFileHasNext, activeFileHasPrev, page, pageItems, hasNextPage, highestPageVisited, isLoading, isSearchSubfoldersEnabled, location, fileDataItems, hasError, hasDownloadError, message, downloadErrorMessage, searchQuery, hasExhaustedSearch, onActionSelect, onDropFiles, onRefresh, onPaginate, onDownload, onNavigate, onNavigateHome, onSelect, onSelectActiveFile, onToggleSelectAll, onSearch, onSearchQueryChange, onSearchClear, onToggleSearchSubfolders, filePreviewState, filePreviewEnabled, onRetryFilePreview, dataItems, } = props;
5663
6180
  const actionsWithDisplayText = actionItems.map((item) => ({
5664
6181
  ...item,
5665
6182
  label: getActionListItemLabel(item.label),
@@ -5716,6 +6233,7 @@ function LocationDetailViewProvider({ children, ...props }) {
5716
6233
  onNavigate,
5717
6234
  onSelect,
5718
6235
  onSelectAll: onToggleSelectAll,
6236
+ dataItems,
5719
6237
  }),
5720
6238
  title: getTitle(location),
5721
6239
  message: messageControlContent,
@@ -5888,7 +6406,7 @@ const useLocationDetailView = (options) => {
5888
6406
  const [activeFile, setActiveFile] = React__namespace["default"].useState();
5889
6407
  const { current, key } = location;
5890
6408
  const { permissions, prefix } = current ?? {};
5891
- const { fileDataItems } = locationItems;
6409
+ const { dataItems, fileDataItems } = locationItems;
5892
6410
  const hasInvalidPrefix = ui.isUndefined(prefix);
5893
6411
  const [{ task }, handleDownload] = useAction('download');
5894
6412
  const [{ value, isLoading, hasError, message }, handleList] = useList('locationItems');
@@ -6015,13 +6533,13 @@ const useLocationDetailView = (options) => {
6015
6533
  actionType: type,
6016
6534
  icon,
6017
6535
  isDisabled: ui.isFunction(disable)
6018
- ? disable(fileDataItems)
6536
+ ? disable(dataItems)
6019
6537
  : disable ?? false,
6020
6538
  isHidden: ui.isFunction(hide) ? hide(permissions) : hide,
6021
6539
  label,
6022
6540
  };
6023
6541
  });
6024
- }, [actionConfigs, fileDataItems, permissions]);
6542
+ }, [actionConfigs, dataItems, permissions]);
6025
6543
  return {
6026
6544
  actionItems,
6027
6545
  actionType,
@@ -6032,6 +6550,7 @@ const useLocationDetailView = (options) => {
6032
6550
  page: currentPage,
6033
6551
  pageItems,
6034
6552
  location,
6553
+ dataItems,
6035
6554
  fileDataItems,
6036
6555
  hasError,
6037
6556
  hasDownloadError: task?.status === 'FAILED',
@@ -6087,16 +6606,16 @@ const useLocationDetailView = (options) => {
6087
6606
  storeDispatch({ type: 'RESET_ACTION_TYPE' });
6088
6607
  locationItemsDispatch({ type: 'RESET_LOCATION_ITEMS' });
6089
6608
  },
6090
- onSelect: (isSelected, fileItem) => {
6609
+ onSelect: (isSelected, item) => {
6091
6610
  locationItemsDispatch(isSelected
6092
- ? { type: 'REMOVE_LOCATION_ITEM', id: fileItem.id }
6093
- : { type: 'SET_LOCATION_ITEMS', items: [fileItem] });
6611
+ ? { type: 'REMOVE_LOCATION_ITEM', id: item.id }
6612
+ : { type: 'SET_LOCATION_ITEMS', items: [item] });
6094
6613
  },
6095
6614
  onToggleSelectAll: () => {
6096
- const fileItems = pageItems.filter((item) => item.type === 'FILE');
6097
- locationItemsDispatch(fileItems.length === fileDataItems?.length
6615
+ const selectableItems = pageItems;
6616
+ locationItemsDispatch(selectableItems.length === dataItems?.length
6098
6617
  ? { type: 'RESET_LOCATION_ITEMS' }
6099
- : { type: 'SET_LOCATION_ITEMS', items: fileItems });
6618
+ : { type: 'SET_LOCATION_ITEMS', items: selectableItems });
6100
6619
  },
6101
6620
  onSearch: () => {
6102
6621
  setActiveFile(undefined);