@wordpress/fields 0.29.0 → 0.29.1-next.06ee73755.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 (282) hide show
  1. package/CHANGELOG.md +0 -2
  2. package/LICENSE.md +1 -1
  3. package/README.md +41 -0
  4. package/build/actions/{delete-post.js → delete-post.cjs} +4 -4
  5. package/build/actions/{duplicate-pattern.js → duplicate-pattern.cjs} +2 -2
  6. package/build/actions/{duplicate-post.js → duplicate-post.cjs} +2 -2
  7. package/build/actions/{duplicate-template-part.js → duplicate-template-part.cjs} +3 -3
  8. package/build/actions/{export-pattern.js → export-pattern.cjs} +2 -2
  9. package/build/actions/{index.js → index.cjs} +14 -14
  10. package/build/actions/{permanently-delete-post.js → permanently-delete-post.cjs} +2 -2
  11. package/build/actions/{rename-post.js → rename-post.cjs} +3 -4
  12. package/build/actions/{rename-post.js.map → rename-post.cjs.map} +2 -2
  13. package/build/actions/{reorder-page.js → reorder-page.cjs} +2 -2
  14. package/build/actions/reorder-page.cjs.map +7 -0
  15. package/build/actions/{reset-post.js → reset-post.cjs} +2 -2
  16. package/build/actions/{restore-post.js → restore-post.cjs} +2 -2
  17. package/build/actions/{trash-post.js → trash-post.cjs} +2 -2
  18. package/build/actions/{utils.js → utils.cjs} +1 -1
  19. package/build/actions/{view-post-revisions.js → view-post-revisions.cjs} +1 -1
  20. package/build/actions/{view-post.js → view-post.cjs} +1 -1
  21. package/build/components/create-template-part-modal/{index.js → index.cjs} +3 -4
  22. package/build/components/create-template-part-modal/index.cjs.map +7 -0
  23. package/build/components/create-template-part-modal/{utils.js → utils.cjs} +1 -1
  24. package/build/components/media-edit/index.cjs +494 -0
  25. package/build/components/media-edit/index.cjs.map +7 -0
  26. package/build/fields/author/{author-view.js → author-view.cjs} +1 -1
  27. package/build/fields/author/{index.js → index.cjs} +2 -2
  28. package/build/fields/comment-status/{index.js → index.cjs} +1 -1
  29. package/build/fields/date/{date-view.js → date-view.cjs} +1 -1
  30. package/build/fields/date/{index.js → index.cjs} +2 -2
  31. package/build/fields/discussion/{index.js → index.cjs} +1 -1
  32. package/build/fields/featured-image/{featured-image-view.js → featured-image-view.cjs} +1 -1
  33. package/build/fields/featured-image/index.cjs +52 -0
  34. package/build/fields/featured-image/index.cjs.map +7 -0
  35. package/build/fields/{index.js → index.cjs} +18 -18
  36. package/build/fields/notes/{index.js → index.cjs} +1 -1
  37. package/build/fields/order/{index.js → index.cjs} +1 -1
  38. package/build/fields/page-title/{index.js → index.cjs} +3 -3
  39. package/build/fields/page-title/{view.js → view.cjs} +3 -3
  40. package/build/fields/parent/{index.js → index.cjs} +3 -3
  41. package/build/fields/parent/{parent-edit.js → parent-edit.cjs} +2 -3
  42. package/build/fields/parent/{parent-edit.js.map → parent-edit.cjs.map} +2 -2
  43. package/build/fields/parent/{parent-view.js → parent-view.cjs} +2 -2
  44. package/build/fields/parent/{utils.js → utils.cjs} +1 -1
  45. package/build/fields/password/{edit.js → edit.cjs} +1 -3
  46. package/build/fields/password/{edit.js.map → edit.cjs.map} +2 -2
  47. package/build/fields/password/{index.js → index.cjs} +2 -2
  48. package/build/fields/pattern-title/{index.js → index.cjs} +3 -3
  49. package/build/fields/pattern-title/{view.js → view.cjs} +3 -3
  50. package/build/fields/ping-status/{index.js → index.cjs} +1 -2
  51. package/build/fields/ping-status/index.cjs.map +7 -0
  52. package/build/fields/slug/{index.js → index.cjs} +3 -3
  53. package/build/fields/slug/{slug-edit.js → slug-edit.cjs} +2 -2
  54. package/build/fields/slug/{slug-view.js → slug-view.cjs} +2 -2
  55. package/build/fields/slug/{utils.js → utils.cjs} +2 -2
  56. package/build/fields/status/{index.js → index.cjs} +3 -3
  57. package/build/fields/status/{status-elements.js → status-elements.cjs} +1 -1
  58. package/build/fields/status/{status-view.js → status-view.cjs} +2 -2
  59. package/build/fields/template/{index.js → index.cjs} +2 -2
  60. package/build/fields/template/{template-edit.js → template-edit.cjs} +3 -3
  61. package/build/fields/template-title/{index.js → index.cjs} +3 -3
  62. package/build/fields/title/{index.js → index.cjs} +3 -3
  63. package/build/fields/title/{view.js → view.cjs} +2 -2
  64. package/build/{index.js → index.cjs} +10 -7
  65. package/build/index.cjs.map +7 -0
  66. package/build/{lock-unlock.js → lock-unlock.cjs} +1 -1
  67. package/build/mutation/{index.js → index.cjs} +1 -1
  68. package/build/{types.js → types.cjs} +1 -1
  69. package/build/types.cjs.map +7 -0
  70. package/build-module/actions/{delete-post.js → delete-post.mjs} +4 -4
  71. package/build-module/actions/{duplicate-pattern.js → duplicate-pattern.mjs} +2 -2
  72. package/build-module/actions/{duplicate-post.js → duplicate-post.mjs} +2 -2
  73. package/build-module/actions/{duplicate-template-part.js → duplicate-template-part.mjs} +3 -3
  74. package/build-module/actions/{export-pattern.js → export-pattern.mjs} +2 -2
  75. package/build-module/actions/index.mjs +30 -0
  76. package/build-module/actions/{permanently-delete-post.js → permanently-delete-post.mjs} +2 -2
  77. package/build-module/actions/{rename-post.js → rename-post.mjs} +3 -4
  78. package/build-module/actions/{rename-post.js.map → rename-post.mjs.map} +2 -2
  79. package/build-module/actions/{reorder-page.js → reorder-page.mjs} +2 -2
  80. package/build-module/actions/reorder-page.mjs.map +7 -0
  81. package/build-module/actions/{reset-post.js → reset-post.mjs} +2 -2
  82. package/build-module/actions/{restore-post.js → restore-post.mjs} +2 -2
  83. package/build-module/actions/{trash-post.js → trash-post.mjs} +2 -2
  84. package/build-module/actions/{utils.js → utils.mjs} +1 -1
  85. package/build-module/actions/{view-post-revisions.js → view-post-revisions.mjs} +1 -1
  86. package/build-module/actions/{view-post.js → view-post.mjs} +1 -1
  87. package/build-module/components/create-template-part-modal/{index.js → index.mjs} +3 -4
  88. package/build-module/components/create-template-part-modal/index.mjs.map +7 -0
  89. package/build-module/components/create-template-part-modal/{utils.js → utils.mjs} +1 -1
  90. package/build-module/components/media-edit/index.mjs +478 -0
  91. package/build-module/components/media-edit/index.mjs.map +7 -0
  92. package/build-module/fields/author/{author-view.js → author-view.mjs} +1 -1
  93. package/build-module/fields/author/{index.js → index.mjs} +2 -2
  94. package/build-module/fields/comment-status/{index.js → index.mjs} +1 -1
  95. package/build-module/fields/date/{date-view.js → date-view.mjs} +1 -1
  96. package/build-module/fields/date/{index.js → index.mjs} +2 -2
  97. package/build-module/fields/discussion/{index.js → index.mjs} +1 -1
  98. package/build-module/fields/featured-image/{featured-image-view.js → featured-image-view.mjs} +1 -1
  99. package/build-module/fields/featured-image/{index.js → index.mjs} +8 -5
  100. package/build-module/fields/featured-image/index.mjs.map +7 -0
  101. package/build-module/fields/index.mjs +38 -0
  102. package/build-module/fields/notes/{index.js → index.mjs} +1 -1
  103. package/build-module/fields/order/{index.js → index.mjs} +1 -1
  104. package/build-module/fields/page-title/{index.js → index.mjs} +3 -3
  105. package/build-module/fields/page-title/{view.js → view.mjs} +3 -3
  106. package/build-module/fields/parent/{index.js → index.mjs} +3 -3
  107. package/build-module/fields/parent/{parent-edit.js → parent-edit.mjs} +2 -3
  108. package/build-module/fields/parent/{parent-edit.js.map → parent-edit.mjs.map} +2 -2
  109. package/build-module/fields/parent/{parent-view.js → parent-view.mjs} +2 -2
  110. package/build-module/fields/parent/{utils.js → utils.mjs} +1 -1
  111. package/build-module/fields/password/{edit.js → edit.mjs} +1 -3
  112. package/build-module/fields/password/{edit.js.map → edit.mjs.map} +2 -2
  113. package/build-module/fields/password/{index.js → index.mjs} +2 -2
  114. package/build-module/fields/pattern-title/{index.js → index.mjs} +3 -3
  115. package/build-module/fields/pattern-title/{view.js → view.mjs} +3 -3
  116. package/build-module/fields/ping-status/{index.js → index.mjs} +1 -2
  117. package/build-module/fields/ping-status/index.mjs.map +7 -0
  118. package/build-module/fields/slug/{index.js → index.mjs} +3 -3
  119. package/build-module/fields/slug/{slug-edit.js → slug-edit.mjs} +2 -2
  120. package/build-module/fields/slug/{slug-view.js → slug-view.mjs} +2 -2
  121. package/build-module/fields/slug/{utils.js → utils.mjs} +2 -2
  122. package/build-module/fields/status/{index.js → index.mjs} +3 -3
  123. package/build-module/fields/status/{status-elements.js → status-elements.mjs} +1 -1
  124. package/build-module/fields/status/{status-view.js → status-view.mjs} +2 -2
  125. package/build-module/fields/template/{index.js → index.mjs} +2 -2
  126. package/build-module/fields/template/{template-edit.js → template-edit.mjs} +3 -3
  127. package/build-module/fields/template-title/{index.js → index.mjs} +3 -3
  128. package/build-module/fields/title/{index.js → index.mjs} +3 -3
  129. package/build-module/fields/title/{view.js → view.mjs} +2 -2
  130. package/build-module/index.mjs +10 -0
  131. package/build-module/index.mjs.map +7 -0
  132. package/build-module/{lock-unlock.js → lock-unlock.mjs} +1 -1
  133. package/build-module/mutation/{index.js → index.mjs} +1 -1
  134. package/build-module/types.mjs +1 -0
  135. package/build-style/style-rtl.css +191 -62
  136. package/build-style/style.css +191 -62
  137. package/build-types/actions/rename-post.d.ts.map +1 -1
  138. package/build-types/actions/reorder-page.d.ts.map +1 -1
  139. package/build-types/components/create-template-part-modal/index.d.ts.map +1 -1
  140. package/build-types/components/media-edit/index.d.ts +42 -0
  141. package/build-types/components/media-edit/index.d.ts.map +1 -0
  142. package/build-types/fields/featured-image/index.d.ts +0 -3
  143. package/build-types/fields/featured-image/index.d.ts.map +1 -1
  144. package/build-types/fields/parent/parent-edit.d.ts.map +1 -1
  145. package/build-types/fields/password/edit.d.ts.map +1 -1
  146. package/build-types/fields/ping-status/index.d.ts.map +1 -1
  147. package/build-types/index.d.ts +2 -1
  148. package/build-types/index.d.ts.map +1 -1
  149. package/build-types/types.d.ts +24 -0
  150. package/build-types/types.d.ts.map +1 -1
  151. package/package.json +38 -30
  152. package/src/actions/rename-post.tsx +0 -1
  153. package/src/actions/reorder-page.tsx +1 -2
  154. package/src/components/create-template-part-modal/index.tsx +2 -3
  155. package/src/components/media-edit/index.tsx +616 -0
  156. package/src/components/media-edit/style.scss +232 -0
  157. package/src/fields/featured-image/{index.ts → index.tsx} +5 -2
  158. package/src/fields/featured-image/style.scss +7 -89
  159. package/src/fields/parent/parent-edit.tsx +0 -1
  160. package/src/fields/password/edit.tsx +0 -2
  161. package/src/fields/ping-status/index.tsx +0 -1
  162. package/src/index.ts +2 -0
  163. package/src/style.scss +1 -0
  164. package/src/types.ts +30 -0
  165. package/build/actions/reorder-page.js.map +0 -7
  166. package/build/components/create-template-part-modal/index.js.map +0 -7
  167. package/build/fields/featured-image/featured-image-edit.js +0 -162
  168. package/build/fields/featured-image/featured-image-edit.js.map +0 -7
  169. package/build/fields/featured-image/index.js +0 -39
  170. package/build/fields/featured-image/index.js.map +0 -7
  171. package/build/fields/ping-status/index.js.map +0 -7
  172. package/build/index.js.map +0 -7
  173. package/build/types.js.map +0 -7
  174. package/build-module/actions/index.js +0 -30
  175. package/build-module/actions/reorder-page.js.map +0 -7
  176. package/build-module/components/create-template-part-modal/index.js.map +0 -7
  177. package/build-module/fields/featured-image/featured-image-edit.js +0 -140
  178. package/build-module/fields/featured-image/featured-image-edit.js.map +0 -7
  179. package/build-module/fields/featured-image/index.js.map +0 -7
  180. package/build-module/fields/index.js +0 -38
  181. package/build-module/fields/ping-status/index.js.map +0 -7
  182. package/build-module/index.js +0 -8
  183. package/build-module/index.js.map +0 -7
  184. package/build-module/types.js +0 -1
  185. package/build-types/fields/featured-image/featured-image-edit.d.ts +0 -7
  186. package/build-types/fields/featured-image/featured-image-edit.d.ts.map +0 -1
  187. package/src/fields/featured-image/featured-image-edit.tsx +0 -170
  188. package/tsconfig.json +0 -31
  189. package/tsconfig.tsbuildinfo +0 -1
  190. /package/build/actions/{delete-post.js.map → delete-post.cjs.map} +0 -0
  191. /package/build/actions/{duplicate-pattern.js.map → duplicate-pattern.cjs.map} +0 -0
  192. /package/build/actions/{duplicate-post.js.map → duplicate-post.cjs.map} +0 -0
  193. /package/build/actions/{duplicate-template-part.js.map → duplicate-template-part.cjs.map} +0 -0
  194. /package/build/actions/{export-pattern.js.map → export-pattern.cjs.map} +0 -0
  195. /package/build/actions/{index.js.map → index.cjs.map} +0 -0
  196. /package/build/actions/{permanently-delete-post.js.map → permanently-delete-post.cjs.map} +0 -0
  197. /package/build/actions/{reset-post.js.map → reset-post.cjs.map} +0 -0
  198. /package/build/actions/{restore-post.js.map → restore-post.cjs.map} +0 -0
  199. /package/build/actions/{trash-post.js.map → trash-post.cjs.map} +0 -0
  200. /package/build/actions/{utils.js.map → utils.cjs.map} +0 -0
  201. /package/build/actions/{view-post-revisions.js.map → view-post-revisions.cjs.map} +0 -0
  202. /package/build/actions/{view-post.js.map → view-post.cjs.map} +0 -0
  203. /package/build/components/create-template-part-modal/{utils.js.map → utils.cjs.map} +0 -0
  204. /package/build/fields/author/{author-view.js.map → author-view.cjs.map} +0 -0
  205. /package/build/fields/author/{index.js.map → index.cjs.map} +0 -0
  206. /package/build/fields/comment-status/{index.js.map → index.cjs.map} +0 -0
  207. /package/build/fields/date/{date-view.js.map → date-view.cjs.map} +0 -0
  208. /package/build/fields/date/{index.js.map → index.cjs.map} +0 -0
  209. /package/build/fields/discussion/{index.js.map → index.cjs.map} +0 -0
  210. /package/build/fields/featured-image/{featured-image-view.js.map → featured-image-view.cjs.map} +0 -0
  211. /package/build/fields/{index.js.map → index.cjs.map} +0 -0
  212. /package/build/fields/notes/{index.js.map → index.cjs.map} +0 -0
  213. /package/build/fields/order/{index.js.map → index.cjs.map} +0 -0
  214. /package/build/fields/page-title/{index.js.map → index.cjs.map} +0 -0
  215. /package/build/fields/page-title/{view.js.map → view.cjs.map} +0 -0
  216. /package/build/fields/parent/{index.js.map → index.cjs.map} +0 -0
  217. /package/build/fields/parent/{parent-view.js.map → parent-view.cjs.map} +0 -0
  218. /package/build/fields/parent/{utils.js.map → utils.cjs.map} +0 -0
  219. /package/build/fields/password/{index.js.map → index.cjs.map} +0 -0
  220. /package/build/fields/pattern-title/{index.js.map → index.cjs.map} +0 -0
  221. /package/build/fields/pattern-title/{view.js.map → view.cjs.map} +0 -0
  222. /package/build/fields/slug/{index.js.map → index.cjs.map} +0 -0
  223. /package/build/fields/slug/{slug-edit.js.map → slug-edit.cjs.map} +0 -0
  224. /package/build/fields/slug/{slug-view.js.map → slug-view.cjs.map} +0 -0
  225. /package/build/fields/slug/{utils.js.map → utils.cjs.map} +0 -0
  226. /package/build/fields/status/{index.js.map → index.cjs.map} +0 -0
  227. /package/build/fields/status/{status-elements.js.map → status-elements.cjs.map} +0 -0
  228. /package/build/fields/status/{status-view.js.map → status-view.cjs.map} +0 -0
  229. /package/build/fields/template/{index.js.map → index.cjs.map} +0 -0
  230. /package/build/fields/template/{template-edit.js.map → template-edit.cjs.map} +0 -0
  231. /package/build/fields/template-title/{index.js.map → index.cjs.map} +0 -0
  232. /package/build/fields/title/{index.js.map → index.cjs.map} +0 -0
  233. /package/build/fields/title/{view.js.map → view.cjs.map} +0 -0
  234. /package/build/{lock-unlock.js.map → lock-unlock.cjs.map} +0 -0
  235. /package/build/mutation/{index.js.map → index.cjs.map} +0 -0
  236. /package/build-module/actions/{delete-post.js.map → delete-post.mjs.map} +0 -0
  237. /package/build-module/actions/{duplicate-pattern.js.map → duplicate-pattern.mjs.map} +0 -0
  238. /package/build-module/actions/{duplicate-post.js.map → duplicate-post.mjs.map} +0 -0
  239. /package/build-module/actions/{duplicate-template-part.js.map → duplicate-template-part.mjs.map} +0 -0
  240. /package/build-module/actions/{export-pattern.js.map → export-pattern.mjs.map} +0 -0
  241. /package/build-module/actions/{index.js.map → index.mjs.map} +0 -0
  242. /package/build-module/actions/{permanently-delete-post.js.map → permanently-delete-post.mjs.map} +0 -0
  243. /package/build-module/actions/{reset-post.js.map → reset-post.mjs.map} +0 -0
  244. /package/build-module/actions/{restore-post.js.map → restore-post.mjs.map} +0 -0
  245. /package/build-module/actions/{trash-post.js.map → trash-post.mjs.map} +0 -0
  246. /package/build-module/actions/{utils.js.map → utils.mjs.map} +0 -0
  247. /package/build-module/actions/{view-post-revisions.js.map → view-post-revisions.mjs.map} +0 -0
  248. /package/build-module/actions/{view-post.js.map → view-post.mjs.map} +0 -0
  249. /package/build-module/components/create-template-part-modal/{utils.js.map → utils.mjs.map} +0 -0
  250. /package/build-module/fields/author/{author-view.js.map → author-view.mjs.map} +0 -0
  251. /package/build-module/fields/author/{index.js.map → index.mjs.map} +0 -0
  252. /package/build-module/fields/comment-status/{index.js.map → index.mjs.map} +0 -0
  253. /package/build-module/fields/date/{date-view.js.map → date-view.mjs.map} +0 -0
  254. /package/build-module/fields/date/{index.js.map → index.mjs.map} +0 -0
  255. /package/build-module/fields/discussion/{index.js.map → index.mjs.map} +0 -0
  256. /package/build-module/fields/featured-image/{featured-image-view.js.map → featured-image-view.mjs.map} +0 -0
  257. /package/build-module/fields/{index.js.map → index.mjs.map} +0 -0
  258. /package/build-module/fields/notes/{index.js.map → index.mjs.map} +0 -0
  259. /package/build-module/fields/order/{index.js.map → index.mjs.map} +0 -0
  260. /package/build-module/fields/page-title/{index.js.map → index.mjs.map} +0 -0
  261. /package/build-module/fields/page-title/{view.js.map → view.mjs.map} +0 -0
  262. /package/build-module/fields/parent/{index.js.map → index.mjs.map} +0 -0
  263. /package/build-module/fields/parent/{parent-view.js.map → parent-view.mjs.map} +0 -0
  264. /package/build-module/fields/parent/{utils.js.map → utils.mjs.map} +0 -0
  265. /package/build-module/fields/password/{index.js.map → index.mjs.map} +0 -0
  266. /package/build-module/fields/pattern-title/{index.js.map → index.mjs.map} +0 -0
  267. /package/build-module/fields/pattern-title/{view.js.map → view.mjs.map} +0 -0
  268. /package/build-module/fields/slug/{index.js.map → index.mjs.map} +0 -0
  269. /package/build-module/fields/slug/{slug-edit.js.map → slug-edit.mjs.map} +0 -0
  270. /package/build-module/fields/slug/{slug-view.js.map → slug-view.mjs.map} +0 -0
  271. /package/build-module/fields/slug/{utils.js.map → utils.mjs.map} +0 -0
  272. /package/build-module/fields/status/{index.js.map → index.mjs.map} +0 -0
  273. /package/build-module/fields/status/{status-elements.js.map → status-elements.mjs.map} +0 -0
  274. /package/build-module/fields/status/{status-view.js.map → status-view.mjs.map} +0 -0
  275. /package/build-module/fields/template/{index.js.map → index.mjs.map} +0 -0
  276. /package/build-module/fields/template/{template-edit.js.map → template-edit.mjs.map} +0 -0
  277. /package/build-module/fields/template-title/{index.js.map → index.mjs.map} +0 -0
  278. /package/build-module/fields/title/{index.js.map → index.mjs.map} +0 -0
  279. /package/build-module/fields/title/{view.js.map → view.mjs.map} +0 -0
  280. /package/build-module/{lock-unlock.js.map → lock-unlock.mjs.map} +0 -0
  281. /package/build-module/mutation/{index.js.map → index.mjs.map} +0 -0
  282. /package/build-module/{types.js.map → types.mjs.map} +0 -0
@@ -0,0 +1,616 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import clsx from 'clsx';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import {
10
+ Button,
11
+ DropZone,
12
+ Icon,
13
+ Spinner,
14
+ __experimentalText as Text,
15
+ __experimentalTruncate as Truncate,
16
+ __experimentalVStack as VStack,
17
+ BaseControl,
18
+ Tooltip,
19
+ VisuallyHidden,
20
+ } from '@wordpress/components';
21
+ import { isBlobURL, getBlobTypeByURL } from '@wordpress/blob';
22
+ import { store as coreStore, type Attachment } from '@wordpress/core-data';
23
+ import { useSelect, useDispatch } from '@wordpress/data';
24
+ import { useCallback, useMemo, useState } from '@wordpress/element';
25
+ import { __ } from '@wordpress/i18n';
26
+ import { archive, audio, video, file, closeSmall } from '@wordpress/icons';
27
+ import {
28
+ MediaUpload,
29
+ uploadMedia,
30
+ privateApis as mediaUtilsPrivateApis,
31
+ } from '@wordpress/media-utils';
32
+ import { store as noticesStore } from '@wordpress/notices';
33
+
34
+ /**
35
+ * Internal dependencies
36
+ */
37
+ import { unlock } from '../../lock-unlock';
38
+ import type { MediaEditProps } from '../../types';
39
+
40
+ const { MediaUploadModal } = unlock( mediaUtilsPrivateApis );
41
+
42
+ type BlobItem = {
43
+ id: string;
44
+ source_url: string;
45
+ mime_type: string | undefined;
46
+ alt_text?: string;
47
+ };
48
+
49
+ /**
50
+ * Conditional Media component that uses MediaUploadModal when experiment is enabled,
51
+ * otherwise falls back to media-utils MediaUpload.
52
+ *
53
+ * @param root0 Component props.
54
+ * @param root0.render Render prop function that receives { open } object.
55
+ * @param root0.multiple Whether to allow multiple media selections.
56
+ * @return The component.
57
+ */
58
+ function ConditionalMediaUpload( { render, multiple, ...props }: any ) {
59
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
60
+ if ( ( window as any ).__experimentalDataViewsMediaModal ) {
61
+ return (
62
+ <>
63
+ { render && render( { open: () => setIsModalOpen( true ) } ) }
64
+ { isModalOpen && (
65
+ <MediaUploadModal
66
+ { ...props }
67
+ multiple={ multiple }
68
+ isOpen={ isModalOpen }
69
+ onClose={ () => {
70
+ setIsModalOpen( false );
71
+ props.onClose?.();
72
+ } }
73
+ onSelect={ ( media: any ) => {
74
+ setIsModalOpen( false );
75
+ props.onSelect?.( media );
76
+ } }
77
+ />
78
+ ) }
79
+ </>
80
+ );
81
+ }
82
+ // Fallback to media-utils MediaUpload when experiment is disabled.
83
+ return (
84
+ <MediaUpload
85
+ { ...props }
86
+ render={ render }
87
+ multiple={ multiple ? 'add' : undefined }
88
+ />
89
+ );
90
+ }
91
+
92
+ function MediaPickerButton( {
93
+ open,
94
+ children,
95
+ label,
96
+ showTooltip = false,
97
+ onFilesDrop,
98
+ attachment,
99
+ isUploading = false,
100
+ }: {
101
+ open: () => void;
102
+ children: React.ReactNode;
103
+ label: string;
104
+ showTooltip?: boolean;
105
+ onFilesDrop: MediaEditAttachmentsProps[ 'onFilesDrop' ];
106
+ attachment?: MediaEditAttachment;
107
+ isUploading?: boolean;
108
+ } ) {
109
+ const isBlob = attachment && isBlobURL( attachment.source_url );
110
+ const mediaPickerButton = (
111
+ <div
112
+ className="fields__media-edit-picker-button"
113
+ role="button"
114
+ tabIndex={ 0 }
115
+ onClick={ () => {
116
+ if ( ! isUploading ) {
117
+ open();
118
+ }
119
+ } }
120
+ onKeyDown={ ( event ) => {
121
+ if ( isUploading ) {
122
+ return;
123
+ }
124
+ if ( event.key === 'Enter' || event.key === ' ' ) {
125
+ event.preventDefault();
126
+ open();
127
+ }
128
+ } }
129
+ aria-label={ label }
130
+ aria-disabled={ isUploading }
131
+ >
132
+ { children }
133
+ { isBlob && (
134
+ <span className="fields__media-edit-picker-button-spinner">
135
+ <Spinner />
136
+ </span>
137
+ ) }
138
+ { ! isUploading && (
139
+ <DropZone
140
+ onFilesDrop={ ( files ) =>
141
+ onFilesDrop( files, attachment?.id as number )
142
+ }
143
+ />
144
+ ) }
145
+ </div>
146
+ );
147
+ if ( ! showTooltip ) {
148
+ return mediaPickerButton;
149
+ }
150
+ return <Tooltip text={ label }>{ mediaPickerButton }</Tooltip>;
151
+ }
152
+
153
+ const archiveMimeTypes = [
154
+ 'application/zip',
155
+ 'application/x-zip-compressed',
156
+ 'application/x-rar-compressed',
157
+ 'application/x-7z-compressed',
158
+ 'application/x-tar',
159
+ 'application/x-gzip',
160
+ ];
161
+
162
+ function MediaTitle( { attachment }: { attachment: Attachment< 'view' > } ) {
163
+ return (
164
+ <Truncate className="fields__media-edit-filename">
165
+ { attachment.title.rendered }
166
+ </Truncate>
167
+ );
168
+ }
169
+
170
+ function MediaEditPlaceholder( props: {
171
+ open: () => void;
172
+ label: string;
173
+ onFilesDrop: MediaEditAttachmentsProps[ 'onFilesDrop' ];
174
+ isUploading: boolean;
175
+ } ) {
176
+ return (
177
+ <MediaPickerButton { ...props }>
178
+ <span className="fields__media-edit-placeholder">
179
+ { props.label }
180
+ </span>
181
+ </MediaPickerButton>
182
+ );
183
+ }
184
+
185
+ function MediaPreview( { attachment }: { attachment: MediaEditAttachment } ) {
186
+ const url = attachment.source_url;
187
+ const mimeType = attachment.mime_type || '';
188
+ if ( mimeType.startsWith( 'image' ) ) {
189
+ return (
190
+ <img
191
+ className="fields__media-edit-thumbnail"
192
+ alt={ attachment.alt_text || '' }
193
+ src={ url }
194
+ />
195
+ );
196
+ } else if ( mimeType.startsWith( 'audio' ) ) {
197
+ return <Icon icon={ audio } />;
198
+ } else if ( mimeType.startsWith( 'video' ) ) {
199
+ return <Icon icon={ video } />;
200
+ } else if ( archiveMimeTypes.includes( mimeType ) ) {
201
+ return <Icon icon={ archive } />;
202
+ }
203
+ return <Icon icon={ file } />;
204
+ }
205
+
206
+ type MediaEditAttachment = Attachment< 'view' > | BlobItem;
207
+ interface MediaEditAttachmentsProps {
208
+ allItems: Array< MediaEditAttachment > | null;
209
+ addButtonLabel: string;
210
+ multiple?: boolean;
211
+ removeItem: ( itemId: number ) => void;
212
+ open: () => void;
213
+ onFilesDrop: ( files: File[], attachmentId?: number ) => void;
214
+ isUploading: boolean;
215
+ }
216
+
217
+ function ExpandedMediaEditAttachments( {
218
+ allItems,
219
+ addButtonLabel,
220
+ multiple,
221
+ removeItem,
222
+ open,
223
+ onFilesDrop,
224
+ isUploading,
225
+ }: MediaEditAttachmentsProps ) {
226
+ return (
227
+ <div
228
+ className={ clsx( 'fields__media-edit-expanded', {
229
+ 'is-multiple': multiple,
230
+ 'is-single': ! multiple,
231
+ 'is-empty': ! allItems?.length,
232
+ } ) }
233
+ >
234
+ { allItems?.map( ( attachment ) => {
235
+ const hasPreviewImage =
236
+ attachment.mime_type?.startsWith( 'image' );
237
+ const isBlob = isBlobURL( attachment.source_url );
238
+ return (
239
+ <div
240
+ key={ attachment.id }
241
+ className={ clsx( 'fields__media-edit-expanded-item', {
242
+ 'has-preview-image': hasPreviewImage,
243
+ } ) }
244
+ >
245
+ <MediaPickerButton
246
+ open={ open }
247
+ label={ __( 'Replace' ) }
248
+ showTooltip
249
+ onFilesDrop={ onFilesDrop }
250
+ attachment={ attachment }
251
+ isUploading={ isUploading }
252
+ >
253
+ <div className="fields__media-edit-expanded-preview">
254
+ <VStack
255
+ spacing={ 0 }
256
+ alignment="center"
257
+ justify="center"
258
+ className="fields__media-edit-expanded-preview-stack"
259
+ >
260
+ { ( ! isBlob || hasPreviewImage ) && (
261
+ <MediaPreview
262
+ attachment={ attachment }
263
+ />
264
+ ) }
265
+ { ! isBlob &&
266
+ ( ! hasPreviewImage ? (
267
+ <MediaTitle
268
+ attachment={
269
+ attachment as Attachment< 'view' >
270
+ }
271
+ />
272
+ ) : (
273
+ <div className="fields__media-edit-expanded-overlay">
274
+ <div className="fields__media-edit-expanded-title">
275
+ <MediaTitle
276
+ attachment={
277
+ attachment as Attachment< 'view' >
278
+ }
279
+ />
280
+ </div>
281
+ </div>
282
+ ) ) }
283
+ </VStack>
284
+ </div>
285
+ </MediaPickerButton>
286
+ { ! isBlob && (
287
+ <div className="fields__media-edit-expanded-overlay">
288
+ <Button
289
+ __next40pxDefaultSize
290
+ className="fields__media-edit-expanded-remove"
291
+ icon={ closeSmall }
292
+ label={ __( 'Remove' ) }
293
+ size="small"
294
+ disabled={ isUploading }
295
+ accessibleWhenDisabled
296
+ onClick={ (
297
+ event: React.MouseEvent< HTMLButtonElement >
298
+ ) => {
299
+ event.stopPropagation();
300
+ removeItem( attachment.id as number );
301
+ } }
302
+ />
303
+ </div>
304
+ ) }
305
+ </div>
306
+ );
307
+ } ) }
308
+ { ( multiple || ! allItems?.length ) && (
309
+ <MediaEditPlaceholder
310
+ open={ open }
311
+ label={ addButtonLabel }
312
+ onFilesDrop={ onFilesDrop }
313
+ isUploading={ isUploading }
314
+ />
315
+ ) }
316
+ </div>
317
+ );
318
+ }
319
+
320
+ function CompactMediaEditAttachments( {
321
+ allItems,
322
+ addButtonLabel,
323
+ multiple,
324
+ removeItem,
325
+ open,
326
+ onFilesDrop,
327
+ isUploading,
328
+ }: MediaEditAttachmentsProps ) {
329
+ return (
330
+ <>
331
+ { !! allItems?.length && (
332
+ <VStack spacing={ 2 }>
333
+ { allItems.map( ( attachment ) => {
334
+ const isBlob = isBlobURL( attachment.source_url );
335
+ return (
336
+ <div
337
+ key={ attachment.id }
338
+ className="fields__media-edit-compact"
339
+ >
340
+ <MediaPickerButton
341
+ open={ open }
342
+ label={ __( 'Replace' ) }
343
+ showTooltip
344
+ onFilesDrop={ onFilesDrop }
345
+ attachment={ attachment }
346
+ isUploading={ isUploading }
347
+ >
348
+ <>
349
+ <MediaPreview
350
+ attachment={ attachment }
351
+ />
352
+ { ! isBlob && (
353
+ <MediaTitle
354
+ attachment={
355
+ attachment as Attachment< 'view' >
356
+ }
357
+ />
358
+ ) }
359
+ </>
360
+ </MediaPickerButton>
361
+ <Button
362
+ __next40pxDefaultSize
363
+ className="fields__media-edit-remove"
364
+ text={ __( 'Remove' ) }
365
+ variant="secondary"
366
+ disabled={ isUploading }
367
+ accessibleWhenDisabled
368
+ onClick={ (
369
+ event: React.MouseEvent< HTMLButtonElement >
370
+ ) => {
371
+ event.stopPropagation();
372
+ if (
373
+ typeof attachment.id === 'number'
374
+ ) {
375
+ removeItem( attachment.id );
376
+ }
377
+ } }
378
+ />
379
+ </div>
380
+ );
381
+ } ) }
382
+ </VStack>
383
+ ) }
384
+ { ( multiple || ! allItems?.length ) && (
385
+ <MediaEditPlaceholder
386
+ open={ open }
387
+ label={ addButtonLabel }
388
+ onFilesDrop={ onFilesDrop }
389
+ isUploading={ isUploading }
390
+ />
391
+ ) }
392
+ </>
393
+ );
394
+ }
395
+
396
+ /**
397
+ * A media edit control component that provides a media picker UI with upload functionality
398
+ * for selecting WordPress media attachments. Supports both the traditional WordPress media
399
+ * library and the experimental DataViews media modal.
400
+ *
401
+ * This component is intended to be used as the `Edit` property of a field definition when
402
+ * registering fields with `registerEntityField` from `@wordpress/editor`.
403
+ *
404
+ * @template Item - The type of the item being edited.
405
+ *
406
+ * @param {MediaEditProps<Item>} props - The component props.
407
+ * @param {Item} props.data - The item being edited.
408
+ * @param {Object} props.field - The field configuration with getValue and setValue methods.
409
+ * @param {Function} props.onChange - Callback function when the media selection changes.
410
+ * @param {string[]} [props.allowedTypes] - Array of allowed media types. Default `['image']`.
411
+ * @param {boolean} [props.multiple] - Whether to allow multiple media selections. Default `false`.
412
+ * @param {boolean} [props.hideLabelFromVision] - Whether the label should be hidden from vision.
413
+ * @param {boolean} [props.isExpanded] - Whether to render in an expanded form. Default `false`.
414
+ *
415
+ * @return {JSX.Element} The media edit control component.
416
+ *
417
+ * @example
418
+ * ```tsx
419
+ * import { MediaEdit } from '@wordpress/fields';
420
+ * import type { DataFormControlProps } from '@wordpress/dataviews';
421
+ *
422
+ * const featuredImageField = {
423
+ * id: 'featured_media',
424
+ * type: 'media',
425
+ * label: 'Featured Image',
426
+ * Edit: (props: DataFormControlProps<MyPostType>) => (
427
+ * <MediaEdit
428
+ * {...props}
429
+ * allowedTypes={['image']}
430
+ * />
431
+ * ),
432
+ * };
433
+ * ```
434
+ */
435
+ export default function MediaEdit< Item >( {
436
+ data,
437
+ field,
438
+ onChange,
439
+ hideLabelFromVision,
440
+ allowedTypes = [ 'image' ],
441
+ multiple,
442
+ isExpanded,
443
+ }: MediaEditProps< Item > ) {
444
+ const value = field.getValue( { item: data } );
445
+ const attachments = useSelect(
446
+ ( select ) => {
447
+ if ( ! value ) {
448
+ return null;
449
+ }
450
+ const normalizedValue = Array.isArray( value ) ? value : [ value ];
451
+ const { getEntityRecords } = select( coreStore );
452
+ return getEntityRecords( 'postType', 'attachment', {
453
+ include: normalizedValue,
454
+ } ) as Attachment< 'view' >[] | null;
455
+ },
456
+ [ value ]
457
+ );
458
+ const { createErrorNotice } = useDispatch( noticesStore );
459
+ // Support one upload action at a time for now.
460
+ const [ replacementId, setReplacementId ] = useState< number >();
461
+ const [ blobs, setBlobs ] = useState< string[] >( [] );
462
+ const onChangeControl = useCallback(
463
+ ( newValue: number | number[] | undefined ) =>
464
+ onChange( field.setValue( { item: data, value: newValue } ) ),
465
+ [ data, field, onChange ]
466
+ );
467
+ const removeItem = ( itemId: number ) => {
468
+ const currentIds = Array.isArray( value ) ? value : [ value ];
469
+ const newIds = currentIds.filter( ( id ) => id !== itemId );
470
+ onChangeControl( newIds.length ? newIds : undefined );
471
+ };
472
+ const onFilesDrop = useCallback(
473
+ ( files: File[], _replacementId?: number ) => {
474
+ uploadMedia( {
475
+ allowedTypes: allowedTypes?.length ? allowedTypes : undefined,
476
+ filesList: files,
477
+ onFileChange( uploadedMedia: any[] ) {
478
+ setReplacementId( _replacementId );
479
+ const { blobItems, uploadedItems } = uploadedMedia.reduce(
480
+ ( accumulator, item ) => {
481
+ if ( isBlobURL( item.url ) ) {
482
+ accumulator.blobItems.push( item.url );
483
+ } else {
484
+ accumulator.uploadedItems.push( item.id );
485
+ }
486
+ return accumulator;
487
+ },
488
+ {
489
+ blobItems: [] as string[],
490
+ uploadedItems: [] as number[],
491
+ }
492
+ );
493
+ setBlobs( blobItems );
494
+ // If all uploads are complete reset the replacementId.
495
+ if ( uploadedItems.length === uploadedMedia.length ) {
496
+ setReplacementId( undefined );
497
+ }
498
+ if ( ! uploadedItems.length ) {
499
+ return;
500
+ }
501
+ if ( ! multiple ) {
502
+ onChangeControl( uploadedItems[ 0 ] );
503
+ return;
504
+ }
505
+ if ( ! value ) {
506
+ onChangeControl( uploadedItems );
507
+ return;
508
+ }
509
+ const normalizedValue = Array.isArray( value )
510
+ ? value
511
+ : [ value ];
512
+ const newIds = [
513
+ ...( _replacementId
514
+ ? normalizedValue.filter(
515
+ ( id: any ) => id !== _replacementId
516
+ )
517
+ : normalizedValue ),
518
+ ...uploadedItems,
519
+ ];
520
+ onChangeControl( newIds );
521
+ },
522
+ onError( error: Error ) {
523
+ setReplacementId( undefined );
524
+ setBlobs( [] );
525
+ createErrorNotice( error.message, { type: 'snackbar' } );
526
+ },
527
+ multiple: !! multiple,
528
+ } );
529
+ },
530
+ [ allowedTypes, value, multiple, createErrorNotice, onChangeControl ]
531
+ );
532
+ const addButtonLabel =
533
+ field.placeholder ||
534
+ ( multiple ? __( 'Choose files' ) : __( 'Choose file' ) );
535
+ // Merge real attachments with any existing blob items that are being uploaded.
536
+ const allItems: Array< MediaEditAttachment > | null = useMemo( () => {
537
+ if ( ! blobs.length ) {
538
+ return attachments;
539
+ }
540
+ const items: Array< MediaEditAttachment > = [
541
+ ...( attachments || [] ),
542
+ ];
543
+ const blobItems = blobs.map( ( url ) => ( {
544
+ id: url,
545
+ source_url: url,
546
+ mime_type: getBlobTypeByURL( url ),
547
+ } ) );
548
+ const replacementIndex = items.findIndex(
549
+ ( a ) => a.id === replacementId
550
+ );
551
+ // Place blobs at the replacement index, when files
552
+ // dropped in existing media item.
553
+ if ( replacementIndex !== -1 ) {
554
+ return [
555
+ ...items.slice( 0, replacementIndex ),
556
+ ...blobItems,
557
+ ...items.slice( replacementIndex + 1 ),
558
+ ];
559
+ }
560
+ items.push( ...blobItems );
561
+ return items;
562
+ }, [ attachments, replacementId, blobs ] );
563
+ return (
564
+ <fieldset className="fields__media-edit" data-field-id={ field.id }>
565
+ <ConditionalMediaUpload
566
+ onSelect={ ( selectedMedia: any ) => {
567
+ if ( multiple ) {
568
+ const newIds = Array.isArray( selectedMedia )
569
+ ? selectedMedia.map( ( m: any ) => m.id )
570
+ : [ selectedMedia.id ];
571
+ onChangeControl( newIds );
572
+ } else {
573
+ onChangeControl( selectedMedia.id );
574
+ }
575
+ } }
576
+ allowedTypes={ allowedTypes }
577
+ value={ value }
578
+ multiple={ multiple }
579
+ title={ field.label }
580
+ render={ ( { open }: any ) => {
581
+ const AttachmentsComponent = isExpanded
582
+ ? ExpandedMediaEditAttachments
583
+ : CompactMediaEditAttachments;
584
+ return (
585
+ <VStack spacing={ 2 }>
586
+ { field.label &&
587
+ ( hideLabelFromVision ? (
588
+ <VisuallyHidden as="legend">
589
+ { field.label }
590
+ </VisuallyHidden>
591
+ ) : (
592
+ <BaseControl.VisualLabel as="legend">
593
+ { field.label }
594
+ </BaseControl.VisualLabel>
595
+ ) ) }
596
+ <AttachmentsComponent
597
+ allItems={ allItems }
598
+ addButtonLabel={ addButtonLabel }
599
+ multiple={ multiple }
600
+ removeItem={ removeItem }
601
+ open={ open }
602
+ onFilesDrop={ onFilesDrop }
603
+ isUploading={ !! blobs.length }
604
+ />
605
+ { field.description && (
606
+ <Text variant="muted">
607
+ { field.description }
608
+ </Text>
609
+ ) }
610
+ </VStack>
611
+ );
612
+ } }
613
+ />
614
+ </fieldset>
615
+ );
616
+ }