@wordpress/block-editor 10.1.0 → 10.1.1-next.4d3b314fd5.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 (76) hide show
  1. package/build/components/block-draggable/index.js +1 -1
  2. package/build/components/block-draggable/index.js.map +1 -1
  3. package/build/components/block-list/use-in-between-inserter.js +4 -4
  4. package/build/components/block-list/use-in-between-inserter.js.map +1 -1
  5. package/build/components/block-popover/index.js +0 -1
  6. package/build/components/block-popover/index.js.map +1 -1
  7. package/build/components/block-tools/index.js +1 -1
  8. package/build/components/block-tools/index.js.map +1 -1
  9. package/build/components/block-tools/use-block-toolbar-popover-props.js +6 -3
  10. package/build/components/block-tools/use-block-toolbar-popover-props.js.map +1 -1
  11. package/build/components/index.js +9 -0
  12. package/build/components/index.js.map +1 -1
  13. package/build/components/url-popover/image-url-input-ui.js +1 -1
  14. package/build/components/url-popover/image-url-input-ui.js.map +1 -1
  15. package/build/components/use-block-drop-zone/index.js +19 -1
  16. package/build/components/use-block-drop-zone/index.js.map +1 -1
  17. package/build/components/use-on-block-drop/index.js +62 -20
  18. package/build/components/use-on-block-drop/index.js.map +1 -1
  19. package/build/components/writing-flow/use-arrow-nav.js +14 -7
  20. package/build/components/writing-flow/use-arrow-nav.js.map +1 -1
  21. package/build/hooks/index.js +13 -1
  22. package/build/hooks/index.js.map +1 -1
  23. package/build/hooks/layout.js +76 -23
  24. package/build/hooks/layout.js.map +1 -1
  25. package/build/index.js +14 -0
  26. package/build/index.js.map +1 -1
  27. package/build/store/selectors.js +1 -1
  28. package/build/store/selectors.js.map +1 -1
  29. package/build-module/components/block-draggable/index.js +1 -1
  30. package/build-module/components/block-draggable/index.js.map +1 -1
  31. package/build-module/components/block-list/use-in-between-inserter.js +4 -4
  32. package/build-module/components/block-list/use-in-between-inserter.js.map +1 -1
  33. package/build-module/components/block-popover/index.js +0 -1
  34. package/build-module/components/block-popover/index.js.map +1 -1
  35. package/build-module/components/block-tools/index.js +1 -1
  36. package/build-module/components/block-tools/index.js.map +1 -1
  37. package/build-module/components/block-tools/use-block-toolbar-popover-props.js +6 -3
  38. package/build-module/components/block-tools/use-block-toolbar-popover-props.js.map +1 -1
  39. package/build-module/components/index.js +1 -0
  40. package/build-module/components/index.js.map +1 -1
  41. package/build-module/components/url-popover/image-url-input-ui.js +1 -1
  42. package/build-module/components/url-popover/image-url-input-ui.js.map +1 -1
  43. package/build-module/components/use-block-drop-zone/index.js +19 -1
  44. package/build-module/components/use-block-drop-zone/index.js.map +1 -1
  45. package/build-module/components/use-on-block-drop/index.js +62 -21
  46. package/build-module/components/use-on-block-drop/index.js.map +1 -1
  47. package/build-module/components/writing-flow/use-arrow-nav.js +14 -7
  48. package/build-module/components/writing-flow/use-arrow-nav.js.map +1 -1
  49. package/build-module/hooks/index.js +1 -0
  50. package/build-module/hooks/index.js.map +1 -1
  51. package/build-module/hooks/layout.js +73 -23
  52. package/build-module/hooks/layout.js.map +1 -1
  53. package/build-module/index.js +1 -1
  54. package/build-module/index.js.map +1 -1
  55. package/build-module/store/selectors.js +1 -1
  56. package/build-module/store/selectors.js.map +1 -1
  57. package/build-style/style-rtl.css +21 -1
  58. package/build-style/style.css +21 -1
  59. package/package.json +29 -29
  60. package/src/components/block-draggable/index.js +1 -1
  61. package/src/components/block-draggable/test/index.native.js +0 -9
  62. package/src/components/block-list/use-in-between-inserter.js +5 -5
  63. package/src/components/block-popover/index.js +0 -1
  64. package/src/components/block-preview/style.scss +13 -0
  65. package/src/components/block-tools/index.js +1 -1
  66. package/src/components/block-tools/use-block-toolbar-popover-props.js +6 -0
  67. package/src/components/index.js +1 -0
  68. package/src/components/url-popover/image-url-input-ui.js +1 -1
  69. package/src/components/use-block-drop-zone/index.js +26 -1
  70. package/src/components/use-on-block-drop/index.js +110 -35
  71. package/src/components/use-on-block-drop/test/index.js +33 -43
  72. package/src/components/writing-flow/use-arrow-nav.js +12 -8
  73. package/src/hooks/index.js +1 -0
  74. package/src/hooks/layout.js +64 -21
  75. package/src/index.js +2 -0
  76. package/src/store/selectors.js +1 -0
@@ -1216,6 +1216,16 @@
1216
1216
  display: none;
1217
1217
  }
1218
1218
 
1219
+ .block-editor-block-preview__container::after {
1220
+ content: "";
1221
+ position: absolute;
1222
+ top: 0;
1223
+ right: 0;
1224
+ left: 0;
1225
+ bottom: 0;
1226
+ z-index: 1;
1227
+ }
1228
+
1219
1229
  .block-editor-block-settings-menu__popover .components-dropdown-menu__menu {
1220
1230
  padding: 0;
1221
1231
  }
@@ -1765,9 +1775,19 @@
1765
1775
  bottom: 0;
1766
1776
  right: 0;
1767
1777
  pointer-events: none;
1768
- border: 1px dashed currentColor;
1769
1778
  border-radius: 2px;
1770
1779
  }
1780
+ .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > .block-list-appender:only-child::after::before, .is-layout-flow.block-editor-block-list__block:not(.is-selected) > .block-list-appender:only-child::after::before {
1781
+ content: "";
1782
+ position: absolute;
1783
+ top: 0;
1784
+ left: 0;
1785
+ bottom: 0;
1786
+ right: 0;
1787
+ pointer-events: none;
1788
+ background: currentColor;
1789
+ opacity: 0.1;
1790
+ }
1771
1791
  .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > .block-list-appender:only-child .block-editor-inserter, .is-layout-flow.block-editor-block-list__block:not(.is-selected) > .block-list-appender:only-child .block-editor-inserter {
1772
1792
  visibility: hidden;
1773
1793
  }
@@ -1216,6 +1216,16 @@
1216
1216
  display: none;
1217
1217
  }
1218
1218
 
1219
+ .block-editor-block-preview__container::after {
1220
+ content: "";
1221
+ position: absolute;
1222
+ top: 0;
1223
+ left: 0;
1224
+ right: 0;
1225
+ bottom: 0;
1226
+ z-index: 1;
1227
+ }
1228
+
1219
1229
  .block-editor-block-settings-menu__popover .components-dropdown-menu__menu {
1220
1230
  padding: 0;
1221
1231
  }
@@ -1765,9 +1775,19 @@
1765
1775
  bottom: 0;
1766
1776
  left: 0;
1767
1777
  pointer-events: none;
1768
- border: 1px dashed currentColor;
1769
1778
  border-radius: 2px;
1770
1779
  }
1780
+ .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > .block-list-appender:only-child::after::before, .is-layout-flow.block-editor-block-list__block:not(.is-selected) > .block-list-appender:only-child::after::before {
1781
+ content: "";
1782
+ position: absolute;
1783
+ top: 0;
1784
+ right: 0;
1785
+ bottom: 0;
1786
+ left: 0;
1787
+ pointer-events: none;
1788
+ background: currentColor;
1789
+ opacity: 0.1;
1790
+ }
1771
1791
  .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > .block-list-appender:only-child .block-editor-inserter, .is-layout-flow.block-editor-block-list__block:not(.is-selected) > .block-list-appender:only-child .block-editor-inserter {
1772
1792
  visibility: hidden;
1773
1793
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/block-editor",
3
- "version": "10.1.0",
3
+ "version": "10.1.1-next.4d3b314fd5.0",
4
4
  "description": "Generic block editor.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -33,32 +33,32 @@
33
33
  "dependencies": {
34
34
  "@babel/runtime": "^7.16.0",
35
35
  "@react-spring/web": "^9.4.5",
36
- "@wordpress/a11y": "^3.18.0",
37
- "@wordpress/api-fetch": "^6.15.0",
38
- "@wordpress/blob": "^3.18.0",
39
- "@wordpress/blocks": "^11.17.0",
40
- "@wordpress/components": "^21.1.0",
41
- "@wordpress/compose": "^5.16.0",
42
- "@wordpress/data": "^7.2.0",
43
- "@wordpress/date": "^4.18.0",
44
- "@wordpress/deprecated": "^3.18.0",
45
- "@wordpress/dom": "^3.18.0",
46
- "@wordpress/element": "^4.16.0",
47
- "@wordpress/hooks": "^3.18.0",
48
- "@wordpress/html-entities": "^3.18.0",
49
- "@wordpress/i18n": "^4.18.0",
50
- "@wordpress/icons": "^9.9.0",
51
- "@wordpress/is-shallow-equal": "^4.18.0",
52
- "@wordpress/keyboard-shortcuts": "^3.16.0",
53
- "@wordpress/keycodes": "^3.18.0",
54
- "@wordpress/notices": "^3.18.0",
55
- "@wordpress/rich-text": "^5.16.0",
56
- "@wordpress/shortcode": "^3.18.0",
57
- "@wordpress/style-engine": "^1.1.0",
58
- "@wordpress/token-list": "^2.18.0",
59
- "@wordpress/url": "^3.19.0",
60
- "@wordpress/warning": "^2.18.0",
61
- "@wordpress/wordcount": "^3.18.0",
36
+ "@wordpress/a11y": "^3.18.1-next.4d3b314fd5.0",
37
+ "@wordpress/api-fetch": "^6.15.1-next.4d3b314fd5.0",
38
+ "@wordpress/blob": "^3.18.1-next.4d3b314fd5.0",
39
+ "@wordpress/blocks": "^11.18.1-next.4d3b314fd5.0",
40
+ "@wordpress/components": "^21.1.2-next.4d3b314fd5.0",
41
+ "@wordpress/compose": "^5.16.1-next.4d3b314fd5.0",
42
+ "@wordpress/data": "^7.2.1-next.4d3b314fd5.0",
43
+ "@wordpress/date": "^4.18.1-next.4d3b314fd5.0",
44
+ "@wordpress/deprecated": "^3.18.1-next.4d3b314fd5.0",
45
+ "@wordpress/dom": "^3.18.1-next.4d3b314fd5.0",
46
+ "@wordpress/element": "^4.16.1-next.4d3b314fd5.0",
47
+ "@wordpress/hooks": "^3.18.1-next.4d3b314fd5.0",
48
+ "@wordpress/html-entities": "^3.18.1-next.4d3b314fd5.0",
49
+ "@wordpress/i18n": "^4.18.1-next.4d3b314fd5.0",
50
+ "@wordpress/icons": "^9.9.1-next.4d3b314fd5.0",
51
+ "@wordpress/is-shallow-equal": "^4.18.1-next.4d3b314fd5.0",
52
+ "@wordpress/keyboard-shortcuts": "^3.16.1-next.4d3b314fd5.0",
53
+ "@wordpress/keycodes": "^3.18.1-next.4d3b314fd5.0",
54
+ "@wordpress/notices": "^3.18.1-next.4d3b314fd5.0",
55
+ "@wordpress/rich-text": "^5.16.1-next.4d3b314fd5.0",
56
+ "@wordpress/shortcode": "^3.18.1-next.4d3b314fd5.0",
57
+ "@wordpress/style-engine": "^1.1.1-next.4d3b314fd5.0",
58
+ "@wordpress/token-list": "^2.18.1-next.4d3b314fd5.0",
59
+ "@wordpress/url": "^3.19.1-next.4d3b314fd5.0",
60
+ "@wordpress/warning": "^2.18.1-next.4d3b314fd5.0",
61
+ "@wordpress/wordcount": "^3.18.1-next.4d3b314fd5.0",
62
62
  "change-case": "^4.1.2",
63
63
  "classnames": "^2.3.1",
64
64
  "colord": "^2.7.0",
@@ -67,7 +67,7 @@
67
67
  "inherits": "^2.0.3",
68
68
  "lodash": "^4.17.21",
69
69
  "react-autosize-textarea": "^7.1.0",
70
- "react-easy-crop": "^3.0.0",
70
+ "react-easy-crop": "^4.5.1",
71
71
  "rememo": "^4.0.0",
72
72
  "remove-accents": "^0.4.2",
73
73
  "traverse": "^0.6.6"
@@ -79,5 +79,5 @@
79
79
  "publishConfig": {
80
80
  "access": "public"
81
81
  },
82
- "gitHead": "23e136283fa1d3b8d9d8b33869f871ad5eb77726"
82
+ "gitHead": "25054766423cb49d959eb656c2533530073ff5c2"
83
83
  }
@@ -52,7 +52,7 @@ const BlockDraggable = ( {
52
52
  }, [] );
53
53
 
54
54
  if ( ! isDraggable ) {
55
- return children( { isDraggable: false } );
55
+ return children( { draggable: false } );
56
56
  }
57
57
 
58
58
  const transferData = {
@@ -29,15 +29,6 @@ import {
29
29
  getDraggableChip,
30
30
  } from './helpers';
31
31
 
32
- // Mock throttle to allow updating the dragging position on every "onDragOver" event.
33
- jest.mock( 'lodash', () => ( {
34
- ...jest.requireActual( 'lodash' ),
35
- throttle: ( fn ) => {
36
- fn.cancel = jest.fn();
37
- return fn;
38
- },
39
- } ) );
40
-
41
32
  beforeAll( () => {
42
33
  // Register all core blocks
43
34
  registerCoreBlocks();
@@ -83,19 +83,19 @@ export function useInBetweenInserter() {
83
83
  const orientation =
84
84
  getBlockListSettings( rootClientId )?.orientation ||
85
85
  'vertical';
86
- const rect = event.target.getBoundingClientRect();
87
- const offsetTop = event.clientY - rect.top;
88
- const offsetLeft = event.clientX - rect.left;
86
+ const offsetTop = event.clientY;
87
+ const offsetLeft = event.clientX;
89
88
 
90
89
  const children = Array.from( event.target.children );
91
90
  let element = children.find( ( blockEl ) => {
91
+ const blockElRect = blockEl.getBoundingClientRect();
92
92
  return (
93
93
  ( blockEl.classList.contains( 'wp-block' ) &&
94
94
  orientation === 'vertical' &&
95
- blockEl.offsetTop > offsetTop ) ||
95
+ blockElRect.top > offsetTop ) ||
96
96
  ( blockEl.classList.contains( 'wp-block' ) &&
97
97
  orientation === 'horizontal' &&
98
- blockEl.offsetLeft > offsetLeft )
98
+ blockElRect.left > offsetLeft )
99
99
  );
100
100
  } );
101
101
 
@@ -143,7 +143,6 @@ function BlockPopover(
143
143
  <Popover
144
144
  ref={ mergedRefs }
145
145
  animate={ false }
146
- position="top right left"
147
146
  focusOnMount={ false }
148
147
  anchor={ popoverAnchor }
149
148
  // Render in the old slot if needed for backward compatibility,
@@ -64,3 +64,16 @@
64
64
  display: none;
65
65
  }
66
66
  }
67
+
68
+ // The goal of this pseudo-element is to cover the iframe
69
+ // otherwise it won't be able to drag elements containing
70
+ // the preview iframe in Safari.
71
+ .block-editor-block-preview__container::after {
72
+ content: "";
73
+ position: absolute;
74
+ top: 0;
75
+ left: 0;
76
+ right: 0;
77
+ bottom: 0;
78
+ z-index: 1;
79
+ }
@@ -124,7 +124,7 @@ export default function BlockTools( {
124
124
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
125
125
  <div { ...props } onKeyDown={ onKeyDown }>
126
126
  <InsertionPointOpenRef.Provider value={ useRef( false ) }>
127
- { ! isTyping && ! isZoomOutMode && (
127
+ { ! isTyping && (
128
128
  <InsertionPoint
129
129
  __unstableContentRef={ __unstableContentRef }
130
130
  />
@@ -11,10 +11,15 @@ import { useCallback, useLayoutEffect, useState } from '@wordpress/element';
11
11
  import { store as blockEditorStore } from '../../store';
12
12
  import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs';
13
13
 
14
+ const COMMON_PROPS = {
15
+ placement: 'top-start',
16
+ };
17
+
14
18
  // By default the toolbar sets the `shift` prop. If the user scrolls the page
15
19
  // down the toolbar will stay on screen by adopting a sticky position at the
16
20
  // top of the viewport.
17
21
  const DEFAULT_PROPS = {
22
+ ...COMMON_PROPS,
18
23
  flip: false,
19
24
  shift: true,
20
25
  };
@@ -25,6 +30,7 @@ const DEFAULT_PROPS = {
25
30
  // the block. This only happens if the block is smaller than the viewport, as
26
31
  // otherwise the toolbar will be off-screen.
27
32
  const RESTRICTED_HEIGHT_PROPS = {
33
+ ...COMMON_PROPS,
28
34
  flip: true,
29
35
  shift: false,
30
36
  };
@@ -155,6 +155,7 @@ export {
155
155
  export { default as __experimentalBlockPatternsList } from './block-patterns-list';
156
156
  export { default as __experimentalPublishDateTimePicker } from './publish-date-time-picker';
157
157
  export { default as __experimentalInspectorPopoverHeader } from './inspector-popover-header';
158
+ export { default as __experimentalUseOnBlockDrop } from './use-on-block-drop';
158
159
 
159
160
  /*
160
161
  * State Related Components
@@ -221,7 +221,7 @@ const ImageURLInputUI = ( {
221
221
  checked={ linkTarget === '_blank' }
222
222
  />
223
223
  <TextControl
224
- label={ __( 'Link Rel' ) }
224
+ label={ __( 'Link rel' ) }
225
225
  value={ rel ?? '' }
226
226
  onChange={ onSetLinkRel }
227
227
  />
@@ -73,6 +73,20 @@ export function getNearestBlockIndex( elements, position, orientation ) {
73
73
  return candidateIndex;
74
74
  }
75
75
 
76
+ /**
77
+ * Determine if the element is an empty paragraph block.
78
+ *
79
+ * @param {?HTMLElement} element The element being tested.
80
+ * @return {boolean} True or False.
81
+ */
82
+ function isEmptyParagraph( element ) {
83
+ return (
84
+ !! element &&
85
+ element.dataset.type === 'core/paragraph' &&
86
+ element.dataset.empty === 'true'
87
+ );
88
+ }
89
+
76
90
  /**
77
91
  * @typedef {Object} WPBlockDropZoneConfig
78
92
  * @property {string} rootClientId The root client id for the block list.
@@ -130,7 +144,18 @@ export default function useBlockDropZone( {
130
144
 
131
145
  setTargetBlockIndex( targetIndex === undefined ? 0 : targetIndex );
132
146
 
133
- if ( targetIndex !== null ) {
147
+ if ( targetIndex !== undefined ) {
148
+ const nextBlock = blockElements[ targetIndex ];
149
+ const previousBlock = blockElements[ targetIndex - 1 ];
150
+
151
+ // Don't show the insertion point when it's near an empty paragraph block.
152
+ if (
153
+ isEmptyParagraph( nextBlock ) ||
154
+ isEmptyParagraph( previousBlock )
155
+ ) {
156
+ return;
157
+ }
158
+
134
159
  showInsertionPoint( targetRootClientId, targetIndex );
135
160
  }
136
161
  }, [] ),
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
+ import { useCallback } from '@wordpress/element';
4
5
  import {
5
6
  cloneBlock,
6
7
  findTransform,
7
8
  getBlockTransforms,
8
9
  pasteHandler,
9
10
  } from '@wordpress/blocks';
10
- import { useDispatch, useSelect } from '@wordpress/data';
11
+ import { useDispatch, useSelect, useRegistry } from '@wordpress/data';
11
12
  import { getFilesFromDataTransfer } from '@wordpress/dom';
12
13
 
13
14
  /**
@@ -56,8 +57,8 @@ export function parseDropEvent( event ) {
56
57
  * @param {number} targetBlockIndex The index where the block(s) will be inserted.
57
58
  * @param {Function} getBlockIndex A function that gets the index of a block.
58
59
  * @param {Function} getClientIdsOfDescendants A function that gets the client ids of descendant blocks.
59
- * @param {Function} moveBlocksToPosition A function that moves blocks.
60
- * @param {Function} insertBlocks A function that inserts blocks.
60
+ * @param {Function} moveBlocks A function that moves blocks.
61
+ * @param {Function} insertOrReplaceBlocks A function that inserts or replaces blocks.
61
62
  * @param {Function} clearSelectedBlock A function that clears block selection.
62
63
  * @return {Function} The event handler for a block drop event.
63
64
  */
@@ -66,8 +67,8 @@ export function onBlockDrop(
66
67
  targetBlockIndex,
67
68
  getBlockIndex,
68
69
  getClientIdsOfDescendants,
69
- moveBlocksToPosition,
70
- insertBlocks,
70
+ moveBlocks,
71
+ insertOrReplaceBlocks,
71
72
  clearSelectedBlock
72
73
  ) {
73
74
  return ( event ) => {
@@ -84,13 +85,7 @@ export function onBlockDrop(
84
85
  const blocksToInsert = blocks.map( ( block ) =>
85
86
  cloneBlock( block )
86
87
  );
87
- insertBlocks(
88
- blocksToInsert,
89
- targetBlockIndex,
90
- targetRootClientId,
91
- true,
92
- null
93
- );
88
+ insertOrReplaceBlocks( blocksToInsert, true, null );
94
89
  }
95
90
 
96
91
  // If the user is moving a block.
@@ -128,12 +123,7 @@ export function onBlockDrop(
128
123
  ? targetBlockIndex - draggedBlockCount
129
124
  : targetBlockIndex;
130
125
 
131
- moveBlocksToPosition(
132
- sourceClientIds,
133
- sourceRootClientId,
134
- targetRootClientId,
135
- insertIndex
136
- );
126
+ moveBlocks( sourceClientIds, sourceRootClientId, insertIndex );
137
127
  }
138
128
  };
139
129
  }
@@ -146,7 +136,7 @@ export function onBlockDrop(
146
136
  * @param {boolean} hasUploadPermissions Whether the user has upload permissions.
147
137
  * @param {Function} updateBlockAttributes A function that updates a block's attributes.
148
138
  * @param {Function} canInsertBlockType A function that returns checks whether a block type can be inserted.
149
- * @param {Function} insertBlocks A function that inserts blocks.
139
+ * @param {Function} insertOrReplaceBlocks A function that inserts or replaces blocks.
150
140
  *
151
141
  * @return {Function} The event handler for a block-related file drop event.
152
142
  */
@@ -156,7 +146,7 @@ export function onFilesDrop(
156
146
  hasUploadPermissions,
157
147
  updateBlockAttributes,
158
148
  canInsertBlockType,
159
- insertBlocks
149
+ insertOrReplaceBlocks
160
150
  ) {
161
151
  return ( files ) => {
162
152
  if ( ! hasUploadPermissions ) {
@@ -176,7 +166,7 @@ export function onFilesDrop(
176
166
  files,
177
167
  updateBlockAttributes
178
168
  );
179
- insertBlocks( blocks, targetBlockIndex, targetRootClientId );
169
+ insertOrReplaceBlocks( blocks );
180
170
  }
181
171
  };
182
172
  }
@@ -184,22 +174,22 @@ export function onFilesDrop(
184
174
  /**
185
175
  * A function that returns an event handler function for block-related HTML drop events.
186
176
  *
187
- * @param {string} targetRootClientId The root client id where the block(s) will be inserted.
188
- * @param {number} targetBlockIndex The index where the block(s) will be inserted.
189
- * @param {Function} insertBlocks A function that inserts blocks.
177
+ * @param {string} targetRootClientId The root client id where the block(s) will be inserted.
178
+ * @param {number} targetBlockIndex The index where the block(s) will be inserted.
179
+ * @param {Function} insertOrReplaceBlocks A function that inserts or replaces blocks.
190
180
  *
191
181
  * @return {Function} The event handler for a block-related HTML drop event.
192
182
  */
193
183
  export function onHTMLDrop(
194
184
  targetRootClientId,
195
185
  targetBlockIndex,
196
- insertBlocks
186
+ insertOrReplaceBlocks
197
187
  ) {
198
188
  return ( HTML ) => {
199
189
  const blocks = pasteHandler( { HTML, mode: 'BLOCKS' } );
200
190
 
201
191
  if ( blocks.length ) {
202
- insertBlocks( blocks, targetBlockIndex, targetRootClientId );
192
+ insertOrReplaceBlocks( blocks );
203
193
  }
204
194
  };
205
195
  }
@@ -207,32 +197,117 @@ export function onHTMLDrop(
207
197
  /**
208
198
  * A React hook for handling block drop events.
209
199
  *
210
- * @param {string} targetRootClientId The root client id where the block(s) will be inserted.
211
- * @param {number} targetBlockIndex The index where the block(s) will be inserted.
200
+ * @typedef {'insert'|'replace'} DropAction The type of action to perform on drop.
201
+ *
202
+ * @param {string} targetRootClientId The root client id where the block(s) will be inserted.
203
+ * @param {number} targetBlockIndex The index where the block(s) will be inserted.
204
+ * @param {Object} options The optional options.
205
+ * @param {DropAction} options.action The type of action to perform on drop. Could be `insert` or `replace` for now.
212
206
  *
213
207
  * @return {Object} An object that contains the event handlers `onDrop`, `onFilesDrop` and `onHTMLDrop`.
214
208
  */
215
- export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) {
209
+ export default function useOnBlockDrop(
210
+ targetRootClientId,
211
+ targetBlockIndex,
212
+ options = {}
213
+ ) {
214
+ const { action = 'insert' } = options;
216
215
  const hasUploadPermissions = useSelect(
217
216
  ( select ) => select( blockEditorStore ).getSettings().mediaUpload,
218
217
  []
219
218
  );
220
- const { canInsertBlockType, getBlockIndex, getClientIdsOfDescendants } =
221
- useSelect( blockEditorStore );
219
+ const {
220
+ canInsertBlockType,
221
+ getBlockIndex,
222
+ getClientIdsOfDescendants,
223
+ getBlockOrder,
224
+ getBlocksByClientId,
225
+ } = useSelect( blockEditorStore );
222
226
  const {
223
227
  insertBlocks,
224
228
  moveBlocksToPosition,
225
229
  updateBlockAttributes,
226
230
  clearSelectedBlock,
231
+ replaceBlocks,
232
+ removeBlocks,
227
233
  } = useDispatch( blockEditorStore );
234
+ const registry = useRegistry();
235
+
236
+ const insertOrReplaceBlocks = useCallback(
237
+ ( blocks, updateSelection = true, initialPosition = 0 ) => {
238
+ if ( action === 'replace' ) {
239
+ const clientIds = getBlockOrder( targetRootClientId );
240
+ const clientId = clientIds[ targetBlockIndex ];
241
+
242
+ replaceBlocks( clientId, blocks, undefined, initialPosition );
243
+ } else {
244
+ insertBlocks(
245
+ blocks,
246
+ targetBlockIndex,
247
+ targetRootClientId,
248
+ updateSelection,
249
+ initialPosition
250
+ );
251
+ }
252
+ },
253
+ [
254
+ action,
255
+ getBlockOrder,
256
+ insertBlocks,
257
+ replaceBlocks,
258
+ targetBlockIndex,
259
+ targetRootClientId,
260
+ ]
261
+ );
262
+
263
+ const moveBlocks = useCallback(
264
+ ( sourceClientIds, sourceRootClientId, insertIndex ) => {
265
+ if ( action === 'replace' ) {
266
+ const sourceBlocks = getBlocksByClientId( sourceClientIds );
267
+ const targetBlockClientIds =
268
+ getBlockOrder( targetRootClientId );
269
+ const targetBlockClientId =
270
+ targetBlockClientIds[ targetBlockIndex ];
271
+
272
+ registry.batch( () => {
273
+ // Remove the source blocks.
274
+ removeBlocks( sourceClientIds, false );
275
+ // Replace the target block with the source blocks.
276
+ replaceBlocks(
277
+ targetBlockClientId,
278
+ sourceBlocks,
279
+ undefined,
280
+ 0
281
+ );
282
+ } );
283
+ } else {
284
+ moveBlocksToPosition(
285
+ sourceClientIds,
286
+ sourceRootClientId,
287
+ targetRootClientId,
288
+ insertIndex
289
+ );
290
+ }
291
+ },
292
+ [
293
+ action,
294
+ getBlockOrder,
295
+ getBlocksByClientId,
296
+ insertBlocks,
297
+ moveBlocksToPosition,
298
+ removeBlocks,
299
+ targetBlockIndex,
300
+ targetRootClientId,
301
+ ]
302
+ );
228
303
 
229
304
  const _onDrop = onBlockDrop(
230
305
  targetRootClientId,
231
306
  targetBlockIndex,
232
307
  getBlockIndex,
233
308
  getClientIdsOfDescendants,
234
- moveBlocksToPosition,
235
- insertBlocks,
309
+ moveBlocks,
310
+ insertOrReplaceBlocks,
236
311
  clearSelectedBlock
237
312
  );
238
313
  const _onFilesDrop = onFilesDrop(
@@ -241,12 +316,12 @@ export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) {
241
316
  hasUploadPermissions,
242
317
  updateBlockAttributes,
243
318
  canInsertBlockType,
244
- insertBlocks
319
+ insertOrReplaceBlocks
245
320
  );
246
321
  const _onHTMLDrop = onHTMLDrop(
247
322
  targetRootClientId,
248
323
  targetBlockIndex,
249
- insertBlocks
324
+ insertOrReplaceBlocks
250
325
  );
251
326
 
252
327
  return ( event ) => {