@wordpress/block-editor 15.13.2-next.v.202602271551.0 → 15.13.2

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 (195) hide show
  1. package/build/components/block-bindings/attribute-control.cjs +5 -2
  2. package/build/components/block-bindings/attribute-control.cjs.map +2 -2
  3. package/build/components/block-inspector/edit-contents.cjs +5 -4
  4. package/build/components/block-inspector/edit-contents.cjs.map +2 -2
  5. package/build/components/block-list/block-html.cjs +6 -2
  6. package/build/components/block-list/block-html.cjs.map +2 -2
  7. package/build/components/block-list/block.cjs +9 -1
  8. package/build/components/block-list/block.cjs.map +2 -2
  9. package/build/components/block-list/use-block-props/index.cjs +10 -1
  10. package/build/components/block-list/use-block-props/index.cjs.map +2 -2
  11. package/build/components/block-quick-navigation/index.cjs +1 -6
  12. package/build/components/block-quick-navigation/index.cjs.map +3 -3
  13. package/build/components/block-settings-menu/block-settings-dropdown.cjs +4 -47
  14. package/build/components/block-settings-menu/block-settings-dropdown.cjs.map +2 -2
  15. package/build/components/block-settings-menu-controls/index.cjs +1 -0
  16. package/build/components/block-settings-menu-controls/index.cjs.map +2 -2
  17. package/build/components/block-styles/index.cjs +7 -1
  18. package/build/components/block-styles/index.cjs.map +3 -3
  19. package/build/components/block-styles/menu-items.cjs +16 -10
  20. package/build/components/block-styles/menu-items.cjs.map +3 -3
  21. package/build/components/block-switcher/block-styles-menu.cjs +2 -50
  22. package/build/components/block-switcher/block-styles-menu.cjs.map +3 -3
  23. package/build/components/block-toolbar/block-toolbar-icon.cjs +1 -1
  24. package/build/components/block-toolbar/block-toolbar-icon.cjs.map +2 -2
  25. package/build/components/block-toolbar/index.cjs +8 -4
  26. package/build/components/block-toolbar/index.cjs.map +2 -2
  27. package/build/components/block-tools/index.cjs +4 -3
  28. package/build/components/block-tools/index.cjs.map +2 -2
  29. package/build/components/block-variation-transforms/index.cjs +11 -5
  30. package/build/components/block-variation-transforms/index.cjs.map +2 -2
  31. package/build/components/block-visibility/modal.cjs +2 -2
  32. package/build/components/block-visibility/modal.cjs.map +2 -2
  33. package/build/components/block-visibility/use-block-visibility.cjs +4 -3
  34. package/build/components/block-visibility/use-block-visibility.cjs.map +2 -2
  35. package/build/components/block-visibility/viewport-visibility-info.cjs +6 -1
  36. package/build/components/block-visibility/viewport-visibility-info.cjs.map +2 -2
  37. package/build/components/dimensions-tool/scale-tool.cjs +2 -2
  38. package/build/components/dimensions-tool/scale-tool.cjs.map +2 -2
  39. package/build/components/grid/grid-visualizer.cjs +0 -3
  40. package/build/components/grid/grid-visualizer.cjs.map +2 -2
  41. package/build/components/iframe/index.cjs +1 -3
  42. package/build/components/iframe/index.cjs.map +2 -2
  43. package/build/components/iframe/use-scale-canvas.cjs +0 -1
  44. package/build/components/iframe/use-scale-canvas.cjs.map +2 -2
  45. package/build/components/inspector-controls-tabs/use-inspector-controls-tabs.cjs +8 -4
  46. package/build/components/inspector-controls-tabs/use-inspector-controls-tabs.cjs.map +2 -2
  47. package/build/components/link-picker/link-preview.cjs +2 -1
  48. package/build/components/link-picker/link-preview.cjs.map +3 -3
  49. package/build/components/list-view/block.cjs +1 -2
  50. package/build/components/list-view/block.cjs.map +2 -2
  51. package/build/components/provider/index.cjs +23 -7
  52. package/build/components/provider/index.cjs.map +2 -2
  53. package/build/components/provider/use-media-upload-settings.cjs +1 -0
  54. package/build/components/provider/use-media-upload-settings.cjs.map +2 -2
  55. package/build/components/writing-flow/use-arrow-nav.cjs +1 -1
  56. package/build/components/writing-flow/use-arrow-nav.cjs.map +2 -2
  57. package/build/hooks/auto-inspector-controls.cjs +26 -2
  58. package/build/hooks/auto-inspector-controls.cjs.map +3 -3
  59. package/build/hooks/cross-origin-isolation.cjs +9 -75
  60. package/build/hooks/cross-origin-isolation.cjs.map +2 -2
  61. package/build/hooks/custom-css.cjs +26 -0
  62. package/build/hooks/custom-css.cjs.map +3 -3
  63. package/build/private-apis.cjs +1 -0
  64. package/build/private-apis.cjs.map +2 -2
  65. package/build/store/actions.cjs +0 -8
  66. package/build/store/actions.cjs.map +2 -2
  67. package/build/store/private-keys.cjs +3 -0
  68. package/build/store/private-keys.cjs.map +2 -2
  69. package/build/store/private-selectors.cjs +4 -3
  70. package/build/store/private-selectors.cjs.map +2 -2
  71. package/build/store/reducer.cjs +65 -59
  72. package/build/store/reducer.cjs.map +2 -2
  73. package/build/store/selectors.cjs +16 -14
  74. package/build/store/selectors.cjs.map +2 -2
  75. package/build-module/components/block-bindings/attribute-control.mjs +5 -2
  76. package/build-module/components/block-bindings/attribute-control.mjs.map +2 -2
  77. package/build-module/components/block-inspector/edit-contents.mjs +5 -4
  78. package/build-module/components/block-inspector/edit-contents.mjs.map +2 -2
  79. package/build-module/components/block-list/block-html.mjs +7 -3
  80. package/build-module/components/block-list/block-html.mjs.map +2 -2
  81. package/build-module/components/block-list/block.mjs +10 -2
  82. package/build-module/components/block-list/block.mjs.map +2 -2
  83. package/build-module/components/block-list/use-block-props/index.mjs +11 -2
  84. package/build-module/components/block-list/use-block-props/index.mjs.map +2 -2
  85. package/build-module/components/block-quick-navigation/index.mjs +1 -6
  86. package/build-module/components/block-quick-navigation/index.mjs.map +2 -2
  87. package/build-module/components/block-settings-menu/block-settings-dropdown.mjs +5 -48
  88. package/build-module/components/block-settings-menu/block-settings-dropdown.mjs.map +2 -2
  89. package/build-module/components/block-settings-menu-controls/index.mjs +1 -0
  90. package/build-module/components/block-settings-menu-controls/index.mjs.map +2 -2
  91. package/build-module/components/block-styles/index.mjs +7 -1
  92. package/build-module/components/block-styles/index.mjs.map +2 -2
  93. package/build-module/components/block-styles/menu-items.mjs +6 -10
  94. package/build-module/components/block-styles/menu-items.mjs.map +2 -2
  95. package/build-module/components/block-switcher/block-styles-menu.mjs +3 -51
  96. package/build-module/components/block-switcher/block-styles-menu.mjs.map +2 -2
  97. package/build-module/components/block-toolbar/block-toolbar-icon.mjs +1 -1
  98. package/build-module/components/block-toolbar/block-toolbar-icon.mjs.map +2 -2
  99. package/build-module/components/block-toolbar/index.mjs +8 -4
  100. package/build-module/components/block-toolbar/index.mjs.map +2 -2
  101. package/build-module/components/block-tools/index.mjs +4 -3
  102. package/build-module/components/block-tools/index.mjs.map +2 -2
  103. package/build-module/components/block-variation-transforms/index.mjs +11 -5
  104. package/build-module/components/block-variation-transforms/index.mjs.map +2 -2
  105. package/build-module/components/block-visibility/modal.mjs +2 -2
  106. package/build-module/components/block-visibility/modal.mjs.map +2 -2
  107. package/build-module/components/block-visibility/use-block-visibility.mjs +4 -3
  108. package/build-module/components/block-visibility/use-block-visibility.mjs.map +2 -2
  109. package/build-module/components/block-visibility/viewport-visibility-info.mjs +6 -1
  110. package/build-module/components/block-visibility/viewport-visibility-info.mjs.map +2 -2
  111. package/build-module/components/dimensions-tool/scale-tool.mjs +2 -2
  112. package/build-module/components/dimensions-tool/scale-tool.mjs.map +2 -2
  113. package/build-module/components/grid/grid-visualizer.mjs +0 -3
  114. package/build-module/components/grid/grid-visualizer.mjs.map +2 -2
  115. package/build-module/components/iframe/index.mjs +2 -9
  116. package/build-module/components/iframe/index.mjs.map +2 -2
  117. package/build-module/components/iframe/use-scale-canvas.mjs +0 -1
  118. package/build-module/components/iframe/use-scale-canvas.mjs.map +2 -2
  119. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.mjs +8 -4
  120. package/build-module/components/inspector-controls-tabs/use-inspector-controls-tabs.mjs.map +2 -2
  121. package/build-module/components/link-picker/link-preview.mjs +2 -1
  122. package/build-module/components/link-picker/link-preview.mjs.map +2 -2
  123. package/build-module/components/list-view/block.mjs +1 -2
  124. package/build-module/components/list-view/block.mjs.map +2 -2
  125. package/build-module/components/provider/index.mjs +23 -7
  126. package/build-module/components/provider/index.mjs.map +2 -2
  127. package/build-module/components/provider/use-media-upload-settings.mjs +1 -0
  128. package/build-module/components/provider/use-media-upload-settings.mjs.map +2 -2
  129. package/build-module/components/writing-flow/use-arrow-nav.mjs +1 -1
  130. package/build-module/components/writing-flow/use-arrow-nav.mjs.map +2 -2
  131. package/build-module/hooks/auto-inspector-controls.mjs +28 -4
  132. package/build-module/hooks/auto-inspector-controls.mjs.map +2 -2
  133. package/build-module/hooks/cross-origin-isolation.mjs +9 -75
  134. package/build-module/hooks/cross-origin-isolation.mjs.map +2 -2
  135. package/build-module/hooks/custom-css.mjs +28 -2
  136. package/build-module/hooks/custom-css.mjs.map +2 -2
  137. package/build-module/private-apis.mjs +3 -1
  138. package/build-module/private-apis.mjs.map +2 -2
  139. package/build-module/store/actions.mjs +0 -8
  140. package/build-module/store/actions.mjs.map +2 -2
  141. package/build-module/store/private-keys.mjs +2 -0
  142. package/build-module/store/private-keys.mjs.map +2 -2
  143. package/build-module/store/private-selectors.mjs +4 -3
  144. package/build-module/store/private-selectors.mjs.map +2 -2
  145. package/build-module/store/reducer.mjs +65 -58
  146. package/build-module/store/reducer.mjs.map +2 -2
  147. package/build-module/store/selectors.mjs +16 -14
  148. package/build-module/store/selectors.mjs.map +2 -2
  149. package/build-style/content-rtl.css +25 -1
  150. package/build-style/content.css +25 -2
  151. package/build-style/style-rtl.css +3 -0
  152. package/build-style/style.css +3 -0
  153. package/package.json +39 -39
  154. package/src/components/block-bindings/attribute-control.js +8 -3
  155. package/src/components/block-inspector/edit-contents.js +5 -3
  156. package/src/components/block-list/block-html.js +13 -3
  157. package/src/components/block-list/block.js +10 -1
  158. package/src/components/block-list/content.scss +35 -4
  159. package/src/components/block-list/use-block-props/index.js +11 -1
  160. package/src/components/block-quick-navigation/index.js +1 -6
  161. package/src/components/block-settings-menu/block-settings-dropdown.js +2 -46
  162. package/src/components/block-settings-menu-controls/index.js +1 -0
  163. package/src/components/block-styles/index.js +7 -1
  164. package/src/components/block-styles/menu-items.js +11 -10
  165. package/src/components/block-switcher/block-styles-menu.js +1 -47
  166. package/src/components/block-toolbar/block-toolbar-icon.js +6 -1
  167. package/src/components/block-toolbar/index.js +7 -2
  168. package/src/components/block-tools/index.js +11 -4
  169. package/src/components/block-tools/style.scss +5 -0
  170. package/src/components/block-variation-transforms/index.js +41 -36
  171. package/src/components/block-visibility/use-block-visibility.js +4 -2
  172. package/src/components/block-visibility/viewport-visibility-info.js +8 -1
  173. package/src/components/dimensions-tool/scale-tool.js +2 -2
  174. package/src/components/grid/grid-visualizer.js +0 -3
  175. package/src/components/iframe/index.js +2 -12
  176. package/src/components/iframe/use-scale-canvas.js +0 -1
  177. package/src/components/inspector-controls-tabs/use-inspector-controls-tabs.js +9 -5
  178. package/src/components/link-picker/link-preview.js +2 -1
  179. package/src/components/list-view/block.js +0 -5
  180. package/src/components/provider/index.js +49 -6
  181. package/src/components/provider/use-media-upload-settings.js +1 -0
  182. package/src/components/writing-flow/use-arrow-nav.js +9 -2
  183. package/src/hooks/auto-inspector-controls.js +31 -4
  184. package/src/hooks/cross-origin-isolation.js +10 -109
  185. package/src/hooks/custom-css.js +36 -2
  186. package/src/hooks/test/cross-origin-isolation.js +15 -42
  187. package/src/private-apis.js +2 -0
  188. package/src/store/actions.js +0 -8
  189. package/src/store/private-keys.js +1 -0
  190. package/src/store/private-selectors.js +6 -3
  191. package/src/store/reducer.js +96 -77
  192. package/src/store/selectors.js +59 -22
  193. package/src/store/test/private-selectors.js +67 -34
  194. package/src/store/test/reducer.js +436 -57
  195. package/src/store/test/selectors.js +611 -53
@@ -1,34 +1,12 @@
1
1
  /**
2
- * WordPress dependencies
3
- */
4
- import { addFilter } from '@wordpress/hooks';
5
- import { createHigherOrderComponent } from '@wordpress/compose';
6
-
7
- /**
8
- * Adds crossorigin and credentialless attributes to elements as needed.
2
+ * Adds crossorigin="anonymous" to an element if missing.
9
3
  *
10
4
  * @param {Element} el The element to modify.
11
5
  */
12
- function addCrossOriginAttributes( el ) {
13
- // Add the crossorigin attribute if missing.
6
+ function addCrossOriginAttribute( el ) {
14
7
  if ( ! el.hasAttribute( 'crossorigin' ) ) {
15
8
  el.setAttribute( 'crossorigin', 'anonymous' );
16
9
  }
17
-
18
- // For iframes, add the credentialless attribute.
19
- if ( el.nodeName === 'IFRAME' && ! el.hasAttribute( 'credentialless' ) ) {
20
- // Do not modify the iframed editor canvas.
21
- if ( el.getAttribute( 'src' )?.startsWith( 'blob:' ) ) {
22
- return;
23
- }
24
-
25
- el.setAttribute( 'credentialless', '' );
26
-
27
- // Reload the iframe to ensure the new attribute is taken into account.
28
- const origSrc = el.getAttribute( 'src' ) || '';
29
- el.setAttribute( 'src', '' );
30
- el.setAttribute( 'src', origSrc );
31
- }
32
10
  }
33
11
 
34
12
  // Only add the mutation observer if the site is cross-origin isolated.
@@ -49,59 +27,18 @@ if ( window.crossOriginIsolated ) {
49
27
  return;
50
28
  }
51
29
 
52
- el.querySelectorAll(
53
- 'img,source,script,video,link,iframe'
54
- ).forEach( ( v ) => {
55
- addCrossOriginAttributes( v );
56
- } );
57
-
58
- if ( el.nodeName === 'IFRAME' ) {
59
- const iframeNode = el;
60
-
61
- /*
62
- * Sandboxed iframes should not get modified. For example embedding a tweet served in a sandboxed
63
- * iframe, the tweet itself would not be modified.
64
- */
65
- const isEmbedSandboxIframe =
66
- iframeNode.classList.contains(
67
- 'components-sandbox'
68
- );
69
-
70
- if ( ! isEmbedSandboxIframe ) {
71
- iframeNode.addEventListener( 'load', () => {
72
- try {
73
- if (
74
- iframeNode.contentDocument &&
75
- iframeNode.contentDocument.body
76
- ) {
77
- observer.observe(
78
- iframeNode.contentDocument,
79
- {
80
- childList: true,
81
- attributes: true,
82
- subtree: true,
83
- }
84
- );
85
- }
86
- } catch ( e ) {
87
- // Iframe may be cross-origin or otherwise inaccessible.
88
- // Silently ignore these cases.
89
- }
90
- } );
30
+ el.querySelectorAll( 'source,script,video,link' ).forEach(
31
+ ( v ) => {
32
+ addCrossOriginAttribute( v );
91
33
  }
92
- }
34
+ );
93
35
 
94
36
  if (
95
- [
96
- 'IMG',
97
- 'SOURCE',
98
- 'SCRIPT',
99
- 'VIDEO',
100
- 'LINK',
101
- 'IFRAME',
102
- ].includes( el.nodeName )
37
+ [ 'SOURCE', 'SCRIPT', 'VIDEO', 'LINK' ].includes(
38
+ el.nodeName
39
+ )
103
40
  ) {
104
- addCrossOriginAttributes( el );
41
+ addCrossOriginAttribute( el );
105
42
  }
106
43
  } );
107
44
  } );
@@ -134,39 +71,3 @@ if ( window.crossOriginIsolated ) {
134
71
 
135
72
  startObservingBody();
136
73
  }
137
-
138
- // Only apply the embed preview filter when cross-origin isolated.
139
- if ( window.crossOriginIsolated ) {
140
- const supportsCredentialless =
141
- 'credentialless' in window.HTMLIFrameElement.prototype;
142
-
143
- const disableEmbedPreviews = createHigherOrderComponent(
144
- ( BlockEdit ) =>
145
- function DisableEmbedPreviews( props ) {
146
- if ( 'core/embed' !== props.name ) {
147
- return <BlockEdit { ...props } />;
148
- }
149
-
150
- // List of embeds that do not support a preview is from packages/block-library/src/embed/variations.js.
151
- const previewable =
152
- supportsCredentialless &&
153
- ! [ 'facebook', 'smugmug' ].includes(
154
- props.attributes.providerNameSlug
155
- );
156
-
157
- return (
158
- <BlockEdit
159
- { ...props }
160
- attributes={ { ...props.attributes, previewable } }
161
- />
162
- );
163
- },
164
- 'withDisabledEmbedPreview'
165
- );
166
-
167
- addFilter(
168
- 'editor.BlockEdit',
169
- 'media-experiments/disable-embed-previews',
170
- disableEmbedPreviews
171
- );
172
- }
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useMemo } from '@wordpress/element';
5
- import { useSelect } from '@wordpress/data';
4
+ import { useEffect, useMemo } from '@wordpress/element';
5
+ import { useDispatch, useSelect } from '@wordpress/data';
6
6
  import { useInstanceId } from '@wordpress/compose';
7
7
  import { getBlockType, hasBlockSupport } from '@wordpress/blocks';
8
8
  import { __, sprintf } from '@wordpress/i18n';
9
9
  import { processCSSNesting } from '@wordpress/global-styles-engine';
10
+ import { useBlockEditingMode } from '../components/block-editing-mode';
11
+ import { store as noticesStore } from '@wordpress/notices';
10
12
 
11
13
  /**
12
14
  * Internal dependencies
@@ -33,6 +35,11 @@ const EMPTY_STYLE = {};
33
35
  * @param {Object} props.style Block style attribute.
34
36
  */
35
37
  function CustomCSSControl( { blockName, setAttributes, style } ) {
38
+ const blockEditingMode = useBlockEditingMode();
39
+
40
+ if ( blockEditingMode !== 'default' ) {
41
+ return null;
42
+ }
36
43
  const blockType = getBlockType( blockName );
37
44
 
38
45
  function onChange( newStyle ) {
@@ -63,6 +70,8 @@ function CustomCSSControl( { blockName, setAttributes, style } ) {
63
70
  );
64
71
  }
65
72
 
73
+ const CUSTOM_CSS_WARNING_NOTICE_ID = 'custom-css-edit-warning';
74
+
66
75
  function CustomCSSEdit( { clientId, name, setAttributes } ) {
67
76
  const { style, canEditCSS } = useSelect(
68
77
  ( select ) => {
@@ -107,6 +116,31 @@ function useBlockProps( { style } ) {
107
116
  customCSS.trim().length > 0 &&
108
117
  validateCSS( customCSS );
109
118
 
119
+ const canEditCSS = useSelect(
120
+ ( select ) => select( blockEditorStore ).getSettings().canEditCSS,
121
+ []
122
+ );
123
+
124
+ const { createWarningNotice } = useDispatch( noticesStore );
125
+
126
+ // Show a warning notice when the user lacks edit_css and a block has
127
+ // custom CSS. The fixed notice ID ensures only one notice is shown
128
+ // regardless of how many blocks have CSS.
129
+ const hasCustomCSS = !! customCSS?.trim();
130
+ useEffect( () => {
131
+ if ( ! canEditCSS && hasCustomCSS ) {
132
+ createWarningNotice(
133
+ __(
134
+ 'This post contains blocks with custom CSS. You do not have permission to edit CSS. If you save this post, the custom CSS will be removed.'
135
+ ),
136
+ {
137
+ id: CUSTOM_CSS_WARNING_NOTICE_ID,
138
+ isDismissible: true,
139
+ }
140
+ );
141
+ }
142
+ }, [ canEditCSS, hasCustomCSS, createWarningNotice ] );
143
+
110
144
  const customCSSIdentifier = useInstanceId(
111
145
  CUSTOM_CSS_INSTANCE_REFERENCE,
112
146
  'wp-custom-css'
@@ -152,60 +152,33 @@ describe( 'cross-origin-isolation', () => {
152
152
  expect( observeSpy ).not.toHaveBeenCalled();
153
153
  } );
154
154
 
155
- it( 'should handle iframe contentDocument errors gracefully', () => {
155
+ it( 'should not add crossorigin="anonymous" to images', async () => {
156
156
  Object.defineProperty( window, 'crossOriginIsolated', {
157
157
  value: true,
158
158
  writable: true,
159
159
  configurable: true,
160
160
  } );
161
161
 
162
- // Re-import the module
162
+ // Re-import the module to trigger the side effects
163
163
  jest.isolateModules( () => {
164
164
  require( '../cross-origin-isolation' );
165
165
  } );
166
166
 
167
- // Create an iframe that throws when accessing contentDocument
168
- const iframe = document.createElement( 'iframe' );
169
- Object.defineProperty( iframe, 'contentDocument', {
170
- get() {
171
- throw new Error( 'Cross-origin access denied' );
172
- },
173
- } );
174
-
175
- // This should not throw an error
176
- expect( () => {
177
- document.body.appendChild( iframe );
178
- iframe.dispatchEvent( new Event( 'load' ) );
179
- } ).not.toThrow();
180
- } );
181
-
182
- it( 'should register embed preview filter when cross-origin isolated', () => {
183
- Object.defineProperty( window, 'crossOriginIsolated', {
184
- value: true,
185
- writable: true,
186
- configurable: true,
187
- } );
188
-
189
- const hasFilter = jest.spyOn(
190
- require( '@wordpress/hooks' ),
191
- 'hasFilter'
192
- );
167
+ // Create an image and add it to the DOM
168
+ const img = document.createElement( 'img' );
169
+ img.setAttribute( 'src', 'https://example.com/image.jpg' );
170
+ document.body.appendChild( img );
193
171
 
194
- // Re-import the module to register filters
195
- jest.isolateModules( () => {
196
- require( '../cross-origin-isolation' );
197
- } );
172
+ // Wait for MutationObserver callback to fire (async microtask).
173
+ await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
198
174
 
199
- // The module should register a filter when cross-origin isolated
200
- // We can't easily test the filter itself without a full React environment,
201
- // but we can verify the module loads without errors
202
- expect( () => {
203
- require( '@wordpress/hooks' ).hasFilter(
204
- 'editor.BlockEdit',
205
- 'media-experiments/disable-embed-previews'
206
- );
207
- } ).not.toThrow();
175
+ // Images should NOT get the crossorigin attribute.
176
+ // Under Document-Isolation-Policy: isolate-and-credentialless,
177
+ // the credentialless mode handles image loading without CORS headers.
178
+ // Adding crossorigin="anonymous" would override this and break
179
+ // external images that don't serve CORS headers.
180
+ expect( img ).not.toHaveAttribute( 'crossorigin' );
208
181
 
209
- hasFilter.mockRestore();
182
+ document.body.removeChild( img );
210
183
  } );
211
184
  } );
@@ -48,6 +48,7 @@ import {
48
48
  deviceTypeKey,
49
49
  isIsolatedEditorKey,
50
50
  isNavigationOverlayContextKey,
51
+ mediaUploadOnSuccessKey,
51
52
  } from './store/private-keys';
52
53
  import { requiresWrapperOnCopy } from './components/writing-flow/utils';
53
54
  import { PrivateRichText } from './components/rich-text/';
@@ -129,6 +130,7 @@ lock( privateApis, {
129
130
  deviceTypeKey,
130
131
  isIsolatedEditorKey,
131
132
  isNavigationOverlayContextKey,
133
+ mediaUploadOnSuccessKey,
132
134
  useBlockElement,
133
135
  useBlockElementRef,
134
136
  LinkPicker,
@@ -1767,10 +1767,6 @@ export const insertBeforeBlock =
1767
1767
  return;
1768
1768
  }
1769
1769
  const rootClientId = select.getBlockRootClientId( clientId );
1770
- const isLocked = select.getTemplateLock( rootClientId );
1771
- if ( isLocked ) {
1772
- return;
1773
- }
1774
1770
 
1775
1771
  const blockIndex = select.getBlockIndex( clientId );
1776
1772
  const directInsertBlock = rootClientId
@@ -1810,10 +1806,6 @@ export const insertAfterBlock =
1810
1806
  return;
1811
1807
  }
1812
1808
  const rootClientId = select.getBlockRootClientId( clientId );
1813
- const isLocked = select.getTemplateLock( rootClientId );
1814
- if ( isLocked ) {
1815
- return;
1816
- }
1817
1809
 
1818
1810
  const blockIndex = select.getBlockIndex( clientId );
1819
1811
  const directInsertBlock = rootClientId
@@ -10,3 +10,4 @@ export const deviceTypeKey = Symbol( 'deviceTypeKey' );
10
10
  export const isNavigationOverlayContextKey = Symbol(
11
11
  'isNavigationOverlayContext'
12
12
  );
13
+ export const mediaUploadOnSuccessKey = Symbol( 'mediaUploadOnSuccess' );
@@ -147,7 +147,7 @@ export const getEnabledClientIdsTree = createRegistrySelector( () =>
147
147
  createSelector( getEnabledClientIdsTreeUnmemoized, ( state ) => [
148
148
  state.blocks.order,
149
149
  state.derivedBlockEditingModes,
150
- state.blockEditingModes,
150
+ state.blocks.blockEditingModes,
151
151
  ] )
152
152
  );
153
153
 
@@ -170,7 +170,7 @@ export const getEnabledBlockParents = createSelector(
170
170
  },
171
171
  ( state ) => [
172
172
  state.blocks.parents,
173
- state.blockEditingModes,
173
+ state.blocks.blockEditingModes,
174
174
  state.settings.templateLock,
175
175
  state.blockListSettings,
176
176
  ]
@@ -501,10 +501,13 @@ function isSectionBlockCandidate( state, clientId ) {
501
501
  const disableContentOnlyForUnsyncedPatterns =
502
502
  state.settings?.disableContentOnlyForUnsyncedPatterns;
503
503
 
504
+ const disableContentOnlyForTemplateParts =
505
+ state.settings?.disableContentOnlyForTemplateParts;
506
+
504
507
  if (
505
508
  ( ( ! disableContentOnlyForUnsyncedPatterns &&
506
509
  attributes?.metadata?.patternName ) ||
507
- isTemplatePart ) &&
510
+ ( isTemplatePart && ! disableContentOnlyForTemplateParts ) ) &&
508
511
  ! isIsolatedEditor
509
512
  ) {
510
513
  return true;
@@ -601,26 +601,23 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
601
601
  if ( action.type === 'RESET_BLOCKS' ) {
602
602
  /**
603
603
  * Preserve controlled inner block flags across RESET_BLOCKS.
604
- * Previously this was cleared to `{}`, which caused nested
605
- * controllers (e.g. post-content, patterns) to lose their
606
- * controlled status and unnecessarily re-clone blocks. Stale
607
- * flags are cleaned up naturally by unsetControlledBlocks()
608
- * when useBlockSync unmounts.
604
+ * If there are old and new blocks that:
605
+ * - have the same `clientId`
606
+ * - have the `controlledInnerBlocks` flag
607
+ * - don't have any own, uncontrolled children
608
+ * then we preserve the `controlledInnerBlocks` flag and the controlled sub-trees.
609
+ * Nested controllers (e.g., `post-content`, patterns) don't lose their
610
+ * controlled status and don't unnecessarily re-clone blocks.
609
611
  */
612
+ const newState = reducer( undefined, {
613
+ type: 'INSERT_BLOCKS',
614
+ rootClientId: '',
615
+ blocks: action.blocks,
616
+ } );
617
+
610
618
  const preservedControlledInnerBlocks =
611
619
  state?.controlledInnerBlocks ?? {};
612
620
 
613
- const newState = {
614
- ...state,
615
- byClientId: new Map(
616
- getFlattenedBlocksWithoutAttributes( action.blocks )
617
- ),
618
- attributes: new Map( getFlattenedBlockAttributes( action.blocks ) ),
619
- order: mapBlockOrder( action.blocks ),
620
- parents: new Map( mapBlockParents( action.blocks ) ),
621
- controlledInnerBlocks: preservedControlledInnerBlocks,
622
- };
623
-
624
621
  // Preserve controlled inner blocks data from the old state.
625
622
  // The maps above are rebuilt solely from action.blocks, but
626
623
  // controlled inner blocks live under cloned IDs that aren't
@@ -637,23 +634,24 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
637
634
  if ( ! newState.byClientId.has( clientId ) ) {
638
635
  continue;
639
636
  }
637
+ newState.controlledInnerBlocks[ clientId ] = true;
640
638
  const oldOrder = state.order.get( clientId );
641
639
  if ( ! oldOrder?.length ) {
642
640
  continue;
643
641
  }
644
642
  newState.order.set( clientId, oldOrder );
645
643
  const preserveBlock = ( blockId, parentId ) => {
646
- const blockData = state.byClientId?.get( blockId );
644
+ const blockData = state.byClientId.get( blockId );
647
645
  if ( ! blockData ) {
648
646
  return;
649
647
  }
650
648
  newState.byClientId.set( blockId, blockData );
651
649
  newState.attributes.set(
652
650
  blockId,
653
- state.attributes?.get( blockId )
651
+ state.attributes.get( blockId )
654
652
  );
655
653
  newState.parents.set( blockId, parentId );
656
- const childOrder = state.order?.get( blockId ) || [];
654
+ const childOrder = state.order.get( blockId ) || [];
657
655
  newState.order.set( blockId, childOrder );
658
656
  childOrder.forEach( ( childId ) =>
659
657
  preserveBlock( childId, blockId )
@@ -663,42 +661,47 @@ const withBlockReset = ( reducer ) => ( state, action ) => {
663
661
  }
664
662
  }
665
663
 
666
- newState.tree = new Map( state?.tree );
667
- updateBlockTreeForBlocks( newState, action.blocks );
668
-
669
664
  // Fix tree entries for controlled blocks. updateBlockTreeForBlocks
670
665
  // built tree entries using action.blocks' inner block structure
671
666
  // (entity-level IDs), but we need them to reference the preserved
672
667
  // cloned inner blocks instead. Mutating the existing object
673
668
  // preserves references held by ancestor tree entries.
674
669
  for ( const clientId of Object.keys(
675
- preservedControlledInnerBlocks
670
+ newState.controlledInnerBlocks
676
671
  ) ) {
677
- if ( ! preservedControlledInnerBlocks[ clientId ] ) {
678
- continue;
679
- }
680
- if ( ! newState.byClientId.has( clientId ) ) {
681
- continue;
682
- }
683
672
  const controlledOrder = newState.order.get( clientId );
684
673
  if ( ! controlledOrder?.length ) {
685
674
  continue;
686
675
  }
687
676
  const innerBlocks = controlledOrder.map( ( id ) =>
688
- newState.tree.get( id )
677
+ state.tree.get( id )
689
678
  );
690
679
  const existingEntry = newState.tree.get( clientId );
691
680
  if ( existingEntry ) {
692
681
  existingEntry.innerBlocks = innerBlocks;
693
682
  }
694
683
  newState.tree.set( 'controlled||' + clientId, { innerBlocks } );
684
+ const preserveTreeEntry = ( blockId ) => {
685
+ const treeEntry = state.tree.get( blockId );
686
+ if ( ! treeEntry ) {
687
+ return;
688
+ }
689
+ newState.tree.set( blockId, treeEntry );
690
+ const childOrder = newState.order.get( blockId ) || [];
691
+ childOrder.forEach( preserveTreeEntry );
692
+ };
693
+ controlledOrder.forEach( preserveTreeEntry );
695
694
  }
696
695
 
697
- newState.tree.set( '', {
698
- innerBlocks: action.blocks.map( ( subBlock ) =>
699
- newState.tree.get( subBlock.clientId )
700
- ),
701
- } );
696
+ // Preserve block editing modes for blocks that are not removed.
697
+ const preservedBlockEditingModes =
698
+ state?.blockEditingModes ?? new Map();
699
+ for ( const [ clientId, mode ] of preservedBlockEditingModes ) {
700
+ if ( ! newState.tree.has( clientId ) ) {
701
+ continue;
702
+ }
703
+ newState.blockEditingModes.set( clientId, mode );
704
+ }
702
705
 
703
706
  return newState;
704
707
  }
@@ -1083,9 +1086,6 @@ export const blocks = pipe(
1083
1086
  if ( fromRootClientId === toRootClientId ) {
1084
1087
  const subState = state.get( toRootClientId );
1085
1088
  const fromIndex = subState.indexOf( clientIds[ 0 ] );
1086
- if ( fromIndex === -1 ) {
1087
- return state;
1088
- }
1089
1089
  const newState = new Map( state );
1090
1090
  newState.set(
1091
1091
  toRootClientId,
@@ -1290,6 +1290,25 @@ export const blocks = pipe(
1290
1290
  }
1291
1291
  return state;
1292
1292
  },
1293
+
1294
+ blockEditingModes( state = new Map(), action ) {
1295
+ switch ( action.type ) {
1296
+ case 'SET_BLOCK_EDITING_MODE':
1297
+ if ( state.get( action.clientId ) === action.mode ) {
1298
+ return state;
1299
+ }
1300
+ return new Map( state ).set( action.clientId, action.mode );
1301
+ case 'UNSET_BLOCK_EDITING_MODE': {
1302
+ if ( ! state.has( action.clientId ) ) {
1303
+ return state;
1304
+ }
1305
+ const newState = new Map( state );
1306
+ newState.delete( action.clientId );
1307
+ return newState;
1308
+ }
1309
+ }
1310
+ return state;
1311
+ },
1293
1312
  } );
1294
1313
 
1295
1314
  /**
@@ -2104,38 +2123,33 @@ export function editedContentOnlySection( state, action ) {
2104
2123
  if ( action.type === 'EDIT_CONTENT_ONLY_SECTION' ) {
2105
2124
  return action.clientId;
2106
2125
  }
2107
- return state;
2108
- }
2109
2126
 
2110
- /**
2111
- * Reducer returning a map of block client IDs to block editing modes.
2112
- *
2113
- * @param {Map} state Current state.
2114
- * @param {Object} action Dispatched action.
2115
- *
2116
- * @return {Map} Updated state.
2117
- */
2118
- export function blockEditingModes( state = new Map(), action ) {
2127
+ // Early return if there's no section being edited.
2128
+ if ( ! state ) {
2129
+ return state;
2130
+ }
2131
+
2119
2132
  switch ( action.type ) {
2120
- case 'SET_BLOCK_EDITING_MODE':
2121
- if ( state.get( action.clientId ) === action.mode ) {
2122
- return state;
2123
- }
2124
- return new Map( state ).set( action.clientId, action.mode );
2125
- case 'UNSET_BLOCK_EDITING_MODE': {
2126
- if ( ! state.has( action.clientId ) ) {
2127
- return state;
2133
+ case 'REMOVE_BLOCKS':
2134
+ case 'REPLACE_BLOCKS':
2135
+ // Clear if the edited section is directly among the removed/replaced blocks.
2136
+ // Note: this doesn't catch the case where a parent of the edited section
2137
+ // is removed, since action.clientIds only contains the top-level IDs.
2138
+ // That edge case is handled by the StopEditingContentOnlySectionOnOutsideSelect
2139
+ // component in block-list/index.js.
2140
+ if ( action.clientIds.includes( state ) ) {
2141
+ return undefined;
2142
+ }
2143
+ break;
2144
+ case 'RESET_BLOCKS':
2145
+ // When all blocks are reset (e.g. navigating to a different post),
2146
+ // check whether the edited section still exists in the new block tree.
2147
+ if ( ! getFlattenedClientIds( action.blocks )[ state ] ) {
2148
+ return undefined;
2128
2149
  }
2129
- const newState = new Map( state );
2130
- newState.delete( action.clientId );
2131
- return newState;
2132
- }
2133
- case 'RESET_BLOCKS': {
2134
- return state.has( '' )
2135
- ? new Map().set( '', state.get( '' ) )
2136
- : state;
2137
- }
2150
+ break;
2138
2151
  }
2152
+
2139
2153
  return state;
2140
2154
  }
2141
2155
 
@@ -2279,9 +2293,6 @@ function openedListViewPanels(
2279
2293
  } );
2280
2294
  return hasChanges ? { ...state, panels: newPanels } : state;
2281
2295
  }
2282
- case 'RESET_BLOCKS':
2283
- // Clear all panel state when blocks are reset
2284
- return { allOpen: false, panels: {} };
2285
2296
  }
2286
2297
  return state;
2287
2298
  }
@@ -2378,7 +2389,6 @@ const combinedReducers = combineReducers( {
2378
2389
  editedContentOnlySection,
2379
2390
  blockVisibility,
2380
2391
  viewportModalClientIds,
2381
- blockEditingModes,
2382
2392
  styleOverrides,
2383
2393
  removalPromptData,
2384
2394
  blockRemovalRules,
@@ -2509,7 +2519,7 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2509
2519
  // so the default block editing mode is set to disabled.
2510
2520
  const sectionRootClientId = state.settings?.[ sectionRootClientIdKey ];
2511
2521
  const sectionClientIds = state.blocks.order.get( sectionRootClientId );
2512
- const hasDisabledBlocks = Array.from( state.blockEditingModes ).some(
2522
+ const hasDisabledBlocks = Array.from( state.blocks.blockEditingModes ).some(
2513
2523
  ( [ , mode ] ) => mode === 'disabled'
2514
2524
  );
2515
2525
  const templatePartClientIds = [];
@@ -2550,10 +2560,15 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2550
2560
  state.blocks.attributes.get( clientId )?.metadata
2551
2561
  ?.patternName
2552
2562
  );
2563
+ const disableContentOnlyForTemplateParts =
2564
+ state.settings?.disableContentOnlyForTemplateParts;
2565
+
2553
2566
  const contentOnlyParents = [
2554
2567
  ...contentOnlyTemplateLockedClientIds,
2555
2568
  ...unsyncedPatternClientIds,
2556
- ...( isIsolatedEditor ? [] : templatePartClientIds ),
2569
+ ...( isIsolatedEditor || disableContentOnlyForTemplateParts
2570
+ ? []
2571
+ : templatePartClientIds ),
2557
2572
  ];
2558
2573
 
2559
2574
  traverseBlockTree( state, treeClientId, ( block ) => {
@@ -2580,7 +2595,7 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2580
2595
 
2581
2596
  // If the block already has an explicit block editing mode set,
2582
2597
  // don't override it.
2583
- if ( state.blockEditingModes.has( clientId ) ) {
2598
+ if ( state.blocks.blockEditingModes.has( clientId ) ) {
2584
2599
  return;
2585
2600
  }
2586
2601
 
@@ -2591,12 +2606,12 @@ function getDerivedBlockEditingModesForTree( state, treeClientId = '' ) {
2591
2606
  let ancestorBlockEditingMode;
2592
2607
  let parent = state.blocks.parents.get( clientId );
2593
2608
  while ( parent !== undefined ) {
2594
- if ( state.blockEditingModes.has( parent ) ) {
2609
+ if ( state.blocks.blockEditingModes.has( parent ) ) {
2595
2610
  // Checking the explicit block editing mode will be slower,
2596
2611
  // as the block editing mode is more likely to be set on a
2597
2612
  // distant ancestor.
2598
2613
  ancestorBlockEditingMode =
2599
- state.blockEditingModes.get( parent );
2614
+ state.blocks.blockEditingModes.get( parent );
2600
2615
  }
2601
2616
  if ( ancestorBlockEditingMode ) {
2602
2617
  break;
@@ -3072,7 +3087,8 @@ export function withDerivedBlockEditingModes( reducer ) {
3072
3087
  case 'UPDATE_SETTINGS': {
3073
3088
  // Recompute the entire tree if the section root,
3074
3089
  // the effective disableContentOnlyForUnsyncedPatterns value,
3075
- // or the isIsolatedEditor value changes.
3090
+ // the isIsolatedEditor value, or the
3091
+ // disableContentOnlyForTemplateParts value changes.
3076
3092
  // These are all values that affect the computation.
3077
3093
  if (
3078
3094
  state?.settings?.[ sectionRootClientIdKey ] !==
@@ -3082,7 +3098,10 @@ export function withDerivedBlockEditingModes( reducer ) {
3082
3098
  !! nextState?.settings
3083
3099
  ?.disableContentOnlyForUnsyncedPatterns ||
3084
3100
  !! state?.settings?.[ isIsolatedEditorKey ] !==
3085
- !! nextState?.settings?.[ isIsolatedEditorKey ]
3101
+ !! nextState?.settings?.[ isIsolatedEditorKey ] ||
3102
+ !! state?.settings?.disableContentOnlyForTemplateParts !==
3103
+ !! nextState?.settings
3104
+ ?.disableContentOnlyForTemplateParts
3086
3105
  ) {
3087
3106
  return {
3088
3107
  ...nextState,