@wordpress/media-utils 5.41.1-next.v.202603102151.0 → 5.42.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 +2 -0
  2. package/build/components/media-upload-modal/index.cjs +136 -89
  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 +129 -90
  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 +131 -108
  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: [],
@@ -69,10 +75,11 @@ function MediaUploadModal({
69
75
  mediaField: "media_thumbnail",
70
76
  search: "",
71
77
  page: 1,
72
- perPage: 20,
78
+ perPage: 50,
73
79
  filters: [],
74
80
  layout: {
75
- previewSize: 170
81
+ previewSize: 170,
82
+ density: "compact"
76
83
  }
77
84
  }));
78
85
  const queryArgs = useMemo(() => {
@@ -113,6 +120,41 @@ function MediaUploadModal({
113
120
  ...filters
114
121
  };
115
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
+ );
116
158
  const {
117
159
  records: mediaRecords,
118
160
  isResolving: isLoading,
@@ -155,7 +197,7 @@ function MediaUploadModal({
155
197
  () => [
156
198
  {
157
199
  id: "select",
158
- label: multiple ? __("Select") : __("Select"),
200
+ label: __("Select"),
159
201
  isPrimary: true,
160
202
  supportsBulk: multiple,
161
203
  async callback() {
@@ -175,31 +217,34 @@ function MediaUploadModal({
175
217
  );
176
218
  const transformedPosts = (selectedPosts ?? []).map(transformAttachment).filter(Boolean);
177
219
  const selectedItems = multiple ? transformedPosts : transformedPosts?.[0];
220
+ removeAllNotices("snackbar", NOTICES_CONTEXT);
178
221
  onSelect(selectedItems);
179
222
  }
180
223
  }
181
224
  ],
182
- [multiple, onSelect, selection]
225
+ [multiple, onSelect, selection, removeAllNotices]
183
226
  );
184
227
  const handleModalClose = useCallback(() => {
228
+ removeAllNotices("snackbar", NOTICES_CONTEXT);
185
229
  onClose?.();
186
- }, [onClose]);
230
+ }, [removeAllNotices, onClose]);
187
231
  const handleUpload = onUpload || uploadMedia;
188
- const handleUploadComplete = useCallback(
189
- (attachments) => {
190
- const allComplete = attachments.every(
191
- (attachment) => attachment.id && attachment.url && !isBlobURL(attachment.url)
192
- );
193
- 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) {
194
239
  createSuccessNotice(
195
240
  sprintf(
196
241
  // translators: %s: number of files
197
242
  _n(
198
243
  "Uploaded %s file",
199
244
  "Uploaded %s files",
200
- attachments.length
245
+ completeCount
201
246
  ),
202
- attachments.length.toLocaleString()
247
+ completeCount.toLocaleString()
203
248
  ),
204
249
  {
205
250
  type: "snackbar",
@@ -207,68 +252,28 @@ function MediaUploadModal({
207
252
  id: NOTICE_ID_UPLOAD_PROGRESS
208
253
  }
209
254
  );
210
- const uploadedIds = attachments.map((attachment) => String(attachment.id)).filter(Boolean);
211
- if (multiple) {
212
- setSelection((prev) => [...prev, ...uploadedIds]);
213
- } else {
214
- setSelection(uploadedIds.slice(0, 1));
215
- }
216
- invalidateResolution("getEntityRecords", [
217
- "postType",
218
- "attachment",
219
- queryArgs
220
- ]);
221
255
  }
222
- },
223
- [createSuccessNotice, invalidateResolution, queryArgs, multiple]
224
- );
225
- const handleUploadError = useCallback(
226
- (error) => {
227
- createErrorNotice(error.message, {
228
- type: "snackbar",
229
- context: NOTICES_CONTEXT,
230
- id: NOTICE_ID_UPLOAD_PROGRESS
231
- });
232
- },
233
- [createErrorNotice]
234
- );
256
+ if (!isPopoverOpenRef.current) {
257
+ clearCompleted();
258
+ }
259
+ }
260
+ prevAllCompleteRef.current = allComplete;
261
+ }, [allComplete, uploadingFiles, createSuccessNotice, clearCompleted]);
235
262
  const handleFileSelect = useCallback(
236
263
  (event) => {
237
264
  const files = event.target.files;
238
265
  if (files && files.length > 0) {
239
266
  const filesArray = Array.from(files);
240
- createInfoNotice(
241
- sprintf(
242
- // translators: %s: number of files
243
- _n(
244
- "Uploading %s file",
245
- "Uploading %s files",
246
- filesArray.length
247
- ),
248
- filesArray.length.toLocaleString()
249
- ),
250
- {
251
- type: "snackbar",
252
- context: NOTICES_CONTEXT,
253
- id: NOTICE_ID_UPLOAD_PROGRESS,
254
- explicitDismiss: true
255
- }
256
- );
267
+ const { onFileChange, onError } = registerBatch(filesArray);
257
268
  handleUpload({
258
269
  allowedTypes,
259
270
  filesList: filesArray,
260
- onFileChange: handleUploadComplete,
261
- onError: handleUploadError
271
+ onFileChange,
272
+ onError
262
273
  });
263
274
  }
264
275
  },
265
- [
266
- allowedTypes,
267
- handleUpload,
268
- createInfoNotice,
269
- handleUploadComplete,
270
- handleUploadError
271
- ]
276
+ [allowedTypes, handleUpload, registerBatch]
272
277
  );
273
278
  const paginationInfo = useMemo(
274
279
  () => ({
@@ -348,35 +353,19 @@ function MediaUploadModal({
348
353
  );
349
354
  }
350
355
  if (filteredFiles.length > 0) {
351
- createInfoNotice(
352
- sprintf(
353
- // translators: %s: number of files
354
- _n(
355
- "Uploading %s file",
356
- "Uploading %s files",
357
- filteredFiles.length
358
- ),
359
- filteredFiles.length.toLocaleString()
360
- ),
361
- {
362
- type: "snackbar",
363
- context: NOTICES_CONTEXT,
364
- id: NOTICE_ID_UPLOAD_PROGRESS,
365
- explicitDismiss: true
366
- }
367
- );
356
+ const { onFileChange, onError } = registerBatch(filteredFiles);
368
357
  handleUpload({
369
358
  allowedTypes,
370
359
  filesList: filteredFiles,
371
- onFileChange: handleUploadComplete,
372
- onError: handleUploadError
360
+ onFileChange,
361
+ onError
373
362
  });
374
363
  }
375
364
  },
376
365
  label: __("Drop files to upload")
377
366
  }
378
367
  ),
379
- /* @__PURE__ */ jsx(
368
+ /* @__PURE__ */ jsxs(
380
369
  DataViewsPicker,
381
370
  {
382
371
  data: mediaRecords || [],
@@ -390,9 +379,59 @@ function MediaUploadModal({
390
379
  paginationInfo,
391
380
  defaultLayouts,
392
381
  getItemId: (item) => String(item.id),
393
- search,
394
- searchLabel,
395
- 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
+ ]
396
435
  }
397
436
  ),
398
437
  createPortal(