@wordpress/block-editor 15.14.0 → 15.14.1-next.v.202603102151.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 (52) hide show
  1. package/build/components/block-tools/index.cjs +4 -3
  2. package/build/components/block-tools/index.cjs.map +2 -2
  3. package/build/components/iframe/index.cjs +14 -6
  4. package/build/components/iframe/index.cjs.map +2 -2
  5. package/build/components/observe-typing/index.cjs +9 -13
  6. package/build/components/observe-typing/index.cjs.map +2 -2
  7. package/build/components/provider/index.cjs +22 -6
  8. package/build/components/provider/index.cjs.map +2 -2
  9. package/build/hooks/block-fields/index.cjs +52 -31
  10. package/build/hooks/block-fields/index.cjs.map +2 -2
  11. package/build/hooks/cross-origin-isolation.cjs +7 -73
  12. package/build/hooks/cross-origin-isolation.cjs.map +2 -2
  13. package/build/private-apis.cjs +1 -0
  14. package/build/private-apis.cjs.map +2 -2
  15. package/build/store/private-keys.cjs +3 -0
  16. package/build/store/private-keys.cjs.map +2 -2
  17. package/build/store/selectors.cjs +9 -7
  18. package/build/store/selectors.cjs.map +2 -2
  19. package/build-module/components/block-tools/index.mjs +4 -3
  20. package/build-module/components/block-tools/index.mjs.map +2 -2
  21. package/build-module/components/iframe/index.mjs +14 -6
  22. package/build-module/components/iframe/index.mjs.map +2 -2
  23. package/build-module/components/observe-typing/index.mjs +9 -13
  24. package/build-module/components/observe-typing/index.mjs.map +2 -2
  25. package/build-module/components/provider/index.mjs +22 -6
  26. package/build-module/components/provider/index.mjs.map +2 -2
  27. package/build-module/hooks/block-fields/index.mjs +53 -32
  28. package/build-module/hooks/block-fields/index.mjs.map +2 -2
  29. package/build-module/hooks/cross-origin-isolation.mjs +7 -73
  30. package/build-module/hooks/cross-origin-isolation.mjs.map +2 -2
  31. package/build-module/private-apis.mjs +3 -1
  32. package/build-module/private-apis.mjs.map +2 -2
  33. package/build-module/store/private-keys.mjs +2 -0
  34. package/build-module/store/private-keys.mjs.map +2 -2
  35. package/build-module/store/selectors.mjs +9 -7
  36. package/build-module/store/selectors.mjs.map +2 -2
  37. package/build-style/style-rtl.css +8 -5
  38. package/build-style/style.css +8 -5
  39. package/package.json +39 -39
  40. package/src/components/block-tools/index.js +11 -4
  41. package/src/components/iframe/index.js +19 -6
  42. package/src/components/observe-typing/index.js +10 -14
  43. package/src/components/provider/index.js +47 -5
  44. package/src/components/responsive-block-control/style.scss +1 -0
  45. package/src/hooks/block-fields/index.js +44 -19
  46. package/src/hooks/block-fields/styles.scss +7 -9
  47. package/src/hooks/cross-origin-isolation.js +8 -107
  48. package/src/hooks/test/cross-origin-isolation.js +11 -42
  49. package/src/private-apis.js +2 -0
  50. package/src/store/private-keys.js +1 -0
  51. package/src/store/selectors.js +27 -9
  52. package/src/store/test/selectors.js +540 -0
@@ -3117,6 +3117,7 @@ iframe[name=editor-canvas] {
3117
3117
  position: absolute;
3118
3118
  width: 1px;
3119
3119
  word-wrap: normal !important;
3120
+ word-break: normal !important;
3120
3121
  }
3121
3122
 
3122
3123
  .preset-input-control__wrapper > * {
@@ -3521,14 +3522,15 @@ iframe[name=editor-canvas] {
3521
3522
  }
3522
3523
 
3523
3524
  .block-editor-block-fields__container {
3524
- padding: 0 16px;
3525
+ padding: 0 16px 16px;
3525
3526
  }
3526
3527
  .block-editor-block-fields__container:first-of-type {
3527
3528
  padding-block-start: 8px;
3528
- border-top: 1px solid #e0e0e0;
3529
- }
3530
- .block-editor-block-fields__container:last-of-type {
3531
- padding-block-end: 16px;
3529
+ /*
3530
+ * Add border for the entire content controls and remove the similar border
3531
+ * for tools panel.
3532
+ */
3533
+ border-block-start: 1px solid #e0e0e0;
3532
3534
  }
3533
3535
 
3534
3536
  .block-editor-block-fields__header {
@@ -3542,6 +3544,7 @@ iframe[name=editor-canvas] {
3542
3544
 
3543
3545
  .block-editor-block-fields__header-title {
3544
3546
  flex: 1;
3547
+ /* Override the default margin on a h2 element. */
3545
3548
  margin: 0 !important;
3546
3549
  }
3547
3550
 
@@ -3119,6 +3119,7 @@ iframe[name=editor-canvas] {
3119
3119
  position: absolute;
3120
3120
  width: 1px;
3121
3121
  word-wrap: normal !important;
3122
+ word-break: normal !important;
3122
3123
  }
3123
3124
 
3124
3125
  .preset-input-control__wrapper > * {
@@ -3523,14 +3524,15 @@ iframe[name=editor-canvas] {
3523
3524
  }
3524
3525
 
3525
3526
  .block-editor-block-fields__container {
3526
- padding: 0 16px;
3527
+ padding: 0 16px 16px;
3527
3528
  }
3528
3529
  .block-editor-block-fields__container:first-of-type {
3529
3530
  padding-block-start: 8px;
3530
- border-top: 1px solid #e0e0e0;
3531
- }
3532
- .block-editor-block-fields__container:last-of-type {
3533
- padding-block-end: 16px;
3531
+ /*
3532
+ * Add border for the entire content controls and remove the similar border
3533
+ * for tools panel.
3534
+ */
3535
+ border-block-start: 1px solid #e0e0e0;
3534
3536
  }
3535
3537
 
3536
3538
  .block-editor-block-fields__header {
@@ -3544,6 +3546,7 @@ iframe[name=editor-canvas] {
3544
3546
 
3545
3547
  .block-editor-block-fields__header-title {
3546
3548
  flex: 1;
3549
+ /* Override the default margin on a h2 element. */
3547
3550
  margin: 0 !important;
3548
3551
  }
3549
3552
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/block-editor",
3
- "version": "15.14.0",
3
+ "version": "15.14.1-next.v.202603102151.0+59e17f9ec",
4
4
  "description": "Generic block editor.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -61,43 +61,43 @@
61
61
  ],
62
62
  "dependencies": {
63
63
  "@react-spring/web": "^9.4.5",
64
- "@wordpress/a11y": "^4.41.0",
65
- "@wordpress/api-fetch": "^7.41.0",
66
- "@wordpress/base-styles": "^6.17.0",
67
- "@wordpress/blob": "^4.41.0",
68
- "@wordpress/block-serialization-default-parser": "^5.41.0",
69
- "@wordpress/blocks": "^15.14.0",
70
- "@wordpress/commands": "^1.41.0",
71
- "@wordpress/components": "^32.3.0",
72
- "@wordpress/compose": "^7.41.0",
73
- "@wordpress/data": "^10.41.0",
74
- "@wordpress/dataviews": "^13.0.0",
75
- "@wordpress/date": "^5.41.0",
76
- "@wordpress/deprecated": "^4.41.0",
77
- "@wordpress/dom": "^4.41.0",
78
- "@wordpress/element": "^6.41.0",
79
- "@wordpress/escape-html": "^3.41.0",
80
- "@wordpress/global-styles-engine": "^1.8.0",
81
- "@wordpress/hooks": "^4.41.0",
82
- "@wordpress/html-entities": "^4.41.0",
83
- "@wordpress/i18n": "^6.14.0",
84
- "@wordpress/icons": "^11.8.0",
85
- "@wordpress/image-cropper": "^1.5.0",
86
- "@wordpress/interactivity": "^6.41.0",
87
- "@wordpress/is-shallow-equal": "^5.41.0",
88
- "@wordpress/keyboard-shortcuts": "^5.41.0",
89
- "@wordpress/keycodes": "^4.41.0",
90
- "@wordpress/notices": "^5.41.0",
91
- "@wordpress/preferences": "^4.41.0",
92
- "@wordpress/priority-queue": "^3.41.0",
93
- "@wordpress/private-apis": "^1.41.0",
94
- "@wordpress/rich-text": "^7.41.0",
95
- "@wordpress/style-engine": "^2.41.0",
96
- "@wordpress/token-list": "^3.41.0",
97
- "@wordpress/upload-media": "^0.26.0",
98
- "@wordpress/url": "^4.41.0",
99
- "@wordpress/warning": "^3.41.0",
100
- "@wordpress/wordcount": "^4.41.0",
64
+ "@wordpress/a11y": "^4.41.1-next.v.202603102151.0+59e17f9ec",
65
+ "@wordpress/api-fetch": "^7.41.1-next.v.202603102151.0+59e17f9ec",
66
+ "@wordpress/base-styles": "^6.17.1-next.v.202603102151.0+59e17f9ec",
67
+ "@wordpress/blob": "^4.41.1-next.v.202603102151.0+59e17f9ec",
68
+ "@wordpress/block-serialization-default-parser": "^5.41.1-next.v.202603102151.0+59e17f9ec",
69
+ "@wordpress/blocks": "^15.14.1-next.v.202603102151.0+59e17f9ec",
70
+ "@wordpress/commands": "^1.41.1-next.v.202603102151.0+59e17f9ec",
71
+ "@wordpress/components": "^32.4.1-next.v.202603102151.0+59e17f9ec",
72
+ "@wordpress/compose": "^7.41.1-next.v.202603102151.0+59e17f9ec",
73
+ "@wordpress/data": "^10.41.1-next.v.202603102151.0+59e17f9ec",
74
+ "@wordpress/dataviews": "^13.1.1-next.v.202603102151.0+59e17f9ec",
75
+ "@wordpress/date": "^5.41.1-next.v.202603102151.0+59e17f9ec",
76
+ "@wordpress/deprecated": "^4.41.1-next.v.202603102151.0+59e17f9ec",
77
+ "@wordpress/dom": "^4.41.1-next.v.202603102151.0+59e17f9ec",
78
+ "@wordpress/element": "^6.41.1-next.v.202603102151.0+59e17f9ec",
79
+ "@wordpress/escape-html": "^3.41.1-next.v.202603102151.0+59e17f9ec",
80
+ "@wordpress/global-styles-engine": "^1.8.1-next.v.202603102151.0+59e17f9ec",
81
+ "@wordpress/hooks": "^4.41.1-next.v.202603102151.0+59e17f9ec",
82
+ "@wordpress/html-entities": "^4.41.1-next.v.202603102151.0+59e17f9ec",
83
+ "@wordpress/i18n": "^6.14.1-next.v.202603102151.0+59e17f9ec",
84
+ "@wordpress/icons": "^12.0.1-next.v.202603102151.0+59e17f9ec",
85
+ "@wordpress/image-cropper": "^1.5.1-next.v.202603102151.0+59e17f9ec",
86
+ "@wordpress/interactivity": "^6.41.2-next.v.202603102151.0+59e17f9ec",
87
+ "@wordpress/is-shallow-equal": "^5.41.1-next.v.202603102151.0+59e17f9ec",
88
+ "@wordpress/keyboard-shortcuts": "^5.41.1-next.v.202603102151.0+59e17f9ec",
89
+ "@wordpress/keycodes": "^4.41.1-next.v.202603102151.0+59e17f9ec",
90
+ "@wordpress/notices": "^5.41.1-next.v.202603102151.0+59e17f9ec",
91
+ "@wordpress/preferences": "^4.41.1-next.v.202603102151.0+59e17f9ec",
92
+ "@wordpress/priority-queue": "^3.41.1-next.v.202603102151.0+59e17f9ec",
93
+ "@wordpress/private-apis": "^1.41.1-next.v.202603102151.0+59e17f9ec",
94
+ "@wordpress/rich-text": "^7.41.1-next.v.202603102151.0+59e17f9ec",
95
+ "@wordpress/style-engine": "^2.41.1-next.v.202603102151.0+59e17f9ec",
96
+ "@wordpress/token-list": "^3.41.1-next.v.202603102151.0+59e17f9ec",
97
+ "@wordpress/upload-media": "^0.26.1-next.v.202603102151.0+59e17f9ec",
98
+ "@wordpress/url": "^4.41.1-next.v.202603102151.0+59e17f9ec",
99
+ "@wordpress/warning": "^3.41.1-next.v.202603102151.0+59e17f9ec",
100
+ "@wordpress/wordcount": "^4.41.1-next.v.202603102151.0+59e17f9ec",
101
101
  "change-case": "^4.1.2",
102
102
  "clsx": "^2.1.1",
103
103
  "colord": "^2.7.0",
@@ -124,5 +124,5 @@
124
124
  "publishConfig": {
125
125
  "access": "public"
126
126
  },
127
- "gitHead": "8bfc179b9aed74c0a6dd6e8edf7a49e40e4f87cc"
127
+ "gitHead": "86db21e727d89e8f0dbba9300d2f97fd22b08693"
128
128
  }
@@ -92,6 +92,7 @@ export default function BlockTools( {
92
92
  getBlockName,
93
93
  isGroupable,
94
94
  getEditedContentOnlySection,
95
+ canEditBlock,
95
96
  } = unlock( useSelect( blockEditorStore ) );
96
97
  const { getGroupingBlockName } = useSelect( blocksStore );
97
98
  const { showEmptyBlockSideInserter, showBlockToolbarPopover } =
@@ -234,7 +235,10 @@ export default function BlockTools( {
234
235
  if ( clientIds.length === 1 ) {
235
236
  const isContentOnly =
236
237
  getBlockEditingMode( clientIds[ 0 ] ) === 'contentOnly';
237
- const canRenameBlock = canRename && ! isContentOnly;
238
+ const canRenameBlock =
239
+ canRename &&
240
+ ! isContentOnly &&
241
+ canEditBlock( clientIds[ 0 ] );
238
242
  if ( canRenameBlock ) {
239
243
  event.preventDefault();
240
244
  setRenamingBlockClientId( clientIds[ 0 ] );
@@ -255,11 +259,14 @@ export default function BlockTools( {
255
259
  return;
256
260
  }
257
261
 
258
- // Don't allow visibility toggle for blocks that
259
- // are not in the default editing mode.
262
+ // Don't allow visibility toggle for blocks that are not in the
263
+ // default editing mode or when block editing is disabled
264
+ // (e.g. Revisions UI with isPreviewMode).
260
265
  if (
261
266
  clientIds.some(
262
- ( id ) => getBlockEditingMode( id ) !== 'default'
267
+ ( id ) =>
268
+ getBlockEditingMode( id ) !== 'default' ||
269
+ ! canEditBlock( id )
263
270
  )
264
271
  ) {
265
272
  return;
@@ -116,7 +116,6 @@ function getIframeSrc( resolvedAssets ) {
116
116
  <head>
117
117
  <meta charset="utf-8">
118
118
  <base href="${ window.location.href }">
119
- <script>window.frameElement._load()</script>
120
119
  <style>
121
120
  html{
122
121
  height: auto !important;
@@ -168,9 +167,6 @@ function Iframe( {
168
167
  const [ before, writingFlowRef, after ] = useWritingFlow();
169
168
 
170
169
  const setRef = useRefEffect( ( node ) => {
171
- node._load = () => {
172
- setIframeDocument( node.contentDocument );
173
- };
174
170
  let iFrameDocument;
175
171
  // Prevent the default browser action for files dropped outside of dropzones.
176
172
  function preventFileDropDefault( event ) {
@@ -218,6 +214,7 @@ function Iframe( {
218
214
  const { contentDocument } = node;
219
215
  const { documentElement } = contentDocument;
220
216
  iFrameDocument = contentDocument;
217
+ setIframeDocument( contentDocument );
221
218
 
222
219
  documentElement.classList.add( 'block-editor-iframe__html' );
223
220
 
@@ -257,7 +254,7 @@ function Iframe( {
257
254
  node.addEventListener( 'load', onLoad );
258
255
 
259
256
  return () => {
260
- delete node._load;
257
+ setIframeDocument( undefined );
261
258
  node.removeEventListener( 'load', onLoad );
262
259
  iFrameDocument?.removeEventListener(
263
260
  'dragover',
@@ -284,13 +281,29 @@ function Iframe( {
284
281
  } );
285
282
 
286
283
  const disabledRef = useDisabled( { isDisabled: ! readonly } );
287
- const bodyRef = useMergeRefs( [
284
+
285
+ const unguardedBodyRef = useMergeRefs( [
288
286
  useBubbleEvents( iframeDocument ),
289
287
  contentRef,
290
288
  writingFlowRef,
291
289
  disabledRef,
292
290
  ] );
293
291
 
292
+ // Attach the body ref only when the iframe document and window are available.
293
+ // When an iframe element is moved in the DOM, like when reordering a list,
294
+ // its `window` object is destroyed and recreated, and the `defaultView` field is
295
+ // briefly `null`. We need to guard for such calls of the ref callbacks.
296
+ const bodyRef = useRefEffect(
297
+ ( node ) => {
298
+ if ( node.ownerDocument.defaultView ) {
299
+ unguardedBodyRef( node );
300
+ return () => unguardedBodyRef( null );
301
+ }
302
+ return () => {};
303
+ },
304
+ [ unguardedBodyRef ]
305
+ );
306
+
294
307
  const src = getIframeSrc( resolvedAssets );
295
308
 
296
309
  // Make sure to not render the before and after focusable div elements in view
@@ -115,21 +115,15 @@ export function useMouseMoveTypingReset() {
115
115
  * field, presses ESC or TAB, or moves the mouse in the document.
116
116
  */
117
117
  export function useTypingObserver() {
118
- const { isTyping } = useSelect( ( select ) => {
119
- const { isTyping: _isTyping } = select( blockEditorStore );
120
- return {
121
- isTyping: _isTyping(),
122
- };
123
- }, [] );
118
+ const isTyping = useSelect(
119
+ ( select ) => select( blockEditorStore ).isTyping(),
120
+ []
121
+ );
124
122
  const { startTyping, stopTyping } = useDispatch( blockEditorStore );
125
123
 
126
124
  const ref1 = useMouseMoveTypingReset();
127
125
  const ref2 = useRefEffect(
128
126
  ( node ) => {
129
- const { ownerDocument } = node;
130
- const { defaultView } = ownerDocument;
131
- const selection = defaultView.getSelection();
132
-
133
127
  // Listeners to stop typing should only be added when typing.
134
128
  // Listeners to start typing should only be added when not typing.
135
129
  if ( isTyping ) {
@@ -147,7 +141,7 @@ export function useTypingObserver() {
147
141
  // before the keydown event, wait until after current stack
148
142
  // before evaluating whether typing is to be stopped. Otherwise,
149
143
  // typing will re-start.
150
- timerId = defaultView.setTimeout( () => {
144
+ timerId = node.ownerDocument.defaultView.setTimeout( () => {
151
145
  if ( ! isTextField( target ) ) {
152
146
  stopTyping();
153
147
  }
@@ -174,6 +168,8 @@ export function useTypingObserver() {
174
168
  * uncollapsed (shift) selection.
175
169
  */
176
170
  function stopTypingOnSelectionUncollapse() {
171
+ const selection =
172
+ node.ownerDocument.defaultView.getSelection();
177
173
  if ( ! selection.isCollapsed ) {
178
174
  stopTyping();
179
175
  }
@@ -182,13 +178,13 @@ export function useTypingObserver() {
182
178
  node.addEventListener( 'focus', stopTypingOnNonTextField );
183
179
  node.addEventListener( 'keydown', stopTypingOnEscapeKey );
184
180
 
185
- ownerDocument.addEventListener(
181
+ node.ownerDocument.addEventListener(
186
182
  'selectionchange',
187
183
  stopTypingOnSelectionUncollapse
188
184
  );
189
185
 
190
186
  return () => {
191
- defaultView.clearTimeout( timerId );
187
+ node.ownerDocument.defaultView.clearTimeout( timerId );
192
188
  node.removeEventListener(
193
189
  'focus',
194
190
  stopTypingOnNonTextField
@@ -197,7 +193,7 @@ export function useTypingObserver() {
197
193
  'keydown',
198
194
  stopTypingOnEscapeKey
199
195
  );
200
- ownerDocument.removeEventListener(
196
+ node.ownerDocument.removeEventListener(
201
197
  'selectionchange',
202
198
  stopTypingOnSelectionUncollapse
203
199
  );
@@ -20,6 +20,7 @@ import { BlockRefsProvider } from './block-refs-provider';
20
20
  import { unlock } from '../../lock-unlock';
21
21
  import KeyboardShortcuts from '../keyboard-shortcuts';
22
22
  import useMediaUploadSettings from './use-media-upload-settings';
23
+ import { mediaUploadOnSuccessKey } from '../../store/private-keys';
23
24
  import { SelectionContext } from './selection-context';
24
25
 
25
26
  /** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */
@@ -89,6 +90,7 @@ function shouldEnableClientSideMediaProcessing() {
89
90
  * or when adding a file to the editor via drag & drop.
90
91
  *
91
92
  * @param {WPDataRegistry} registry
93
+ * @param {Object} settings Block editor settings.
92
94
  * @param {Object} $3 Parameters object passed to the function.
93
95
  * @param {Array} $3.allowedTypes Array with the types of media that can be uploaded, if unset all types are allowed.
94
96
  * @param {Object} $3.additionalData Additional data to include in the request.
@@ -100,6 +102,7 @@ function shouldEnableClientSideMediaProcessing() {
100
102
  */
101
103
  function mediaUpload(
102
104
  registry,
105
+ settings,
103
106
  {
104
107
  allowedTypes,
105
108
  additionalData = {},
@@ -113,7 +116,10 @@ function mediaUpload(
113
116
  void registry.dispatch( uploadStore ).addItems( {
114
117
  files: Array.from( filesList ),
115
118
  onChange: onFileChange,
116
- onSuccess,
119
+ onSuccess: ( attachments ) => {
120
+ settings?.[ mediaUploadOnSuccessKey ]?.( attachments );
121
+ onSuccess?.( attachments );
122
+ },
117
123
  onBatchSuccess,
118
124
  onError: ( { message } ) => onError( message ),
119
125
  additionalData,
@@ -146,16 +152,38 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider(
146
152
  const isClientSideMediaEnabled =
147
153
  shouldEnableClientSideMediaProcessing();
148
154
 
155
+ // Nested providers (e.g. from useBlockPreview) inherit settings
156
+ // where mediaUpload has already been replaced with the
157
+ // interceptor. Detect this so we skip the replacement and
158
+ // MediaUploadProvider for them — see the longer comment below.
159
+ const isMediaUploadIntercepted =
160
+ !! _settings?.mediaUpload?.__isMediaUploadInterceptor;
161
+
149
162
  const settings = useMemo( () => {
150
- if ( isClientSideMediaEnabled && _settings?.mediaUpload ) {
163
+ if (
164
+ isClientSideMediaEnabled &&
165
+ _settings?.mediaUpload &&
166
+ ! isMediaUploadIntercepted
167
+ ) {
151
168
  // Create a new object so that the original props.settings.mediaUpload is not modified.
169
+ const interceptor = mediaUpload.bind(
170
+ null,
171
+ registry,
172
+ _settings
173
+ );
174
+ interceptor.__isMediaUploadInterceptor = true;
152
175
  return {
153
176
  ..._settings,
154
- mediaUpload: mediaUpload.bind( null, registry ),
177
+ mediaUpload: interceptor,
155
178
  };
156
179
  }
157
180
  return _settings;
158
- }, [ _settings, registry, isClientSideMediaEnabled ] );
181
+ }, [
182
+ _settings,
183
+ registry,
184
+ isClientSideMediaEnabled,
185
+ isMediaUploadIntercepted,
186
+ ] );
159
187
 
160
188
  const { __experimentalUpdateSettings } = unlock(
161
189
  useDispatch( blockEditorStore )
@@ -215,7 +243,21 @@ export const ExperimentalBlockEditorProvider = withRegistryProvider(
215
243
  </SelectionContext.Provider>
216
244
  );
217
245
 
218
- if ( isClientSideMediaEnabled ) {
246
+ // MediaUploadProvider writes the mediaUpload function from
247
+ // _settings into the shared upload-media store so the store can
248
+ // hand files off to the server. useMediaUploadSettings extracts
249
+ // mediaUpload from the original _settings prop — *before* the
250
+ // interceptor replacement above — so the store receives the
251
+ // real server-side upload function.
252
+ //
253
+ // Only the first (outermost) provider should do this.
254
+ // Nested providers (e.g. from useBlockPreview in
255
+ // core/post-template) inherit settings that already contain
256
+ // the interceptor, so their MediaUploadProvider would
257
+ // overwrite the store's server-side function with the
258
+ // interceptor, causing uploads to loop instead of reaching
259
+ // the server.
260
+ if ( isClientSideMediaEnabled && ! isMediaUploadIntercepted ) {
219
261
  return (
220
262
  <MediaUploadProvider
221
263
  settings={ mediaUploadSettings }
@@ -11,6 +11,7 @@
11
11
  position: absolute;
12
12
  width: 1px;
13
13
  word-wrap: normal !important;
14
+ word-break: normal !important;
14
15
  }
15
16
 
16
17
  .block-editor-responsive-block-control {
@@ -1,23 +1,17 @@
1
- /**
2
- * WordPress dependencies
3
- */
4
1
  import {
5
2
  privateApis as blocksPrivateApis,
6
3
  getBlockType,
7
4
  store as blocksStore,
8
5
  } from '@wordpress/blocks';
6
+ import { useDebounce } from '@wordpress/compose';
9
7
  import {
10
8
  __experimentalHStack as HStack,
11
9
  __experimentalTruncate as Truncate,
12
10
  } from '@wordpress/components';
13
- import { useSelect } from '@wordpress/data';
11
+ import { useDispatch, useSelect } from '@wordpress/data';
14
12
  import { DataForm } from '@wordpress/dataviews';
15
13
  import { useContext, useState, useMemo } from '@wordpress/element';
16
14
  import { __ } from '@wordpress/i18n';
17
-
18
- /**
19
- * Internal dependencies
20
- */
21
15
  import { store as blockEditorStore } from '../../store';
22
16
  import { unlock } from '../../lock-unlock';
23
17
  import BlockContext from '../../components/block-context';
@@ -55,16 +49,18 @@ function createConfiguredControl( ControlComponent, config = {} ) {
55
49
  * @param {string} props.clientId The clientId of the block.
56
50
  * @param {Object} props.blockType The blockType definition.
57
51
  * @param {Function} props.setAttributes Action to set the block's attributes.
58
- * @param {boolean} props.isCollapsed Whether the DataForm is rendered as 'collapsed' with only the first field
59
- * displayed by default. When collapsed a dropdown is displayed to allow
60
- * displaying additional fields. The block's title is displayed as the title.
61
- * The collapsed mode is often used when multiple BlockForms are shown together.
52
+ * @param {boolean} props.isMultiBlock Whether forms for multiple blocks are shown at the same time.
53
+ * This changes the behavior of the component:
54
+ * - Only the first field is shown for each block.
55
+ * - A dropdown is rendered allowing display of additional fields.
56
+ * - Hovering the block fields highlights the block in the canvas
57
+ * - Focusing a block field soft-selects the block in the canvas.
62
58
  */
63
59
  function BlockFields( {
64
60
  clientId,
65
61
  blockType,
66
62
  setAttributes,
67
- isCollapsed = false,
63
+ isMultiBlock = false,
68
64
  } ) {
69
65
  const blockTitle = useBlockDisplayTitle( {
70
66
  clientId,
@@ -103,9 +99,16 @@ function BlockFields( {
103
99
  },
104
100
  [ blockContext, clientId ]
105
101
  );
102
+ const { selectBlock, toggleBlockHighlight } =
103
+ useDispatch( blockEditorStore );
104
+
105
+ const debouncedToggleBlockHighlight = useDebounce(
106
+ toggleBlockHighlight,
107
+ 50
108
+ );
106
109
 
107
110
  const computedForm = useMemo( () => {
108
- if ( ! isCollapsed ) {
111
+ if ( ! isMultiBlock ) {
109
112
  return blockType?.[ formKey ];
110
113
  }
111
114
 
@@ -114,7 +117,7 @@ function BlockFields( {
114
117
  ...blockType?.[ formKey ],
115
118
  fields: [ blockType?.[ formKey ]?.fields?.[ 0 ] ],
116
119
  };
117
- }, [ blockType, isCollapsed ] );
120
+ }, [ blockType, isMultiBlock ] );
118
121
 
119
122
  const [ form, setForm ] = useState( computedForm );
120
123
 
@@ -182,10 +185,32 @@ function BlockFields( {
182
185
  };
183
186
 
184
187
  return (
185
- <div className="block-editor-block-fields__container">
188
+ <div
189
+ className="block-editor-block-fields__container"
190
+ onMouseEnter={
191
+ isMultiBlock
192
+ ? () => debouncedToggleBlockHighlight( clientId, true )
193
+ : undefined
194
+ }
195
+ onMouseLeave={ () =>
196
+ isMultiBlock
197
+ ? debouncedToggleBlockHighlight( clientId, false )
198
+ : undefined
199
+ }
200
+ onFocus={
201
+ isMultiBlock
202
+ ? () => {
203
+ selectBlock(
204
+ clientId,
205
+ null /* null to avoid focus on the block in the canvas */
206
+ );
207
+ }
208
+ : undefined
209
+ }
210
+ >
186
211
  <div className="block-editor-block-fields__header">
187
212
  <HStack spacing={ 1 }>
188
- { isCollapsed && (
213
+ { isMultiBlock && (
189
214
  <>
190
215
  <BlockIcon
191
216
  className="block-editor-block-fields__header-icon"
@@ -203,7 +228,7 @@ function BlockFields( {
203
228
  />
204
229
  </>
205
230
  ) }
206
- { ! isCollapsed && (
231
+ { ! isMultiBlock && (
207
232
  <h2 className="block-editor-block-fields__header-title">
208
233
  { __( 'Content' ) }
209
234
  </h2>
@@ -236,7 +261,7 @@ export function BlockFieldsPanel( props ) {
236
261
  <BlockFields
237
262
  { ...props }
238
263
  blockType={ blockType }
239
- isCollapsed={ isSelectionWithinCurrentSection }
264
+ isMultiBlock={ isSelectionWithinCurrentSection }
240
265
  />
241
266
  </InspectorControls>
242
267
  );
@@ -5,17 +5,15 @@
5
5
  @use "./rich-text/styles.scss" as *;
6
6
 
7
7
  .block-editor-block-fields__container {
8
- padding: 0 $grid-unit-20;
8
+ padding: 0 $grid-unit-20 $grid-unit-20;
9
9
 
10
10
  &:first-of-type {
11
11
  padding-block-start: $grid-unit-10;
12
- // Add border for the entire content controls and remove the similar border
13
- // for tools panel.
14
- border-top: $border-width solid $gray-200;
15
- }
16
-
17
- &:last-of-type {
18
- padding-block-end: $grid-unit-20;
12
+ /*
13
+ * Add border for the entire content controls and remove the similar border
14
+ * for tools panel.
15
+ */
16
+ border-block-start: $border-width solid $gray-200;
19
17
  }
20
18
  }
21
19
 
@@ -30,6 +28,6 @@
30
28
 
31
29
  .block-editor-block-fields__header-title {
32
30
  flex: 1;
33
- // Override the default margin on a h2 element.
31
+ /* Override the default margin on a h2 element. */
34
32
  margin: 0 !important;
35
33
  }