@wordpress/media-utils 5.41.1-next.v.202603161435.0 → 5.43.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 (33) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/components/media-upload-modal/index.cjs +133 -87
  3. package/build/components/media-upload-modal/index.cjs.map +3 -3
  4. package/build/components/media-upload-modal/upload-status-popover.cjs +156 -0
  5. package/build/components/media-upload-modal/upload-status-popover.cjs.map +7 -0
  6. package/build/components/media-upload-modal/use-invalidate-attachment-resolutions.cjs +45 -0
  7. package/build/components/media-upload-modal/use-invalidate-attachment-resolutions.cjs.map +7 -0
  8. package/build/components/media-upload-modal/use-upload-status.cjs +127 -0
  9. package/build/components/media-upload-modal/use-upload-status.cjs.map +7 -0
  10. package/build-module/components/media-upload-modal/index.mjs +126 -88
  11. package/build-module/components/media-upload-modal/index.mjs.map +2 -2
  12. package/build-module/components/media-upload-modal/upload-status-popover.mjs +131 -0
  13. package/build-module/components/media-upload-modal/upload-status-popover.mjs.map +7 -0
  14. package/build-module/components/media-upload-modal/use-invalidate-attachment-resolutions.mjs +20 -0
  15. package/build-module/components/media-upload-modal/use-invalidate-attachment-resolutions.mjs.map +7 -0
  16. package/build-module/components/media-upload-modal/use-upload-status.mjs +102 -0
  17. package/build-module/components/media-upload-modal/use-upload-status.mjs.map +7 -0
  18. package/build-style/style-rtl.css +73 -3
  19. package/build-style/style.css +73 -3
  20. package/build-types/components/media-upload-modal/index.d.ts.map +1 -1
  21. package/build-types/components/media-upload-modal/upload-status-popover.d.ts +15 -0
  22. package/build-types/components/media-upload-modal/upload-status-popover.d.ts.map +1 -0
  23. package/build-types/components/media-upload-modal/use-invalidate-attachment-resolutions.d.ts +22 -0
  24. package/build-types/components/media-upload-modal/use-invalidate-attachment-resolutions.d.ts.map +1 -0
  25. package/build-types/components/media-upload-modal/use-upload-status.d.ts +41 -0
  26. package/build-types/components/media-upload-modal/use-upload-status.d.ts.map +1 -0
  27. package/package.json +17 -15
  28. package/src/components/media-upload-modal/index.tsx +129 -107
  29. package/src/components/media-upload-modal/style.scss +88 -3
  30. package/src/components/media-upload-modal/test/use-upload-status.test.ts +501 -0
  31. package/src/components/media-upload-modal/upload-status-popover.tsx +155 -0
  32. package/src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts +50 -0
  33. package/src/components/media-upload-modal/use-upload-status.ts +203 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/components/media-upload-modal/upload-status-popover.tsx"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useState, useEffect, useCallback, useRef } from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport { Button, Icon, Notice, Popover, Spinner } from '@wordpress/components';\nimport { check, chevronDown } from '@wordpress/icons';\n\nexport interface UploadingFile {\n\tid: string;\n\tbatchId: string;\n\tname: string;\n\tstatus: 'uploading' | 'uploaded' | 'error';\n\terror?: string;\n}\n\ninterface UploadStatusPopoverProps {\n\tuploadingFiles: UploadingFile[];\n\tonDismissError?: ( fileId: string ) => void;\n\tonOpenChange?: ( open: boolean ) => void;\n}\n\nexport function UploadStatusPopover( {\n\tuploadingFiles,\n\tonDismissError,\n\tonOpenChange,\n}: UploadStatusPopoverProps ) {\n\tconst [ isOpen, setIsOpen ] = useState( false );\n\tconst [ prevHadErrors, setPrevHadErrors ] = useState( false );\n\tconst triggerRef = useRef< HTMLButtonElement >( null );\n\n\tconst updateIsOpen = useCallback(\n\t\t( open: boolean ) => {\n\t\t\tsetIsOpen( open );\n\t\t\tonOpenChange?.( open );\n\t\t},\n\t\t[ onOpenChange ]\n\t);\n\n\tconst activeFiles = uploadingFiles.filter(\n\t\t( file ) => file.status === 'uploading'\n\t);\n\tconst errorFiles = uploadingFiles.filter(\n\t\t( file ) => file.status === 'error'\n\t);\n\tconst hasErrors = errorFiles.length > 0;\n\tconst isUploading = activeFiles.length > 0;\n\n\t// Auto-expand when an error occurs.\n\tuseEffect( () => {\n\t\tif ( hasErrors && ! prevHadErrors ) {\n\t\t\tupdateIsOpen( true );\n\t\t}\n\t\tsetPrevHadErrors( hasErrors );\n\t}, [ hasErrors, prevHadErrors, updateIsOpen ] );\n\n\tif ( uploadingFiles.length === 0 ) {\n\t\treturn null;\n\t}\n\n\tlet buttonLabel, popoverHeading: string;\n\tif ( isUploading ) {\n\t\tbuttonLabel = sprintf(\n\t\t\t// translators: %s: number of files being uploaded\n\t\t\t_n( 'Uploading %s file', 'Uploading %s files', activeFiles.length ),\n\t\t\tactiveFiles.length.toLocaleString()\n\t\t);\n\t\tpopoverHeading = __( 'Uploading' );\n\t} else if ( hasErrors ) {\n\t\tbuttonLabel = sprintf(\n\t\t\t// translators: %s: number of upload errors\n\t\t\t_n( '%s upload error', '%s upload errors', errorFiles.length ),\n\t\t\terrorFiles.length.toLocaleString()\n\t\t);\n\t\tpopoverHeading = __( 'Upload errors' );\n\t} else {\n\t\tbuttonLabel = __( 'Upload complete' );\n\t\tpopoverHeading = __( 'Upload complete' );\n\t}\n\n\treturn (\n\t\t<div className=\"media-upload-modal__upload-status\">\n\t\t\t{ isUploading && <Spinner /> }\n\t\t\t<Button\n\t\t\t\tclassName=\"media-upload-modal__upload-status__trigger\"\n\t\t\t\tsize=\"compact\"\n\t\t\t\ticon={ chevronDown }\n\t\t\t\ticonPosition=\"right\"\n\t\t\t\tonClick={ () => updateIsOpen( ! isOpen ) }\n\t\t\t\taria-expanded={ isOpen }\n\t\t\t\tref={ triggerRef }\n\t\t\t>\n\t\t\t\t{ buttonLabel }\n\t\t\t</Button>\n\t\t\t{ isOpen && (\n\t\t\t\t<Popover\n\t\t\t\t\tclassName=\"media-upload-modal__upload-status__popover\"\n\t\t\t\t\tplacement=\"top-start\"\n\t\t\t\t\toffset={ 8 }\n\t\t\t\t\tanchor={ triggerRef.current }\n\t\t\t\t\tfocusOnMount\n\t\t\t\t\tonClose={ () => {\n\t\t\t\t\t\t// Let the button's onClick handle toggling when\n\t\t\t\t\t\t// the close was triggered by clicking the trigger.\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttriggerRef.current?.contains(\n\t\t\t\t\t\t\t\ttriggerRef.current.ownerDocument.activeElement\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tupdateIsOpen( false );\n\t\t\t\t\t} }\n\t\t\t\t>\n\t\t\t\t\t<div className=\"media-upload-modal__upload-status__header\">\n\t\t\t\t\t\t<h3>{ popoverHeading }</h3>\n\t\t\t\t\t</div>\n\t\t\t\t\t<ul className=\"media-upload-modal__upload-status__list\">\n\t\t\t\t\t\t{ uploadingFiles.map( ( file ) => (\n\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\tkey={ file.id }\n\t\t\t\t\t\t\t\tclassName=\"media-upload-modal__upload-status__item\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ file.status === 'uploading' && <Spinner /> }\n\t\t\t\t\t\t\t\t{ file.status === 'uploaded' && (\n\t\t\t\t\t\t\t\t\t<Icon icon={ check } size={ 16 } />\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t{ ( file.status === 'uploading' ||\n\t\t\t\t\t\t\t\t\tfile.status === 'uploaded' ) && (\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tclassName=\"media-upload-modal__upload-status__filename\"\n\t\t\t\t\t\t\t\t\t\ttitle={ file.name }\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{ file.name }\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t{ file.status === 'error' && (\n\t\t\t\t\t\t\t\t\t<Notice\n\t\t\t\t\t\t\t\t\t\tstatus=\"error\"\n\t\t\t\t\t\t\t\t\t\tisDismissible={ !! onDismissError }\n\t\t\t\t\t\t\t\t\t\tonRemove={ () =>\n\t\t\t\t\t\t\t\t\t\t\tonDismissError?.( file.id )\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{ file.name }: { file.error }\n\t\t\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</ul>\n\t\t\t\t</Popover>\n\t\t\t) }\n\t\t</div>\n\t);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // packages/media-utils/src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts
21
+ var use_invalidate_attachment_resolutions_exports = {};
22
+ __export(use_invalidate_attachment_resolutions_exports, {
23
+ useInvalidateAttachmentResolutions: () => useInvalidateAttachmentResolutions
24
+ });
25
+ module.exports = __toCommonJS(use_invalidate_attachment_resolutions_exports);
26
+ var import_element = require("@wordpress/element");
27
+ var import_core_data = require("@wordpress/core-data");
28
+ var import_data = require("@wordpress/data");
29
+ function useInvalidateAttachmentResolutions() {
30
+ const registry = (0, import_data.useRegistry)();
31
+ return (0, import_element.useCallback)(() => {
32
+ const resolvers = registry.select(import_core_data.store).getCachedResolvers();
33
+ const entityRecordResolutions = resolvers.getEntityRecords;
34
+ entityRecordResolutions?.forEach((_value, args) => {
35
+ if (args[0] === "postType" && args[1] === "attachment") {
36
+ registry.dispatch(import_core_data.store).invalidateResolution("getEntityRecords", args);
37
+ }
38
+ });
39
+ }, [registry]);
40
+ }
41
+ // Annotate the CommonJS export names for ESM import in node:
42
+ 0 && (module.exports = {
43
+ useInvalidateAttachmentResolutions
44
+ });
45
+ //# sourceMappingURL=use-invalidate-attachment-resolutions.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts"],
4
+ "sourcesContent": ["/**\n * Hook for invalidating all cached `getEntityRecords` resolutions for the\n * attachment post type.\n *\n * After a file upload completes the media grid needs to refresh, but\n * `invalidateResolution` only clears the exact query that is passed to it.\n * If the user is on page 2, page 1 (where the new upload would appear) stays\n * stale. Using `invalidateResolutionForStoreSelector` would work but is too\n * broad \u2014 it clears every `getEntityRecords` resolution, potentially\n * triggering unnecessary refetches for unrelated entity types.\n *\n * This hook provides a middle ground: it iterates over every cached\n * resolution for `getEntityRecords` and invalidates only the entries where\n * the first two arguments match `['postType', 'attachment']`.\n */\n\n/**\n * WordPress dependencies\n */\nimport { useCallback } from '@wordpress/element';\nimport { store as coreStore } from '@wordpress/core-data';\nimport { useRegistry } from '@wordpress/data';\n\n/**\n * Returns a stable callback that invalidates all cached `getEntityRecords`\n * resolutions for `postType / attachment`, leaving every other entity type\n * untouched.\n */\nexport function useInvalidateAttachmentResolutions() {\n\tconst registry = useRegistry();\n\n\treturn useCallback( () => {\n\t\tconst resolvers = registry.select( coreStore ).getCachedResolvers();\n\n\t\t// getCachedResolvers() is typed as Record<string, unknown> but the\n\t\t// values are EquivalentKeyMap instances (Map-like). Cast the same\n\t\t// way the resolvers-cache-middleware does internally.\n\t\tconst entityRecordResolutions = resolvers.getEntityRecords as\n\t\t\t| Map< string[], { status: string } >\n\t\t\t| undefined;\n\n\t\tentityRecordResolutions?.forEach( ( _value, args ) => {\n\t\t\tif ( args[ 0 ] === 'postType' && args[ 1 ] === 'attachment' ) {\n\t\t\t\tregistry\n\t\t\t\t\t.dispatch( coreStore )\n\t\t\t\t\t.invalidateResolution( 'getEntityRecords', args );\n\t\t\t}\n\t\t} );\n\t}, [ registry ] );\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,qBAA4B;AAC5B,uBAAmC;AACnC,kBAA4B;AAOrB,SAAS,qCAAqC;AACpD,QAAM,eAAW,yBAAY;AAE7B,aAAO,4BAAa,MAAM;AACzB,UAAM,YAAY,SAAS,OAAQ,iBAAAA,KAAU,EAAE,mBAAmB;AAKlE,UAAM,0BAA0B,UAAU;AAI1C,6BAAyB,QAAS,CAAE,QAAQ,SAAU;AACrD,UAAK,KAAM,CAAE,MAAM,cAAc,KAAM,CAAE,MAAM,cAAe;AAC7D,iBACE,SAAU,iBAAAA,KAAU,EACpB,qBAAsB,oBAAoB,IAAK;AAAA,MAClD;AAAA,IACD,CAAE;AAAA,EACH,GAAG,CAAE,QAAS,CAAE;AACjB;",
6
+ "names": ["coreStore"]
7
+ }
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // packages/media-utils/src/components/media-upload-modal/use-upload-status.ts
21
+ var use_upload_status_exports = {};
22
+ __export(use_upload_status_exports, {
23
+ useUploadStatus: () => useUploadStatus
24
+ });
25
+ module.exports = __toCommonJS(use_upload_status_exports);
26
+ var import_element = require("@wordpress/element");
27
+ var import_blob = require("@wordpress/blob");
28
+ var import_upload_error = require("../../utils/upload-error.cjs");
29
+ var idCounter = 0;
30
+ var batchIdCounter = 0;
31
+ function useUploadStatus({
32
+ onBatchComplete
33
+ } = {}) {
34
+ const [uploadingFiles, setUploadingFiles] = (0, import_element.useState)(
35
+ []
36
+ );
37
+ const clearCompleted = (0, import_element.useCallback)(() => {
38
+ setUploadingFiles(
39
+ (prev) => prev.filter((item) => item.status !== "uploaded")
40
+ );
41
+ }, []);
42
+ const dismissError = (0, import_element.useCallback)((fileId) => {
43
+ setUploadingFiles(
44
+ (prev) => prev.filter((item) => item.id !== fileId)
45
+ );
46
+ }, []);
47
+ const registerBatch = (0, import_element.useCallback)(
48
+ (files) => {
49
+ const batchId = String(++batchIdCounter);
50
+ const batchSize = files.length;
51
+ const newEntries = files.map((file) => ({
52
+ id: String(++idCounter),
53
+ batchId,
54
+ name: file.name,
55
+ status: "uploading"
56
+ }));
57
+ setUploadingFiles((prev) => [...prev, ...newEntries]);
58
+ let successCount = 0;
59
+ let errorCount = 0;
60
+ let batchDone = false;
61
+ let successAttachments = [];
62
+ const completeBatchIfDone = () => {
63
+ if (batchDone || successCount + errorCount < batchSize) {
64
+ return;
65
+ }
66
+ batchDone = true;
67
+ setUploadingFiles(
68
+ (prev) => prev.map(
69
+ (item) => item.batchId === batchId && item.status === "uploading" ? {
70
+ ...item,
71
+ status: "uploaded"
72
+ } : item
73
+ )
74
+ );
75
+ onBatchComplete?.(successAttachments);
76
+ };
77
+ const onFileChange = (attachments) => {
78
+ if (batchDone) {
79
+ return;
80
+ }
81
+ const allReal = attachments.every(
82
+ (attachment) => attachment.id && attachment.url && !(0, import_blob.isBlobURL)(attachment.url)
83
+ );
84
+ if (!allReal) {
85
+ return;
86
+ }
87
+ successCount = attachments.length;
88
+ successAttachments = attachments;
89
+ completeBatchIfDone();
90
+ };
91
+ const onError = (error) => {
92
+ const fileName = error instanceof import_upload_error.UploadError ? error.file.name : void 0;
93
+ setUploadingFiles((prev) => {
94
+ let matched = false;
95
+ return prev.map((item) => {
96
+ if (!matched && item.batchId === batchId && item.status === "uploading" && (!fileName || item.name === fileName)) {
97
+ matched = true;
98
+ return {
99
+ ...item,
100
+ status: "error",
101
+ error: error.message
102
+ };
103
+ }
104
+ return item;
105
+ });
106
+ });
107
+ errorCount++;
108
+ completeBatchIfDone();
109
+ };
110
+ return { onFileChange, onError };
111
+ },
112
+ [onBatchComplete]
113
+ );
114
+ const allComplete = uploadingFiles.length > 0 && uploadingFiles.every((item) => item.status !== "uploading");
115
+ return {
116
+ uploadingFiles,
117
+ registerBatch,
118
+ dismissError,
119
+ clearCompleted,
120
+ allComplete
121
+ };
122
+ }
123
+ // Annotate the CommonJS export names for ESM import in node:
124
+ 0 && (module.exports = {
125
+ useUploadStatus
126
+ });
127
+ //# sourceMappingURL=use-upload-status.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/components/media-upload-modal/use-upload-status.ts"],
4
+ "sourcesContent": ["/**\n * Hook for tracking media upload status with batch-scoped callbacks.\n *\n * This is a transitional layer that manually tracks upload progress using\n * local state. The @wordpress/upload-media package provides a Redux-based\n * store with richer capabilities (per-file progress, pause/resume, retry,\n * concurrency control, client-side processing). When the media upload modal\n * adopts @wordpress/upload-media, this hook can be replaced by selectors\n * from that store (getItems, isBatchUploaded, getItemProgress, etc.) while\n * keeping the same return interface.\n */\n\n/**\n * WordPress dependencies\n */\nimport { useState, useCallback } from '@wordpress/element';\nimport { isBlobURL } from '@wordpress/blob';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment } from '../../utils/types';\nimport { UploadError } from '../../utils/upload-error';\nimport type { UploadingFile } from './upload-status-popover';\n\nlet idCounter = 0;\nlet batchIdCounter = 0;\n\ninterface UseUploadStatusOptions {\n\tonBatchComplete?: ( attachments: Partial< Attachment >[] ) => void;\n}\n\ninterface RegisterBatchResult {\n\tonFileChange: ( attachments: Partial< Attachment >[] ) => void;\n\tonError: ( error: Error ) => void;\n}\n\ninterface UseUploadStatusReturn {\n\t/** Current list of all tracked files. */\n\tuploadingFiles: UploadingFile[];\n\t/**\n\t * Register a new batch of files for tracking.\n\t * Returns batch-scoped onFileChange and onError callbacks.\n\t */\n\tregisterBatch: ( files: File[] ) => RegisterBatchResult;\n\t/** Remove a single error entry by file id. */\n\tdismissError: ( fileId: string ) => void;\n\t/** Remove all uploaded (completed) entries from the list. */\n\tclearCompleted: () => void;\n\t/** True when tracked entries exist but none are still uploading. */\n\tallComplete: boolean;\n}\n\nexport function useUploadStatus( {\n\tonBatchComplete,\n}: UseUploadStatusOptions = {} ): UseUploadStatusReturn {\n\tconst [ uploadingFiles, setUploadingFiles ] = useState< UploadingFile[] >(\n\t\t[]\n\t);\n\n\tconst clearCompleted = useCallback( () => {\n\t\tsetUploadingFiles( ( prev ) =>\n\t\t\tprev.filter( ( item ) => item.status !== 'uploaded' )\n\t\t);\n\t}, [] );\n\n\tconst dismissError = useCallback( ( fileId: string ) => {\n\t\tsetUploadingFiles( ( prev ) =>\n\t\t\tprev.filter( ( item ) => item.id !== fileId )\n\t\t);\n\t}, [] );\n\n\tconst registerBatch = useCallback(\n\t\t( files: File[] ): RegisterBatchResult => {\n\t\t\tconst batchId = String( ++batchIdCounter );\n\t\t\tconst batchSize = files.length;\n\n\t\t\tconst newEntries: UploadingFile[] = files.map( ( file ) => ( {\n\t\t\t\tid: String( ++idCounter ),\n\t\t\t\tbatchId,\n\t\t\t\tname: file.name,\n\t\t\t\tstatus: 'uploading' as const,\n\t\t\t} ) );\n\n\t\t\tsetUploadingFiles( ( prev ) => [ ...prev, ...newEntries ] );\n\n\t\t\t// Track successes and errors separately. onFileChange receives\n\t\t\t// the full current array each time (not incremental), so we\n\t\t\t// store the latest count rather than accumulating. The batch\n\t\t\t// is complete when successCount + errorCount >= batchSize.\n\t\t\tlet successCount = 0;\n\t\t\tlet errorCount = 0;\n\t\t\tlet batchDone = false;\n\t\t\tlet successAttachments: Partial< Attachment >[] = [];\n\n\t\t\tconst completeBatchIfDone = () => {\n\t\t\t\tif ( batchDone || successCount + errorCount < batchSize ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tbatchDone = true;\n\n\t\t\t\t// Mark any remaining 'uploading' entries in this batch\n\t\t\t\t// as 'uploaded' (these are the successful ones).\n\t\t\t\tsetUploadingFiles( ( prev ) =>\n\t\t\t\t\tprev.map( ( item ) =>\n\t\t\t\t\t\titem.batchId === batchId && item.status === 'uploading'\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t...item,\n\t\t\t\t\t\t\t\t\tstatus: 'uploaded' as const,\n\t\t\t\t\t\t\t }\n\t\t\t\t\t\t\t: item\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\tonBatchComplete?.( successAttachments );\n\t\t\t};\n\n\t\t\tconst onFileChange = ( attachments: Partial< Attachment >[] ) => {\n\t\t\t\tif ( batchDone ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Ignore intermediate calls where some files still have\n\t\t\t\t// blob URLs (not yet uploaded to the server).\n\t\t\t\tconst allReal = attachments.every(\n\t\t\t\t\t( attachment ) =>\n\t\t\t\t\t\tattachment.id &&\n\t\t\t\t\t\tattachment.url &&\n\t\t\t\t\t\t! isBlobURL( attachment.url )\n\t\t\t\t);\n\n\t\t\t\tif ( ! allReal ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Store the latest success count and attachments.\n\t\t\t\t// onFileChange receives the full array each time, so\n\t\t\t\t// we replace rather than accumulate.\n\t\t\t\tsuccessCount = attachments.length;\n\t\t\t\tsuccessAttachments = attachments;\n\n\t\t\t\tcompleteBatchIfDone();\n\t\t\t};\n\n\t\t\tconst onError = ( error: Error ) => {\n\t\t\t\t// uploadMedia always wraps errors in UploadError, which\n\t\t\t\t// carries the originating File so we can match it back to\n\t\t\t\t// the correct item. A custom onUpload prop could pass a\n\t\t\t\t// plain Error instead \u2014 in that case fileName is undefined\n\t\t\t\t// and no entry will be visually marked as errored, but the\n\t\t\t\t// batch will still complete correctly via errorCount.\n\t\t\t\tconst fileName =\n\t\t\t\t\terror instanceof UploadError ? error.file.name : undefined;\n\n\t\t\t\t// Find the first still-uploading entry in this batch whose\n\t\t\t\t// name matches the failed file and mark it as errored.\n\t\t\t\t// Falls back to the first uploading entry in the batch\n\t\t\t\t// when no filename is available. The `matched` flag\n\t\t\t\t// ensures only one entry is updated even when duplicate\n\t\t\t\t// filenames exist in the same batch.\n\t\t\t\tsetUploadingFiles( ( prev ) => {\n\t\t\t\t\tlet matched = false;\n\t\t\t\t\treturn prev.map( ( item ) => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t! matched &&\n\t\t\t\t\t\t\titem.batchId === batchId &&\n\t\t\t\t\t\t\titem.status === 'uploading' &&\n\t\t\t\t\t\t\t( ! fileName || item.name === fileName )\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tmatched = true;\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t...item,\n\t\t\t\t\t\t\t\tstatus: 'error' as const,\n\t\t\t\t\t\t\t\terror: error.message,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn item;\n\t\t\t\t\t} );\n\t\t\t\t} );\n\n\t\t\t\terrorCount++;\n\n\t\t\t\tcompleteBatchIfDone();\n\t\t\t};\n\n\t\t\treturn { onFileChange, onError };\n\t\t},\n\t\t[ onBatchComplete ]\n\t);\n\n\tconst allComplete =\n\t\tuploadingFiles.length > 0 &&\n\t\tuploadingFiles.every( ( item ) => item.status !== 'uploading' );\n\n\treturn {\n\t\tuploadingFiles,\n\t\tregisterBatch,\n\t\tdismissError,\n\t\tclearCompleted,\n\t\tallComplete,\n\t};\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,qBAAsC;AACtC,kBAA0B;AAM1B,0BAA4B;AAG5B,IAAI,YAAY;AAChB,IAAI,iBAAiB;AA2Bd,SAAS,gBAAiB;AAAA,EAChC;AACD,IAA4B,CAAC,GAA2B;AACvD,QAAM,CAAE,gBAAgB,iBAAkB,QAAI;AAAA,IAC7C,CAAC;AAAA,EACF;AAEA,QAAM,qBAAiB,4BAAa,MAAM;AACzC;AAAA,MAAmB,CAAE,SACpB,KAAK,OAAQ,CAAE,SAAU,KAAK,WAAW,UAAW;AAAA,IACrD;AAAA,EACD,GAAG,CAAC,CAAE;AAEN,QAAM,mBAAe,4BAAa,CAAE,WAAoB;AACvD;AAAA,MAAmB,CAAE,SACpB,KAAK,OAAQ,CAAE,SAAU,KAAK,OAAO,MAAO;AAAA,IAC7C;AAAA,EACD,GAAG,CAAC,CAAE;AAEN,QAAM,oBAAgB;AAAA,IACrB,CAAE,UAAwC;AACzC,YAAM,UAAU,OAAQ,EAAE,cAAe;AACzC,YAAM,YAAY,MAAM;AAExB,YAAM,aAA8B,MAAM,IAAK,CAAE,UAAY;AAAA,QAC5D,IAAI,OAAQ,EAAE,SAAU;AAAA,QACxB;AAAA,QACA,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,MACT,EAAI;AAEJ,wBAAmB,CAAE,SAAU,CAAE,GAAG,MAAM,GAAG,UAAW,CAAE;AAM1D,UAAI,eAAe;AACnB,UAAI,aAAa;AACjB,UAAI,YAAY;AAChB,UAAI,qBAA8C,CAAC;AAEnD,YAAM,sBAAsB,MAAM;AACjC,YAAK,aAAa,eAAe,aAAa,WAAY;AACzD;AAAA,QACD;AAEA,oBAAY;AAIZ;AAAA,UAAmB,CAAE,SACpB,KAAK;AAAA,YAAK,CAAE,SACX,KAAK,YAAY,WAAW,KAAK,WAAW,cACzC;AAAA,cACA,GAAG;AAAA,cACH,QAAQ;AAAA,YACR,IACA;AAAA,UACJ;AAAA,QACD;AAEA,0BAAmB,kBAAmB;AAAA,MACvC;AAEA,YAAM,eAAe,CAAE,gBAA0C;AAChE,YAAK,WAAY;AAChB;AAAA,QACD;AAIA,cAAM,UAAU,YAAY;AAAA,UAC3B,CAAE,eACD,WAAW,MACX,WAAW,OACX,KAAE,uBAAW,WAAW,GAAI;AAAA,QAC9B;AAEA,YAAK,CAAE,SAAU;AAChB;AAAA,QACD;AAKA,uBAAe,YAAY;AAC3B,6BAAqB;AAErB,4BAAoB;AAAA,MACrB;AAEA,YAAM,UAAU,CAAE,UAAkB;AAOnC,cAAM,WACL,iBAAiB,kCAAc,MAAM,KAAK,OAAO;AAQlD,0BAAmB,CAAE,SAAU;AAC9B,cAAI,UAAU;AACd,iBAAO,KAAK,IAAK,CAAE,SAAU;AAC5B,gBACC,CAAE,WACF,KAAK,YAAY,WACjB,KAAK,WAAW,gBACd,CAAE,YAAY,KAAK,SAAS,WAC7B;AACD,wBAAU;AACV,qBAAO;AAAA,gBACN,GAAG;AAAA,gBACH,QAAQ;AAAA,gBACR,OAAO,MAAM;AAAA,cACd;AAAA,YACD;AACA,mBAAO;AAAA,UACR,CAAE;AAAA,QACH,CAAE;AAEF;AAEA,4BAAoB;AAAA,MACrB;AAEA,aAAO,EAAE,cAAc,QAAQ;AAAA,IAChC;AAAA,IACA,CAAE,eAAgB;AAAA,EACnB;AAEA,QAAM,cACL,eAAe,SAAS,KACxB,eAAe,MAAO,CAAE,SAAU,KAAK,WAAW,WAAY;AAE/D,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
6
+ "names": []
7
+ }
@@ -1,9 +1,12 @@
1
1
  // packages/media-utils/src/components/media-upload-modal/index.tsx
2
+ import clsx from "clsx";
2
3
  import {
3
4
  createPortal,
4
5
  useState,
5
6
  useCallback,
6
- useMemo
7
+ useMemo,
8
+ useRef,
9
+ useEffect
7
10
  } from "@wordpress/element";
8
11
  import { __, sprintf, _n } from "@wordpress/i18n";
9
12
  import {
@@ -14,6 +17,7 @@ import { resolveSelect, useDispatch } from "@wordpress/data";
14
17
  import { Modal, DropZone, FormFileUpload, Button } from "@wordpress/components";
15
18
  import { upload as uploadIcon } from "@wordpress/icons";
16
19
  import { DataViewsPicker } from "@wordpress/dataviews";
20
+ import { Stack } from "@wordpress/ui";
17
21
  import {
18
22
  altTextField,
19
23
  attachedToField,
@@ -29,10 +33,12 @@ import {
29
33
  mimeTypeField
30
34
  } from "@wordpress/media-fields";
31
35
  import { store as noticesStore, SnackbarNotices } from "@wordpress/notices";
32
- import { isBlobURL } from "@wordpress/blob";
33
36
  import { transformAttachment } from "../../utils/transform-attachment.mjs";
34
37
  import { uploadMedia } from "../../utils/upload-media.mjs";
35
38
  import { unlock } from "../../lock-unlock.mjs";
39
+ import { UploadStatusPopover } from "./upload-status-popover.mjs";
40
+ import { useInvalidateAttachmentResolutions } from "./use-invalidate-attachment-resolutions.mjs";
41
+ import { useUploadStatus } from "./use-upload-status.mjs";
36
42
  import { jsx, jsxs } from "react/jsx-runtime";
37
43
  var { useEntityRecordsWithPermissions } = unlock(coreDataPrivateApis);
38
44
  var LAYOUT_PICKER_GRID = "pickerGrid";
@@ -59,8 +65,8 @@ function MediaUploadModal({
59
65
  }
60
66
  return Array.isArray(value) ? value.map(String) : [String(value)];
61
67
  });
62
- const { createSuccessNotice, createErrorNotice, createInfoNotice } = useDispatch(noticesStore);
63
- const { invalidateResolution } = useDispatch(coreStore);
68
+ const { createSuccessNotice, removeAllNotices } = useDispatch(noticesStore);
69
+ const invalidateAttachmentResolutions = useInvalidateAttachmentResolutions();
64
70
  const [view, setView] = useState(() => ({
65
71
  type: LAYOUT_PICKER_GRID,
66
72
  fields: [],
@@ -114,6 +120,41 @@ function MediaUploadModal({
114
120
  ...filters
115
121
  };
116
122
  }, [view, allowedTypes]);
123
+ const handleBatchComplete = useCallback(
124
+ (attachments) => {
125
+ const uploadedIds = attachments.map((attachment) => String(attachment.id)).filter(Boolean);
126
+ if (multiple) {
127
+ setSelection((prev) => {
128
+ const existing = new Set(prev);
129
+ const newIds = uploadedIds.filter(
130
+ (id) => !existing.has(id)
131
+ );
132
+ return [...prev, ...newIds];
133
+ });
134
+ } else {
135
+ setSelection(uploadedIds.slice(0, 1));
136
+ }
137
+ invalidateAttachmentResolutions();
138
+ },
139
+ [multiple, invalidateAttachmentResolutions]
140
+ );
141
+ const {
142
+ uploadingFiles,
143
+ registerBatch,
144
+ dismissError,
145
+ clearCompleted,
146
+ allComplete
147
+ } = useUploadStatus({ onBatchComplete: handleBatchComplete });
148
+ const isPopoverOpenRef = useRef(false);
149
+ const handlePopoverOpenChange = useCallback(
150
+ (open) => {
151
+ isPopoverOpenRef.current = open;
152
+ if (!open) {
153
+ clearCompleted();
154
+ }
155
+ },
156
+ [clearCompleted]
157
+ );
117
158
  const {
118
159
  records: mediaRecords,
119
160
  isResolving: isLoading,
@@ -156,7 +197,7 @@ function MediaUploadModal({
156
197
  () => [
157
198
  {
158
199
  id: "select",
159
- label: multiple ? __("Select") : __("Select"),
200
+ label: __("Select"),
160
201
  isPrimary: true,
161
202
  supportsBulk: multiple,
162
203
  async callback() {
@@ -176,31 +217,34 @@ function MediaUploadModal({
176
217
  );
177
218
  const transformedPosts = (selectedPosts ?? []).map(transformAttachment).filter(Boolean);
178
219
  const selectedItems = multiple ? transformedPosts : transformedPosts?.[0];
220
+ removeAllNotices("snackbar", NOTICES_CONTEXT);
179
221
  onSelect(selectedItems);
180
222
  }
181
223
  }
182
224
  ],
183
- [multiple, onSelect, selection]
225
+ [multiple, onSelect, selection, removeAllNotices]
184
226
  );
185
227
  const handleModalClose = useCallback(() => {
228
+ removeAllNotices("snackbar", NOTICES_CONTEXT);
186
229
  onClose?.();
187
- }, [onClose]);
230
+ }, [removeAllNotices, onClose]);
188
231
  const handleUpload = onUpload || uploadMedia;
189
- const handleUploadComplete = useCallback(
190
- (attachments) => {
191
- const allComplete = attachments.every(
192
- (attachment) => attachment.id && attachment.url && !isBlobURL(attachment.url)
193
- );
194
- if (allComplete && attachments.length > 0) {
232
+ const prevAllCompleteRef = useRef(false);
233
+ useEffect(() => {
234
+ if (allComplete && !prevAllCompleteRef.current) {
235
+ const completeCount = uploadingFiles.filter(
236
+ (file) => file.status === "uploaded"
237
+ ).length;
238
+ if (completeCount > 0) {
195
239
  createSuccessNotice(
196
240
  sprintf(
197
241
  // translators: %s: number of files
198
242
  _n(
199
243
  "Uploaded %s file",
200
244
  "Uploaded %s files",
201
- attachments.length
245
+ completeCount
202
246
  ),
203
- attachments.length.toLocaleString()
247
+ completeCount.toLocaleString()
204
248
  ),
205
249
  {
206
250
  type: "snackbar",
@@ -208,68 +252,28 @@ function MediaUploadModal({
208
252
  id: NOTICE_ID_UPLOAD_PROGRESS
209
253
  }
210
254
  );
211
- const uploadedIds = attachments.map((attachment) => String(attachment.id)).filter(Boolean);
212
- if (multiple) {
213
- setSelection((prev) => [...prev, ...uploadedIds]);
214
- } else {
215
- setSelection(uploadedIds.slice(0, 1));
216
- }
217
- invalidateResolution("getEntityRecords", [
218
- "postType",
219
- "attachment",
220
- queryArgs
221
- ]);
222
255
  }
223
- },
224
- [createSuccessNotice, invalidateResolution, queryArgs, multiple]
225
- );
226
- const handleUploadError = useCallback(
227
- (error) => {
228
- createErrorNotice(error.message, {
229
- type: "snackbar",
230
- context: NOTICES_CONTEXT,
231
- id: NOTICE_ID_UPLOAD_PROGRESS
232
- });
233
- },
234
- [createErrorNotice]
235
- );
256
+ if (!isPopoverOpenRef.current) {
257
+ clearCompleted();
258
+ }
259
+ }
260
+ prevAllCompleteRef.current = allComplete;
261
+ }, [allComplete, uploadingFiles, createSuccessNotice, clearCompleted]);
236
262
  const handleFileSelect = useCallback(
237
263
  (event) => {
238
264
  const files = event.target.files;
239
265
  if (files && files.length > 0) {
240
266
  const filesArray = Array.from(files);
241
- createInfoNotice(
242
- sprintf(
243
- // translators: %s: number of files
244
- _n(
245
- "Uploading %s file",
246
- "Uploading %s files",
247
- filesArray.length
248
- ),
249
- filesArray.length.toLocaleString()
250
- ),
251
- {
252
- type: "snackbar",
253
- context: NOTICES_CONTEXT,
254
- id: NOTICE_ID_UPLOAD_PROGRESS,
255
- explicitDismiss: true
256
- }
257
- );
267
+ const { onFileChange, onError } = registerBatch(filesArray);
258
268
  handleUpload({
259
269
  allowedTypes,
260
270
  filesList: filesArray,
261
- onFileChange: handleUploadComplete,
262
- onError: handleUploadError
271
+ onFileChange,
272
+ onError
263
273
  });
264
274
  }
265
275
  },
266
- [
267
- allowedTypes,
268
- handleUpload,
269
- createInfoNotice,
270
- handleUploadComplete,
271
- handleUploadError
272
- ]
276
+ [allowedTypes, handleUpload, registerBatch]
273
277
  );
274
278
  const paginationInfo = useMemo(
275
279
  () => ({
@@ -349,35 +353,19 @@ function MediaUploadModal({
349
353
  );
350
354
  }
351
355
  if (filteredFiles.length > 0) {
352
- createInfoNotice(
353
- sprintf(
354
- // translators: %s: number of files
355
- _n(
356
- "Uploading %s file",
357
- "Uploading %s files",
358
- filteredFiles.length
359
- ),
360
- filteredFiles.length.toLocaleString()
361
- ),
362
- {
363
- type: "snackbar",
364
- context: NOTICES_CONTEXT,
365
- id: NOTICE_ID_UPLOAD_PROGRESS,
366
- explicitDismiss: true
367
- }
368
- );
356
+ const { onFileChange, onError } = registerBatch(filteredFiles);
369
357
  handleUpload({
370
358
  allowedTypes,
371
359
  filesList: filteredFiles,
372
- onFileChange: handleUploadComplete,
373
- onError: handleUploadError
360
+ onFileChange,
361
+ onError
374
362
  });
375
363
  }
376
364
  },
377
365
  label: __("Drop files to upload")
378
366
  }
379
367
  ),
380
- /* @__PURE__ */ jsx(
368
+ /* @__PURE__ */ jsxs(
381
369
  DataViewsPicker,
382
370
  {
383
371
  data: mediaRecords || [],
@@ -391,9 +379,59 @@ function MediaUploadModal({
391
379
  paginationInfo,
392
380
  defaultLayouts,
393
381
  getItemId: (item) => String(item.id),
394
- search,
395
- searchLabel,
396
- itemListLabel: __("Media items")
382
+ itemListLabel: __("Media items"),
383
+ children: [
384
+ /* @__PURE__ */ jsxs(
385
+ Stack,
386
+ {
387
+ direction: "row",
388
+ align: "top",
389
+ justify: "space-between",
390
+ className: "dataviews__view-actions",
391
+ gap: "xs",
392
+ children: [
393
+ /* @__PURE__ */ jsxs(
394
+ Stack,
395
+ {
396
+ direction: "row",
397
+ gap: "sm",
398
+ justify: "start",
399
+ className: "dataviews__search",
400
+ children: [
401
+ search && /* @__PURE__ */ jsx(DataViewsPicker.Search, { label: searchLabel }),
402
+ /* @__PURE__ */ jsx(DataViewsPicker.FiltersToggle, {})
403
+ ]
404
+ }
405
+ ),
406
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "xs", style: { flexShrink: 0 }, children: [
407
+ /* @__PURE__ */ jsx(DataViewsPicker.LayoutSwitcher, {}),
408
+ /* @__PURE__ */ jsx(DataViewsPicker.ViewConfig, {})
409
+ ] })
410
+ ]
411
+ }
412
+ ),
413
+ /* @__PURE__ */ jsx(DataViewsPicker.FiltersToggled, { className: "dataviews-filters__container" }),
414
+ /* @__PURE__ */ jsx(DataViewsPicker.Layout, {}),
415
+ /* @__PURE__ */ jsxs(
416
+ "div",
417
+ {
418
+ className: clsx("media-upload-modal__footer", {
419
+ "is-uploading": uploadingFiles.length > 0
420
+ }),
421
+ children: [
422
+ /* @__PURE__ */ jsx(
423
+ UploadStatusPopover,
424
+ {
425
+ uploadingFiles,
426
+ onDismissError: dismissError,
427
+ onOpenChange: handlePopoverOpenChange
428
+ }
429
+ ),
430
+ /* @__PURE__ */ jsx(DataViewsPicker.BulkActionToolbar, {})
431
+ ]
432
+ }
433
+ )
434
+ ]
397
435
  }
398
436
  ),
399
437
  createPortal(