@wordpress/block-editor 15.8.1-next.dc3f6d3c1.0 → 15.9.1-next.8b30e05b0.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 (216) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +4 -0
  3. package/build/components/block-list/index.js +2 -1
  4. package/build/components/block-list/index.js.map +2 -2
  5. package/build/components/block-list/use-block-props/use-selected-block-event-handlers.js +27 -5
  6. package/build/components/block-list/use-block-props/use-selected-block-event-handlers.js.map +2 -2
  7. package/build/components/block-lock/modal.js +5 -5
  8. package/build/components/block-lock/modal.js.map +2 -2
  9. package/build/components/block-lock/use-block-lock.js +10 -13
  10. package/build/components/block-lock/use-block-lock.js.map +2 -2
  11. package/build/components/block-settings-menu-controls/index.js +1 -1
  12. package/build/components/block-settings-menu-controls/index.js.map +2 -2
  13. package/build/components/block-tools/index.js +56 -45
  14. package/build/components/block-tools/index.js.map +3 -3
  15. package/build/components/block-visibility/toolbar.js +1 -1
  16. package/build/components/block-visibility/toolbar.js.map +1 -1
  17. package/build/components/content-only-controls/fields-dropdown-menu.js +66 -0
  18. package/build/components/content-only-controls/fields-dropdown-menu.js.map +7 -0
  19. package/build/components/content-only-controls/index.js +225 -44
  20. package/build/components/content-only-controls/index.js.map +3 -3
  21. package/build/components/content-only-controls/link/index.js +92 -103
  22. package/build/components/content-only-controls/link/index.js.map +3 -3
  23. package/build/components/content-only-controls/media/index.js +134 -134
  24. package/build/components/content-only-controls/media/index.js.map +3 -3
  25. package/build/components/content-only-controls/rich-text/index.js +65 -74
  26. package/build/components/content-only-controls/rich-text/index.js.map +3 -3
  27. package/build/components/font-family/index.js +1 -15
  28. package/build/components/font-family/index.js.map +2 -2
  29. package/build/components/global-styles/dimensions-panel.js +35 -2
  30. package/build/components/global-styles/dimensions-panel.js.map +2 -2
  31. package/build/components/global-styles/hooks.js +1 -1
  32. package/build/components/global-styles/hooks.js.map +2 -2
  33. package/build/components/global-styles/typography-panel.js +1 -2
  34. package/build/components/global-styles/typography-panel.js.map +2 -2
  35. package/build/components/index.js +3 -0
  36. package/build/components/index.js.map +2 -2
  37. package/build/components/inspector-controls-tabs/use-inspector-controls-tabs.js +1 -1
  38. package/build/components/inspector-controls-tabs/use-inspector-controls-tabs.js.map +2 -2
  39. package/build/components/link-control/index.js +15 -7
  40. package/build/components/link-control/index.js.map +2 -2
  41. package/build/components/list-view/block-select-button.js +3 -11
  42. package/build/components/list-view/block-select-button.js.map +2 -2
  43. package/build/components/list-view/block.js +9 -7
  44. package/build/components/list-view/block.js.map +2 -2
  45. package/build/components/media-placeholder/index.js +17 -4
  46. package/build/components/media-placeholder/index.js.map +2 -2
  47. package/build/components/media-placeholder/utils.js +60 -0
  48. package/build/components/media-placeholder/utils.js.map +7 -0
  49. package/build/components/media-replace-flow/index.js +20 -3
  50. package/build/components/media-replace-flow/index.js.map +2 -2
  51. package/build/components/tool-selector/index.js +46 -0
  52. package/build/components/tool-selector/index.js.map +7 -0
  53. package/build/components/use-block-commands/index.js +1 -1
  54. package/build/components/use-block-commands/index.js.map +2 -2
  55. package/build/components/use-block-drop-zone/index.js +1 -5
  56. package/build/components/use-block-drop-zone/index.js.map +2 -2
  57. package/build/hooks/dimensions.js +3 -3
  58. package/build/hooks/dimensions.js.map +2 -2
  59. package/build/hooks/metadata.js +1 -1
  60. package/build/hooks/metadata.js.map +2 -2
  61. package/build/hooks/utils.js +5 -1
  62. package/build/hooks/utils.js.map +2 -2
  63. package/build/store/private-selectors.js +43 -3
  64. package/build/store/private-selectors.js.map +2 -2
  65. package/build/store/reducer.js +2 -1
  66. package/build/store/reducer.js.map +2 -2
  67. package/build/store/selectors.js +6 -4
  68. package/build/store/selectors.js.map +2 -2
  69. package/build-module/components/block-list/index.js +2 -1
  70. package/build-module/components/block-list/index.js.map +2 -2
  71. package/build-module/components/block-list/use-block-props/use-selected-block-event-handlers.js +27 -5
  72. package/build-module/components/block-list/use-block-props/use-selected-block-event-handlers.js.map +2 -2
  73. package/build-module/components/block-lock/modal.js +5 -5
  74. package/build-module/components/block-lock/modal.js.map +2 -2
  75. package/build-module/components/block-lock/use-block-lock.js +10 -13
  76. package/build-module/components/block-lock/use-block-lock.js.map +2 -2
  77. package/build-module/components/block-settings-menu-controls/index.js +1 -1
  78. package/build-module/components/block-settings-menu-controls/index.js.map +2 -2
  79. package/build-module/components/block-tools/index.js +56 -45
  80. package/build-module/components/block-tools/index.js.map +2 -2
  81. package/build-module/components/block-visibility/toolbar.js +1 -1
  82. package/build-module/components/block-visibility/toolbar.js.map +1 -1
  83. package/build-module/components/content-only-controls/fields-dropdown-menu.js +45 -0
  84. package/build-module/components/content-only-controls/fields-dropdown-menu.js.map +7 -0
  85. package/build-module/components/content-only-controls/index.js +229 -46
  86. package/build-module/components/content-only-controls/index.js.map +2 -2
  87. package/build-module/components/content-only-controls/link/index.js +92 -104
  88. package/build-module/components/content-only-controls/link/index.js.map +2 -2
  89. package/build-module/components/content-only-controls/media/index.js +134 -135
  90. package/build-module/components/content-only-controls/media/index.js.map +2 -2
  91. package/build-module/components/content-only-controls/rich-text/index.js +67 -81
  92. package/build-module/components/content-only-controls/rich-text/index.js.map +2 -2
  93. package/build-module/components/font-family/index.js +1 -15
  94. package/build-module/components/font-family/index.js.map +2 -2
  95. package/build-module/components/global-styles/dimensions-panel.js +35 -2
  96. package/build-module/components/global-styles/dimensions-panel.js.map +2 -2
  97. package/build-module/components/global-styles/hooks.js +1 -1
  98. package/build-module/components/global-styles/hooks.js.map +2 -2
  99. package/build-module/components/global-styles/typography-panel.js +1 -2
  100. package/build-module/components/global-styles/typography-panel.js.map +2 -2
  101. package/build-module/components/index.js +2 -0
  102. package/build-module/components/index.js.map +2 -2
  103. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.js +1 -1
  104. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.js.map +2 -2
  105. package/build-module/components/link-control/index.js +16 -8
  106. package/build-module/components/link-control/index.js.map +2 -2
  107. package/build-module/components/list-view/block-select-button.js +3 -11
  108. package/build-module/components/list-view/block-select-button.js.map +2 -2
  109. package/build-module/components/list-view/block.js +9 -7
  110. package/build-module/components/list-view/block.js.map +2 -2
  111. package/build-module/components/media-placeholder/index.js +18 -5
  112. package/build-module/components/media-placeholder/index.js.map +2 -2
  113. package/build-module/components/media-placeholder/utils.js +35 -0
  114. package/build-module/components/media-placeholder/utils.js.map +7 -0
  115. package/build-module/components/media-replace-flow/index.js +20 -3
  116. package/build-module/components/media-replace-flow/index.js.map +2 -2
  117. package/build-module/components/tool-selector/index.js +15 -0
  118. package/build-module/components/tool-selector/index.js.map +7 -0
  119. package/build-module/components/use-block-commands/index.js +1 -1
  120. package/build-module/components/use-block-commands/index.js.map +2 -2
  121. package/build-module/components/use-block-drop-zone/index.js +1 -5
  122. package/build-module/components/use-block-drop-zone/index.js.map +2 -2
  123. package/build-module/hooks/dimensions.js +3 -3
  124. package/build-module/hooks/dimensions.js.map +2 -2
  125. package/build-module/hooks/metadata.js +1 -1
  126. package/build-module/hooks/metadata.js.map +2 -2
  127. package/build-module/hooks/utils.js +5 -1
  128. package/build-module/hooks/utils.js.map +2 -2
  129. package/build-module/store/private-selectors.js +39 -3
  130. package/build-module/store/private-selectors.js.map +2 -2
  131. package/build-module/store/reducer.js +2 -1
  132. package/build-module/store/reducer.js.map +2 -2
  133. package/build-module/store/selectors.js +6 -4
  134. package/build-module/store/selectors.js.map +2 -2
  135. package/build-style/content-rtl.css +3 -0
  136. package/build-style/content.css +3 -0
  137. package/build-style/style-rtl.css +14 -5
  138. package/build-style/style.css +14 -5
  139. package/package.json +38 -37
  140. package/src/components/block-list/content.scss +5 -0
  141. package/src/components/block-list/index.js +3 -1
  142. package/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +34 -3
  143. package/src/components/block-lock/modal.js +6 -5
  144. package/src/components/block-lock/use-block-lock.js +10 -14
  145. package/src/components/block-patterns-list/stories/{index.story.js → index.story.jsx} +3 -1
  146. package/src/components/block-settings-menu-controls/index.js +1 -1
  147. package/src/components/block-tools/index.js +15 -2
  148. package/src/components/block-tools/style.scss +4 -0
  149. package/src/components/block-visibility/toolbar.js +1 -1
  150. package/src/components/content-only-controls/fields-dropdown-menu.js +53 -0
  151. package/src/components/content-only-controls/index.js +314 -50
  152. package/src/components/content-only-controls/link/index.js +62 -57
  153. package/src/components/content-only-controls/media/index.js +177 -156
  154. package/src/components/content-only-controls/rich-text/index.js +30 -44
  155. package/src/components/content-only-controls/styles.scss +10 -1
  156. package/src/components/font-family/README.md +0 -9
  157. package/src/components/font-family/index.js +1 -16
  158. package/src/components/font-family/stories/{index.story.js → index.story.jsx} +0 -1
  159. package/src/components/global-styles/dimensions-panel.js +36 -0
  160. package/src/components/global-styles/hooks.js +1 -1
  161. package/src/components/global-styles/typography-panel.js +0 -1
  162. package/src/components/index.js +4 -0
  163. package/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js +1 -5
  164. package/src/components/link-control/index.js +36 -12
  165. package/src/components/list-view/block-select-button.js +22 -30
  166. package/src/components/list-view/block.js +9 -7
  167. package/src/components/media-placeholder/index.js +20 -5
  168. package/src/components/media-placeholder/test/get-computed-accept-attribute.js +164 -0
  169. package/src/components/media-placeholder/utils.js +65 -0
  170. package/src/components/media-replace-flow/index.js +22 -3
  171. package/src/components/tool-selector/index.js +19 -0
  172. package/src/components/use-block-commands/index.js +1 -1
  173. package/src/components/use-block-drop-zone/index.js +1 -5
  174. package/src/hooks/dimensions.js +8 -3
  175. package/src/hooks/metadata.js +1 -1
  176. package/src/hooks/test/metadata.js +1 -1
  177. package/src/hooks/utils.js +4 -0
  178. package/src/store/private-selectors.js +123 -6
  179. package/src/store/reducer.js +3 -0
  180. package/src/store/selectors.js +6 -4
  181. package/src/store/test/private-selectors.js +242 -0
  182. package/src/store/test/reducer.js +17 -7
  183. package/src/style.scss +0 -1
  184. package/tsconfig.json +1 -0
  185. package/build/components/content-only-controls/plain-text/index.js +0 -68
  186. package/build/components/content-only-controls/plain-text/index.js.map +0 -7
  187. package/build-module/components/content-only-controls/plain-text/index.js +0 -50
  188. package/build-module/components/content-only-controls/plain-text/index.js.map +0 -7
  189. package/src/components/content-only-controls/plain-text/index.js +0 -49
  190. package/src/components/font-family/style.scss +0 -7
  191. /package/src/components/alignment-control/stories/{aliginment-toolbar.story.js → aliginment-toolbar.story.jsx} +0 -0
  192. /package/src/components/alignment-control/stories/{index.story.js → index.story.jsx} +0 -0
  193. /package/src/components/block-alignment-matrix-control/stories/{index.story.js → index.story.jsx} +0 -0
  194. /package/src/components/block-draggable/stories/{index.story.js → index.story.jsx} +0 -0
  195. /package/src/components/block-heading-level-dropdown/stories/{index.story.js → index.story.jsx} +0 -0
  196. /package/src/components/block-mover/stories/{index.story.js → index.story.jsx} +0 -0
  197. /package/src/components/block-title/stories/{index.story.js → index.story.jsx} +0 -0
  198. /package/src/components/border-radius-control/stories/{index.story.js → index.story.jsx} +0 -0
  199. /package/src/components/date-format-picker/stories/{index.story.js → index.story.jsx} +0 -0
  200. /package/src/components/dimensions-tool/stories/{aspect-ratio-tool.story.js → aspect-ratio-tool.story.jsx} +0 -0
  201. /package/src/components/dimensions-tool/stories/{index.story.js → index.story.jsx} +0 -0
  202. /package/src/components/dimensions-tool/stories/{scale-tool.story.js → scale-tool.story.jsx} +0 -0
  203. /package/src/components/dimensions-tool/stories/{width-height-tool.story.js → width-height-tool.story.jsx} +0 -0
  204. /package/src/components/height-control/stories/{index.story.js → index.story.jsx} +0 -0
  205. /package/src/components/inserter/stories/{index.story.js → index.story.jsx} +0 -0
  206. /package/src/components/line-height-control/stories/{index.story.js → index.story.jsx} +0 -0
  207. /package/src/components/plain-text/stories/{index.story.js → index.story.jsx} +0 -0
  208. /package/src/components/resolution-tool/stories/{index.story.js → index.story.jsx} +0 -0
  209. /package/src/components/tabbed-sidebar/stories/{index.story.js → index.story.jsx} +0 -0
  210. /package/src/components/text-alignment-control/stories/{index.story.js → index.story.jsx} +0 -0
  211. /package/src/components/text-decoration-control/stories/{index.story.js → index.story.jsx} +0 -0
  212. /package/src/components/text-transform-control/stories/{index.story.js → index.story.jsx} +0 -0
  213. /package/src/components/unit-control/stories/{index.story.js → index.story.jsx} +0 -0
  214. /package/src/components/url-popover/stories/{index.story.js → index.story.jsx} +0 -0
  215. /package/src/components/warning/stories/{index.story.js → index.story.jsx} +0 -0
  216. /package/src/components/writing-mode-control/stories/{index.story.js → index.story.jsx} +0 -0
@@ -39,6 +39,7 @@ export function useHasDimensionsPanel( settings ) {
39
39
  const hasMargin = useHasMargin( settings );
40
40
  const hasGap = useHasGap( settings );
41
41
  const hasMinHeight = useHasMinHeight( settings );
42
+ const hasWidth = useHasWidth( settings );
42
43
  const hasAspectRatio = useHasAspectRatio( settings );
43
44
  const hasChildLayout = useHasChildLayout( settings );
44
45
 
@@ -50,6 +51,7 @@ export function useHasDimensionsPanel( settings ) {
50
51
  hasMargin ||
51
52
  hasGap ||
52
53
  hasMinHeight ||
54
+ hasWidth ||
53
55
  hasAspectRatio ||
54
56
  hasChildLayout )
55
57
  );
@@ -79,6 +81,10 @@ function useHasMinHeight( settings ) {
79
81
  return settings?.dimensions?.minHeight;
80
82
  }
81
83
 
84
+ function useHasWidth( settings ) {
85
+ return settings?.dimensions?.width;
86
+ }
87
+
82
88
  function useHasAspectRatio( settings ) {
83
89
  return settings?.dimensions?.aspectRatio;
84
90
  }
@@ -206,6 +212,7 @@ const DEFAULT_CONTROLS = {
206
212
  margin: true,
207
213
  blockGap: true,
208
214
  minHeight: true,
215
+ width: true,
209
216
  aspectRatio: true,
210
217
  childLayout: true,
211
218
  };
@@ -384,6 +391,17 @@ export default function DimensionsPanel( {
384
391
  };
385
392
  const hasMinHeightValue = () => !! value?.dimensions?.minHeight;
386
393
 
394
+ // Width
395
+ const showWidthControl = useHasWidth( settings );
396
+ const widthValue = decodeValue( inheritedValue?.dimensions?.width );
397
+ const setWidthValue = ( newValue ) => {
398
+ onChange( setImmutably( value, [ 'dimensions', 'width' ], newValue ) );
399
+ };
400
+ const resetWidthValue = () => {
401
+ setWidthValue( undefined );
402
+ };
403
+ const hasWidthValue = () => !! value?.dimensions?.width;
404
+
387
405
  // Aspect Ratio
388
406
  const showAspectRatioControl = useHasAspectRatio( settings );
389
407
  const aspectRatioValue = decodeValue(
@@ -439,6 +457,7 @@ export default function DimensionsPanel( {
439
457
  ...previousValue?.dimensions,
440
458
  minHeight: undefined,
441
459
  aspectRatio: undefined,
460
+ width: undefined,
442
461
  },
443
462
  };
444
463
  }, [] );
@@ -688,6 +707,23 @@ export default function DimensionsPanel( {
688
707
  />
689
708
  </ToolsPanelItem>
690
709
  ) }
710
+ { showWidthControl && (
711
+ <ToolsPanelItem
712
+ hasValue={ hasWidthValue }
713
+ label={ __( 'Width' ) }
714
+ onDeselect={ resetWidthValue }
715
+ isShownByDefault={
716
+ defaultControls.width ?? DEFAULT_CONTROLS.width
717
+ }
718
+ panelId={ panelId }
719
+ >
720
+ <HeightControl
721
+ label={ __( 'Width' ) }
722
+ value={ widthValue }
723
+ onChange={ setWidthValue }
724
+ />
725
+ </ToolsPanelItem>
726
+ ) }
691
727
  { showAspectRatioControl && (
692
728
  <AspectRatioTool
693
729
  hasValue={ hasAspectRatioValue }
@@ -152,7 +152,7 @@ export function useSettingsForBlockElement(
152
152
  }
153
153
  } );
154
154
 
155
- [ 'aspectRatio', 'minHeight' ].forEach( ( key ) => {
155
+ [ 'aspectRatio', 'minHeight', 'width' ].forEach( ( key ) => {
156
156
  if ( ! supportedStyles.includes( key ) ) {
157
157
  updatedSettings.dimensions = {
158
158
  ...updatedSettings.dimensions,
@@ -446,7 +446,6 @@ export default function TypographyPanel( {
446
446
  value={ fontFamily }
447
447
  onChange={ setFontFamily }
448
448
  size="__unstable-large"
449
- __nextHasNoMarginBottom
450
449
  />
451
450
  </ToolsPanelItem>
452
451
  ) }
@@ -172,3 +172,7 @@ export { useBlockEditingMode } from './block-editing-mode';
172
172
  export { default as BlockEditorProvider } from './provider';
173
173
  export { useSettings, useSetting } from './use-settings';
174
174
  export { useBlockCommands } from './use-block-commands';
175
+
176
+ // This component is no longer used in Gutenberg,
177
+ // but kept for backwards compatibility.
178
+ export { default as ToolSelector } from './tool-selector';
@@ -99,11 +99,7 @@ export default function useInspectorControlsTabs(
99
99
  tabs.push( TAB_SETTINGS );
100
100
  }
101
101
 
102
- if (
103
- hasBlockStyles ||
104
- hasStyleFills ||
105
- window?.__experimentalContentOnlyPatternInsertion
106
- ) {
102
+ if ( hasBlockStyles || hasStyleFills ) {
107
103
  tabs.push( TAB_STYLES );
108
104
  }
109
105
 
@@ -15,7 +15,7 @@ import {
15
15
  __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper,
16
16
  } from '@wordpress/components';
17
17
  import { __, sprintf } from '@wordpress/i18n';
18
- import { useRef, useState, useEffect } from '@wordpress/element';
18
+ import { useRef, useState, useEffect, useMemo } from '@wordpress/element';
19
19
  import { useInstanceId } from '@wordpress/compose';
20
20
  import { focus } from '@wordpress/dom';
21
21
  import { ENTER } from '@wordpress/keycodes';
@@ -187,7 +187,15 @@ function LinkControl( {
187
187
  const wrapperNode = useRef();
188
188
  const textInputRef = useRef();
189
189
  const searchInputRef = useRef();
190
- const isEndingEditWithFocusRef = useRef( false );
190
+ // TODO: Remove entityUrlFallbackRef and previewValue in favor of value prop after taxonomy entity binding
191
+ // is stable and returns the correct URL instead of null while resolving when creating the entity.
192
+ //
193
+ // Preserve the URL from entity suggestions before binding overrides it
194
+ // This is due to entity binding not being available immediately after the suggestion is selected.
195
+ // The URL can return null, especially for taxonomy entities, while entity binding is being resolved.
196
+ // To avoid unnecessary rerenders and focus loss, we preserve the URL from the suggestion and use it
197
+ // as a fallback until the entity binding is available.
198
+ const entityUrlFallbackRef = useRef();
191
199
 
192
200
  const settingsKeys = settings.map( ( { id } ) => id );
193
201
 
@@ -244,8 +252,6 @@ function LinkControl( {
244
252
  wrapperNode.current;
245
253
 
246
254
  nextFocusTarget.focus();
247
-
248
- isEndingEditWithFocusRef.current = false;
249
255
  }, [ isEditingLink, isCreatingPage ] );
250
256
 
251
257
  // The component mounting reference is maintained separately
@@ -261,18 +267,18 @@ function LinkControl( {
261
267
  const hasLinkValue = value?.url?.trim()?.length > 0;
262
268
 
263
269
  /**
264
- * Cancels editing state and marks that focus may need to be restored after
265
- * the next render, if focus was within the wrapper when editing finished.
270
+ * Cancels editing state.
266
271
  */
267
272
  const stopEditing = () => {
268
- isEndingEditWithFocusRef.current = !! wrapperNode.current?.contains(
269
- wrapperNode.current.ownerDocument.activeElement
270
- );
271
-
272
273
  setIsEditingLink( false );
273
274
  };
274
275
 
275
276
  const handleSelectSuggestion = ( updatedValue ) => {
277
+ // Preserve the URL for taxonomy entities before binding overrides it
278
+ if ( updatedValue?.kind === 'taxonomy' && updatedValue?.url ) {
279
+ entityUrlFallbackRef.current = updatedValue.url;
280
+ }
281
+
276
282
  // Suggestions may contains "settings" values (e.g. `opensInNewTab`)
277
283
  // which should not override any existing settings values set by the
278
284
  // user. This filters out any settings values from the suggestion.
@@ -396,6 +402,24 @@ function LinkControl( {
396
402
  const isDisabled = ! valueHasChanges || currentInputIsEmpty;
397
403
  const showSettings = !! settings?.length && isEditingLink && hasLinkValue;
398
404
 
405
+ const previewValue = useMemo( () => {
406
+ // There is a chance that the value is not yet set from the entity binding, so we use the preserved URL.
407
+ if (
408
+ value?.kind === 'taxonomy' &&
409
+ ! value?.url &&
410
+ entityUrlFallbackRef.current
411
+ ) {
412
+ // combine the value prop with the preserved URL from the suggestion
413
+ return {
414
+ ...value,
415
+ url: entityUrlFallbackRef.current,
416
+ };
417
+ }
418
+
419
+ // If we don't have a fallback URL, use the value prop.
420
+ return value;
421
+ }, [ value ] );
422
+
399
423
  return (
400
424
  <div
401
425
  tabIndex={ -1 }
@@ -487,8 +511,8 @@ function LinkControl( {
487
511
 
488
512
  { value && ! isEditingLink && ! isCreatingPage && (
489
513
  <LinkPreview
490
- key={ value?.url } // force remount when URL changes to avoid race conditions for rich previews
491
- value={ value }
514
+ key={ previewValue?.url } // force remount when URL changes to avoid race conditions for rich previews
515
+ value={ previewValue }
492
516
  onEditClick={ () => setIsEditingLink( true ) }
493
517
  hasRichPreviews={ hasRichPreviews }
494
518
  hasUnlinkControl={ shownUnlinkControl }
@@ -61,36 +61,28 @@ function ListViewBlockSelectButton(
61
61
  context: 'list-view',
62
62
  } );
63
63
  const { isLocked } = useBlockLock( clientId );
64
- const {
65
- canToggleBlockVisibility,
66
- isBlockHidden,
67
- isContentOnly,
68
- hasPatternName,
69
- } = useSelect(
70
- ( select ) => {
71
- const { getBlockName, getBlockAttributes } =
72
- select( blockEditorStore );
73
- const { isBlockHidden: _isBlockHidden } = unlock(
74
- select( blockEditorStore )
75
- );
76
- const blockAttributes = getBlockAttributes( clientId );
77
- return {
78
- canToggleBlockVisibility: hasBlockSupport(
79
- getBlockName( clientId ),
80
- 'blockVisibility',
81
- true
82
- ),
83
- isBlockHidden: _isBlockHidden( clientId ),
84
- isContentOnly:
85
- select( blockEditorStore ).getBlockEditingMode(
86
- clientId
87
- ) === 'contentOnly',
88
- hasPatternName: !! blockAttributes?.metadata?.patternName,
89
- };
90
- },
91
- [ clientId ]
92
- );
93
- const shouldShowLockIcon = isLocked && ! isContentOnly;
64
+ const { canToggleBlockVisibility, isBlockHidden, hasPatternName } =
65
+ useSelect(
66
+ ( select ) => {
67
+ const { getBlockName, getBlockAttributes } =
68
+ select( blockEditorStore );
69
+ const { isBlockHidden: _isBlockHidden } = unlock(
70
+ select( blockEditorStore )
71
+ );
72
+ const blockAttributes = getBlockAttributes( clientId );
73
+ return {
74
+ canToggleBlockVisibility: hasBlockSupport(
75
+ getBlockName( clientId ),
76
+ 'visibility',
77
+ true
78
+ ),
79
+ isBlockHidden: _isBlockHidden( clientId ),
80
+ hasPatternName: !! blockAttributes?.metadata?.patternName,
81
+ };
82
+ },
83
+ [ clientId ]
84
+ );
85
+ const shouldShowLockIcon = isLocked;
94
86
  const shouldShowBlockVisibilityIcon =
95
87
  canToggleBlockVisibility && isBlockHidden;
96
88
  const isSticky = blockInformation?.positionType === 'sticky';
@@ -80,7 +80,7 @@ function ListViewBlock( {
80
80
  const [ isHovered, setIsHovered ] = useState( false );
81
81
  const [ settingsAnchorRect, setSettingsAnchorRect ] = useState();
82
82
 
83
- const { isLocked, canEdit, canMove } = useBlockLock( clientId );
83
+ const { isLocked } = useBlockLock( clientId );
84
84
 
85
85
  const isFirstSelectedBlock =
86
86
  isSelected && selectedClientIds[ 0 ] === clientId;
@@ -112,6 +112,8 @@ function ListViewBlock( {
112
112
  getBlockOrder,
113
113
  getBlockParents,
114
114
  getBlocksByClientId,
115
+ canEditBlock,
116
+ canMoveBlock,
115
117
  canRemoveBlocks,
116
118
  isGroupable,
117
119
  } = useSelect( blockEditorStore );
@@ -375,10 +377,10 @@ function ListViewBlock( {
375
377
  event.preventDefault();
376
378
  const { blocksToUpdate } = getBlocksToUpdate();
377
379
  const blocks = getBlocksByClientId( blocksToUpdate );
378
- const canToggleBlockVisibility = blocks.every( ( blockToUpdate ) =>
379
- hasBlockSupport( blockToUpdate.name, 'blockVisibility', true )
380
+ const canToggleVisibility = blocks.every( ( blockToUpdate ) =>
381
+ hasBlockSupport( blockToUpdate.name, 'visibility', true )
380
382
  );
381
- if ( ! canToggleBlockVisibility ) {
383
+ if ( ! canToggleVisibility ) {
382
384
  return;
383
385
  }
384
386
  const hasHiddenBlock = blocks.some(
@@ -553,7 +555,7 @@ function ListViewBlock( {
553
555
  'is-dragging': isDragged,
554
556
  'has-single-cell': ! showBlockActions,
555
557
  'is-synced': blockInformation?.isSynced,
556
- 'is-draggable': canMove,
558
+ 'is-draggable': canMoveBlock,
557
559
  'is-displacement-normal': displacement === 'normal',
558
560
  'is-displacement-up': displacement === 'up',
559
561
  'is-displacement-down': displacement === 'down',
@@ -588,7 +590,7 @@ function ListViewBlock( {
588
590
  path={ path }
589
591
  id={ `list-view-${ listViewInstanceId }-block-${ clientId }` }
590
592
  data-block={ clientId }
591
- data-expanded={ canEdit ? isExpanded : undefined }
593
+ data-expanded={ canEditBlock ? isExpanded : undefined }
592
594
  ref={ rowRef }
593
595
  >
594
596
  <TreeGridCell
@@ -614,7 +616,7 @@ function ListViewBlock( {
614
616
  currentlyEditingBlockInCanvas ? 0 : tabIndex
615
617
  }
616
618
  onFocus={ onFocus }
617
- isExpanded={ canEdit ? isExpanded : undefined }
619
+ isExpanded={ canEditBlock ? isExpanded : undefined }
618
620
  selectedClientIds={ selectedClientIds }
619
621
  ariaDescribedBy={ descriptionId }
620
622
  />
@@ -16,7 +16,7 @@ import {
16
16
  withFilters,
17
17
  } from '@wordpress/components';
18
18
  import { __, _x } from '@wordpress/i18n';
19
- import { useState, useEffect } from '@wordpress/element';
19
+ import { useState, useEffect, useMemo } from '@wordpress/element';
20
20
  import { useSelect } from '@wordpress/data';
21
21
  import { keyboardReturn } from '@wordpress/icons';
22
22
  import deprecated from '@wordpress/deprecated';
@@ -29,6 +29,7 @@ import MediaUploadCheck from '../media-upload/check';
29
29
  import URLPopover from '../url-popover';
30
30
  import { store as blockEditorStore } from '../../store';
31
31
  import { parseDropEvent } from '../use-on-block-drop';
32
+ import { getComputedAcceptAttribute } from './utils';
32
33
 
33
34
  const noop = () => {};
34
35
 
@@ -150,9 +151,13 @@ export function MediaPlaceholder( {
150
151
  } );
151
152
  }
152
153
 
153
- const mediaUpload = useSelect( ( select ) => {
154
+ const { mediaUpload, allowedMimeTypes } = useSelect( ( select ) => {
154
155
  const { getSettings } = select( blockEditorStore );
155
- return getSettings().mediaUpload;
156
+ const settings = getSettings();
157
+ return {
158
+ mediaUpload: settings.mediaUpload,
159
+ allowedMimeTypes: settings.allowedMimeTypes,
160
+ };
156
161
  }, [] );
157
162
  const [ src, setSrc ] = useState( '' );
158
163
 
@@ -160,6 +165,16 @@ export function MediaPlaceholder( {
160
165
  setSrc( value?.src ?? '' );
161
166
  }, [ value?.src ] );
162
167
 
168
+ const computedAccept = useMemo(
169
+ () =>
170
+ getComputedAcceptAttribute(
171
+ allowedTypes,
172
+ allowedMimeTypes,
173
+ accept
174
+ ),
175
+ [ allowedTypes, allowedMimeTypes, accept ]
176
+ );
177
+
163
178
  const onlyAllowsImages = () => {
164
179
  if ( ! allowedTypes || allowedTypes.length === 0 ) {
165
180
  return false;
@@ -470,7 +485,7 @@ export function MediaPlaceholder( {
470
485
  { renderDropZone() }
471
486
  <FormFileUpload
472
487
  onChange={ onUpload }
473
- accept={ accept }
488
+ accept={ computedAccept }
474
489
  multiple={ !! multiple }
475
490
  render={ ( { openFileDialog } ) => {
476
491
  const content = (
@@ -518,7 +533,7 @@ export function MediaPlaceholder( {
518
533
  </Button>
519
534
  ) }
520
535
  onChange={ onUpload }
521
- accept={ accept }
536
+ accept={ computedAccept }
522
537
  multiple={ !! multiple }
523
538
  />
524
539
  { uploadMediaLibraryButton }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import { getComputedAcceptAttribute } from '../utils';
5
+
6
+ describe( 'getComputedAcceptAttribute', () => {
7
+ it( 'returns the accept prop as-is when explicitly provided', () => {
8
+ const result = getComputedAcceptAttribute(
9
+ [ 'image' ],
10
+ { 'jpg|jpeg': 'image/jpeg', png: 'image/png' },
11
+ 'image/png,image/jpeg'
12
+ );
13
+ expect( result ).toBe( 'image/png,image/jpeg' );
14
+ } );
15
+
16
+ it( 'returns undefined when no allowedTypes are specified', () => {
17
+ const result = getComputedAcceptAttribute(
18
+ [],
19
+ { 'jpg|jpeg': 'image/jpeg', png: 'image/png' },
20
+ undefined
21
+ );
22
+ expect( result ).toBeUndefined();
23
+ } );
24
+
25
+ it( 'returns undefined when allowedTypes is null or undefined', () => {
26
+ const result = getComputedAcceptAttribute(
27
+ null,
28
+ { 'jpg|jpeg': 'image/jpeg', png: 'image/png' },
29
+ undefined
30
+ );
31
+ expect( result ).toBeUndefined();
32
+ } );
33
+
34
+ it( 'returns wildcard format when allowedMimeTypes is not available', () => {
35
+ const result = getComputedAcceptAttribute(
36
+ [ 'image', 'video' ],
37
+ undefined,
38
+ undefined
39
+ );
40
+ expect( result ).toBe( 'image/*,video/*' );
41
+ } );
42
+
43
+ it( 'returns wildcard format when allowedMimeTypes is not an object', () => {
44
+ const result = getComputedAcceptAttribute(
45
+ [ 'image' ],
46
+ 'not-an-object',
47
+ undefined
48
+ );
49
+ expect( result ).toBe( 'image/*' );
50
+ } );
51
+
52
+ it( 'returns specific MIME types when they match allowedTypes', () => {
53
+ const result = getComputedAcceptAttribute(
54
+ [ 'image' ],
55
+ {
56
+ 'jpg|jpeg': 'image/jpeg',
57
+ png: 'image/png',
58
+ mp4: 'video/mp4',
59
+ },
60
+ undefined
61
+ );
62
+ expect( result ).toBe( 'image/jpeg,image/png' );
63
+ } );
64
+
65
+ it( 'supports allowedTypes with full MIME type format', () => {
66
+ const result = getComputedAcceptAttribute(
67
+ [ 'image/png', 'video/mp4' ],
68
+ {
69
+ png: 'image/png',
70
+ 'jpg|jpeg': 'image/jpeg',
71
+ mp4: 'video/mp4',
72
+ },
73
+ undefined
74
+ );
75
+ expect( result ).toBe( 'image/png,video/mp4' );
76
+ } );
77
+
78
+ it( 'falls back to wildcard when specific MIME types are not found', () => {
79
+ const result = getComputedAcceptAttribute( [ 'image' ], {}, undefined );
80
+ expect( result ).toBe( 'image/*' );
81
+ } );
82
+
83
+ it( 'filters MIME types correctly when mixed with other types', () => {
84
+ const result = getComputedAcceptAttribute(
85
+ [ 'image', 'audio' ],
86
+ {
87
+ 'jpg|jpeg': 'image/jpeg',
88
+ png: 'image/png',
89
+ mp4: 'video/mp4',
90
+ mp3: 'audio/mp3',
91
+ wav: 'audio/wav',
92
+ },
93
+ undefined
94
+ );
95
+ expect( result ).toBe( 'image/jpeg,image/png,audio/mp3,audio/wav' );
96
+ } );
97
+
98
+ it( 'returns undefined when allowedMimeTypes is invalid and allowedTypes is empty', () => {
99
+ const result = getComputedAcceptAttribute( [], null, undefined );
100
+ expect( result ).toBeUndefined();
101
+ } );
102
+
103
+ it( 'handles empty allowedMimeTypes object', () => {
104
+ const result = getComputedAcceptAttribute( [ 'image' ], {}, undefined );
105
+ expect( result ).toBe( 'image/*' );
106
+ } );
107
+
108
+ it( 'preserves MIME type order from allowedMimeTypes', () => {
109
+ const mimeTypes = {
110
+ 'jpg|jpeg': 'image/jpeg',
111
+ png: 'image/png',
112
+ gif: 'image/gif',
113
+ };
114
+ const result = getComputedAcceptAttribute(
115
+ [ 'image' ],
116
+ mimeTypes,
117
+ undefined
118
+ );
119
+ // The exact order depends on Object.entries, which preserves insertion order in modern JS
120
+ expect( result ).toContain( 'image/jpeg' );
121
+ expect( result ).toContain( 'image/png' );
122
+ expect( result ).toContain( 'image/gif' );
123
+ } );
124
+
125
+ it( 'handles mixed allowedTypes with and without MIME type format', () => {
126
+ const result = getComputedAcceptAttribute(
127
+ [ 'image', 'video/mp4' ],
128
+ {
129
+ png: 'image/png',
130
+ 'jpg|jpeg': 'image/jpeg',
131
+ mp4: 'video/mp4',
132
+ webm: 'video/webm',
133
+ },
134
+ undefined
135
+ );
136
+ // 'image' matches image/* types and 'video/mp4' matches exact MIME type
137
+ expect( result ).toBe( 'image/png,image/jpeg,video/mp4' );
138
+ } );
139
+
140
+ it( 'returns single MIME type without comma', () => {
141
+ const result = getComputedAcceptAttribute(
142
+ [ 'image' ],
143
+ {
144
+ png: 'image/png',
145
+ },
146
+ undefined
147
+ );
148
+ expect( result ).toBe( 'image/png' );
149
+ } );
150
+
151
+ it( 'prioritizes specific MIME types over wildcard', () => {
152
+ const result = getComputedAcceptAttribute(
153
+ [ 'image' ],
154
+ {
155
+ 'jpg|jpeg': 'image/jpeg',
156
+ png: 'image/png',
157
+ },
158
+ undefined
159
+ );
160
+ // Should return specific types, not 'image/*'
161
+ expect( result ).not.toBe( 'image/*' );
162
+ expect( result ).toBe( 'image/jpeg,image/png' );
163
+ } );
164
+ } );
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Computes the accept attribute for file inputs based on allowed types
3
+ * and server-supported MIME types.
4
+ *
5
+ * This ensures users can only select file types that the server can handle,
6
+ * preventing upload failures (e.g., HEIC files when server lacks support).
7
+ *
8
+ * @param {Array} allowedTypes - List of allowed media types (e.g., ['image', 'video']).
9
+ * @param {Object} allowedMimeTypes - Map of allowed MIME types from server settings.
10
+ * @param {string} accept - Optional explicit accept attribute to use.
11
+ *
12
+ * @return {string|undefined} Computed accept attribute value, or undefined if no restrictions apply.
13
+ */
14
+ export function getComputedAcceptAttribute(
15
+ allowedTypes,
16
+ allowedMimeTypes,
17
+ accept
18
+ ) {
19
+ // If accept prop is explicitly provided, use it as is.
20
+ if ( accept ) {
21
+ return accept;
22
+ }
23
+
24
+ // If allowedMimeTypes is not available, fall back to wildcard.
25
+ if (
26
+ ! allowedMimeTypes ||
27
+ typeof allowedMimeTypes !== 'object' ||
28
+ Object.keys( allowedMimeTypes ).length === 0
29
+ ) {
30
+ if ( allowedTypes && allowedTypes.length > 0 ) {
31
+ return allowedTypes.map( ( type ) => `${ type }/*` ).join( ',' );
32
+ }
33
+ return undefined;
34
+ }
35
+
36
+ // If no allowedTypes specified, we can't filter, so return undefined.
37
+ if ( ! allowedTypes || allowedTypes.length === 0 ) {
38
+ return undefined;
39
+ }
40
+
41
+ // Build a list of specific MIME types based on allowedTypes.
42
+ const acceptedMimeTypes = [];
43
+
44
+ for ( const [ , mimeType ] of Object.entries( allowedMimeTypes ) ) {
45
+ // Check if this MIME type matches any of the allowedTypes.
46
+ const isAllowed = allowedTypes.some( ( allowedType ) => {
47
+ // Support both 'image' and 'image/jpeg' formats.
48
+ if ( allowedType.includes( '/' ) ) {
49
+ return mimeType === allowedType;
50
+ }
51
+ return mimeType.startsWith( `${ allowedType }/` );
52
+ } );
53
+
54
+ if ( isAllowed ) {
55
+ acceptedMimeTypes.push( mimeType );
56
+ }
57
+ }
58
+
59
+ // If we found specific MIME types, use them. Otherwise fall back to wildcard.
60
+ if ( acceptedMimeTypes.length > 0 ) {
61
+ return acceptedMimeTypes.join( ',' );
62
+ }
63
+
64
+ return allowedTypes.map( ( type ) => `${ type }/*` ).join( ',' );
65
+ }