@wordpress/block-editor 14.3.1 → 14.3.3

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 (137) hide show
  1. package/README.md +2 -2
  2. package/build/components/block-heading-level-dropdown/index.js +3 -1
  3. package/build/components/block-heading-level-dropdown/index.js.map +1 -1
  4. package/build/components/block-list/block.js +27 -4
  5. package/build/components/block-list/block.js.map +1 -1
  6. package/build/components/block-list/use-block-props/use-focus-first-element.js +0 -1
  7. package/build/components/block-list/use-block-props/use-focus-first-element.js.map +1 -1
  8. package/build/components/block-list/use-block-props/use-zoom-out-mode-exit.js +9 -4
  9. package/build/components/block-list/use-block-props/use-zoom-out-mode-exit.js.map +1 -1
  10. package/build/components/block-list/zoom-out-separator.js +18 -1
  11. package/build/components/block-list/zoom-out-separator.js.map +1 -1
  12. package/build/components/block-switcher/utils.js +1 -1
  13. package/build/components/block-switcher/utils.js.map +1 -1
  14. package/build/components/block-tools/zoom-out-toolbar.js +5 -2
  15. package/build/components/block-tools/zoom-out-toolbar.js.map +1 -1
  16. package/build/components/block-variation-transforms/index.js +12 -6
  17. package/build/components/block-variation-transforms/index.js.map +1 -1
  18. package/build/components/rich-text/event-listeners/paste-handler.js +2 -13
  19. package/build/components/rich-text/event-listeners/paste-handler.js.map +1 -1
  20. package/build/components/rich-text/index.js +34 -20
  21. package/build/components/rich-text/index.js.map +1 -1
  22. package/build/components/tool-selector/index.js +2 -1
  23. package/build/components/tool-selector/index.js.map +1 -1
  24. package/build/components/writing-flow/index.js +1 -2
  25. package/build/components/writing-flow/index.js.map +1 -1
  26. package/build/components/writing-flow/use-arrow-nav.js +1 -4
  27. package/build/components/writing-flow/use-arrow-nav.js.map +1 -1
  28. package/build/components/writing-flow/use-input.js +1 -31
  29. package/build/components/writing-flow/use-input.js.map +1 -1
  30. package/build/components/writing-flow/use-select-all.js +1 -14
  31. package/build/components/writing-flow/use-select-all.js.map +1 -1
  32. package/build/components/writing-flow/use-selection-observer.js +2 -6
  33. package/build/components/writing-flow/use-selection-observer.js.map +1 -1
  34. package/build/components/writing-flow/utils.js +0 -27
  35. package/build/components/writing-flow/utils.js.map +1 -1
  36. package/build/hooks/layout.js +12 -8
  37. package/build/hooks/layout.js.map +1 -1
  38. package/build/hooks/use-bindings-attributes.js +22 -28
  39. package/build/hooks/use-bindings-attributes.js.map +1 -1
  40. package/build/hooks/use-zoom-out.js +20 -23
  41. package/build/hooks/use-zoom-out.js.map +1 -1
  42. package/build/store/private-actions.js +25 -0
  43. package/build/store/private-actions.js.map +1 -1
  44. package/build/store/private-selectors.js +22 -0
  45. package/build/store/private-selectors.js.map +1 -1
  46. package/build/store/reducer.js +21 -1
  47. package/build/store/reducer.js.map +1 -1
  48. package/build/store/selectors.js +12 -5
  49. package/build/store/selectors.js.map +1 -1
  50. package/build-module/components/block-heading-level-dropdown/index.js +3 -1
  51. package/build-module/components/block-heading-level-dropdown/index.js.map +1 -1
  52. package/build-module/components/block-list/block.js +28 -7
  53. package/build-module/components/block-list/block.js.map +1 -1
  54. package/build-module/components/block-list/use-block-props/use-focus-first-element.js +0 -1
  55. package/build-module/components/block-list/use-block-props/use-focus-first-element.js.map +1 -1
  56. package/build-module/components/block-list/use-block-props/use-zoom-out-mode-exit.js +9 -4
  57. package/build-module/components/block-list/use-block-props/use-zoom-out-mode-exit.js.map +1 -1
  58. package/build-module/components/block-list/zoom-out-separator.js +18 -1
  59. package/build-module/components/block-list/zoom-out-separator.js.map +1 -1
  60. package/build-module/components/block-switcher/utils.js +1 -1
  61. package/build-module/components/block-switcher/utils.js.map +1 -1
  62. package/build-module/components/block-tools/zoom-out-toolbar.js +5 -2
  63. package/build-module/components/block-tools/zoom-out-toolbar.js.map +1 -1
  64. package/build-module/components/block-variation-transforms/index.js +12 -6
  65. package/build-module/components/block-variation-transforms/index.js.map +1 -1
  66. package/build-module/components/rich-text/event-listeners/paste-handler.js +2 -13
  67. package/build-module/components/rich-text/event-listeners/paste-handler.js.map +1 -1
  68. package/build-module/components/rich-text/index.js +34 -20
  69. package/build-module/components/rich-text/index.js.map +1 -1
  70. package/build-module/components/tool-selector/index.js +2 -1
  71. package/build-module/components/tool-selector/index.js.map +1 -1
  72. package/build-module/components/writing-flow/index.js +1 -2
  73. package/build-module/components/writing-flow/index.js.map +1 -1
  74. package/build-module/components/writing-flow/use-arrow-nav.js +1 -4
  75. package/build-module/components/writing-flow/use-arrow-nav.js.map +1 -1
  76. package/build-module/components/writing-flow/use-input.js +1 -31
  77. package/build-module/components/writing-flow/use-input.js.map +1 -1
  78. package/build-module/components/writing-flow/use-select-all.js +1 -14
  79. package/build-module/components/writing-flow/use-select-all.js.map +1 -1
  80. package/build-module/components/writing-flow/use-selection-observer.js +2 -6
  81. package/build-module/components/writing-flow/use-selection-observer.js.map +1 -1
  82. package/build-module/components/writing-flow/utils.js +0 -26
  83. package/build-module/components/writing-flow/utils.js.map +1 -1
  84. package/build-module/hooks/layout.js +13 -9
  85. package/build-module/hooks/layout.js.map +1 -1
  86. package/build-module/hooks/use-bindings-attributes.js +22 -28
  87. package/build-module/hooks/use-bindings-attributes.js.map +1 -1
  88. package/build-module/hooks/use-zoom-out.js +20 -23
  89. package/build-module/hooks/use-zoom-out.js.map +1 -1
  90. package/build-module/store/private-actions.js +23 -0
  91. package/build-module/store/private-actions.js.map +1 -1
  92. package/build-module/store/private-selectors.js +20 -0
  93. package/build-module/store/private-selectors.js.map +1 -1
  94. package/build-module/store/reducer.js +20 -1
  95. package/build-module/store/reducer.js.map +1 -1
  96. package/build-module/store/selectors.js +12 -5
  97. package/build-module/store/selectors.js.map +1 -1
  98. package/build-style/content-rtl.css +10 -0
  99. package/build-style/content.css +10 -0
  100. package/build-style/style-rtl.css +4 -0
  101. package/build-style/style.css +4 -0
  102. package/package.json +20 -20
  103. package/src/components/block-heading-level-dropdown/index.js +7 -1
  104. package/src/components/block-list/block.js +47 -11
  105. package/src/components/block-list/content.scss +12 -0
  106. package/src/components/block-list/use-block-props/use-focus-first-element.js +0 -1
  107. package/src/components/block-list/use-block-props/use-zoom-out-mode-exit.js +7 -3
  108. package/src/components/block-list/zoom-out-separator.js +14 -1
  109. package/src/components/block-switcher/test/use-transformed.patterns.js +3 -3
  110. package/src/components/block-switcher/test/utils.js +3 -3
  111. package/src/components/block-switcher/utils.js +1 -1
  112. package/src/components/block-toolbar/style.scss +7 -0
  113. package/src/components/block-tools/zoom-out-toolbar.js +5 -2
  114. package/src/components/block-variation-transforms/index.js +13 -4
  115. package/src/components/rich-text/event-listeners/paste-handler.js +2 -7
  116. package/src/components/rich-text/index.js +36 -23
  117. package/src/components/tool-selector/index.js +4 -1
  118. package/src/components/writing-flow/index.js +0 -2
  119. package/src/components/writing-flow/use-arrow-nav.js +2 -9
  120. package/src/components/writing-flow/use-input.js +1 -36
  121. package/src/components/writing-flow/use-select-all.js +1 -18
  122. package/src/components/writing-flow/use-selection-observer.js +3 -14
  123. package/src/components/writing-flow/utils.js +0 -30
  124. package/src/hooks/layout.js +17 -12
  125. package/src/hooks/use-bindings-attributes.js +26 -32
  126. package/src/hooks/use-zoom-out.js +21 -27
  127. package/src/store/private-actions.js +23 -0
  128. package/src/store/private-selectors.js +20 -0
  129. package/src/store/reducer.js +20 -0
  130. package/src/store/selectors.js +13 -8
  131. package/src/store/test/private-selectors.js +2 -2
  132. package/src/store/test/selectors.js +10 -6
  133. package/build/components/writing-flow/use-event-redirect.js +0 -66
  134. package/build/components/writing-flow/use-event-redirect.js.map +0 -1
  135. package/build-module/components/writing-flow/use-event-redirect.js +0 -60
  136. package/build-module/components/writing-flow/use-event-redirect.js.map +0 -1
  137. package/src/components/writing-flow/use-event-redirect.js +0 -72
@@ -19,7 +19,6 @@ import { useRefEffect } from '@wordpress/compose';
19
19
  */
20
20
  import { getBlockClientId, isInSameBlock } from '../../utils/dom';
21
21
  import { store as blockEditorStore } from '../../store';
22
- import { getSelectionRoot } from './utils';
23
22
 
24
23
  /**
25
24
  * Returns true if the element should consider edge navigation upon a keyboard
@@ -191,7 +190,8 @@ export default function useArrowNav() {
191
190
  return;
192
191
  }
193
192
 
194
- const { keyCode, shiftKey, ctrlKey, altKey, metaKey } = event;
193
+ const { keyCode, target, shiftKey, ctrlKey, altKey, metaKey } =
194
+ event;
195
195
  const isUp = keyCode === UP;
196
196
  const isDown = keyCode === DOWN;
197
197
  const isLeft = keyCode === LEFT;
@@ -233,11 +233,6 @@ export default function useArrowNav() {
233
233
  return;
234
234
  }
235
235
 
236
- const target =
237
- ownerDocument.activeElement === node
238
- ? getSelectionRoot( ownerDocument )
239
- : event.target;
240
-
241
236
  // Abort if our current target is not a candidate for navigation
242
237
  // (e.g. preserve native input behaviors).
243
238
  if ( ! isNavigationCandidate( target, keyCode, hasModifier ) ) {
@@ -279,7 +274,6 @@ export default function useArrowNav() {
279
274
  ( altKey ? isHorizontalEdge( target, isReverseDir ) : true ) &&
280
275
  ! keepCaretInsideBlock
281
276
  ) {
282
- node.contentEditable = false;
283
277
  const closestTabbable = getClosestTabbable(
284
278
  target,
285
279
  isReverse,
@@ -303,7 +297,6 @@ export default function useArrowNav() {
303
297
  isHorizontalEdge( target, isReverseDir ) &&
304
298
  ! keepCaretInsideBlock
305
299
  ) {
306
- node.contentEditable = false;
307
300
  const closestTabbable = getClosestTabbable(
308
301
  target,
309
302
  isReverseDir,
@@ -16,7 +16,6 @@ import {
16
16
  * Internal dependencies
17
17
  */
18
18
  import { store as blockEditorStore } from '../../store';
19
- import { getSelectionRoot } from './utils';
20
19
 
21
20
  /**
22
21
  * Handles input for selections across blocks.
@@ -50,24 +49,7 @@ export default function useInput() {
50
49
  // DOM. This will cause React errors (and the DOM should only be
51
50
  // altered in a controlled fashion).
52
51
  if ( node.contentEditable === 'true' ) {
53
- const selection = node.ownerDocument.defaultView.getSelection();
54
- const range = selection.rangeCount
55
- ? selection.getRangeAt( 0 )
56
- : null;
57
- const root = getSelectionRoot( node.ownerDocument );
58
-
59
- // If selection is contained within a nested editable, allow
60
- // input. We need to ensure that selection is maintained.
61
- if ( root ) {
62
- node.contentEditable = false;
63
- root.focus();
64
- selection.removeAllRanges();
65
- if ( range ) {
66
- selection.addRange( range );
67
- }
68
- } else {
69
- event.preventDefault();
70
- }
52
+ event.preventDefault();
71
53
  }
72
54
  }
73
55
 
@@ -77,23 +59,6 @@ export default function useInput() {
77
59
  }
78
60
 
79
61
  if ( ! hasMultiSelection() ) {
80
- const { ownerDocument } = node;
81
- if ( node === ownerDocument.activeElement ) {
82
- if ( event.key === 'End' || event.key === 'Home' ) {
83
- const selectionRoot = getSelectionRoot( ownerDocument );
84
- const selection =
85
- ownerDocument.defaultView.getSelection();
86
- selection.selectAllChildren( selectionRoot );
87
- const method =
88
- event.key === 'End'
89
- ? 'collapseToEnd'
90
- : 'collapseToStart';
91
- selection[ method ]();
92
- event.preventDefault();
93
- return;
94
- }
95
- }
96
-
97
62
  if ( event.keyCode === ENTER ) {
98
63
  if ( event.shiftKey || __unstableIsFullySelected() ) {
99
64
  return;
@@ -10,7 +10,6 @@ import { useRefEffect } from '@wordpress/compose';
10
10
  * Internal dependencies
11
11
  */
12
12
  import { store as blockEditorStore } from '../../store';
13
- import { getSelectionRoot } from './utils';
14
13
 
15
14
  export default function useSelectAll() {
16
15
  const { getBlockOrder, getSelectedBlockClientIds, getBlockRootClientId } =
@@ -24,27 +23,12 @@ export default function useSelectAll() {
24
23
  return;
25
24
  }
26
25
 
27
- const selectionRoot = getSelectionRoot( node.ownerDocument );
28
26
  const selectedClientIds = getSelectedBlockClientIds();
29
27
 
30
- // Abort if there is selection, but it is not within a block.
31
- if ( selectionRoot && ! selectedClientIds.length ) {
32
- return;
33
- }
34
-
35
28
  if (
36
- selectionRoot &&
37
29
  selectedClientIds.length < 2 &&
38
- ! isEntirelySelected( selectionRoot )
30
+ ! isEntirelySelected( event.target )
39
31
  ) {
40
- if ( node === node.ownerDocument.activeElement ) {
41
- event.preventDefault();
42
- node.ownerDocument.defaultView
43
- .getSelection()
44
- .selectAllChildren( selectionRoot );
45
- return;
46
- }
47
-
48
32
  return;
49
33
  }
50
34
 
@@ -61,7 +45,6 @@ export default function useSelectAll() {
61
45
  node.ownerDocument.defaultView
62
46
  .getSelection()
63
47
  .removeAllRanges();
64
- node.contentEditable = 'false';
65
48
  selectBlock( rootClientId );
66
49
  }
67
50
  return;
@@ -107,12 +107,8 @@ function getRichTextElement( node ) {
107
107
  export default function useSelectionObserver() {
108
108
  const { multiSelect, selectBlock, selectionChange } =
109
109
  useDispatch( blockEditorStore );
110
- const {
111
- getBlockParents,
112
- getBlockSelectionStart,
113
- isMultiSelecting,
114
- getSelectedBlockClientId,
115
- } = useSelect( blockEditorStore );
110
+ const { getBlockParents, getBlockSelectionStart, isMultiSelecting } =
111
+ useSelect( blockEditorStore );
116
112
  return useRefEffect(
117
113
  ( node ) => {
118
114
  const { ownerDocument } = node;
@@ -195,17 +191,10 @@ export default function useSelectionObserver() {
195
191
  return;
196
192
  }
197
193
 
198
- setContentEditableWrapper(
199
- node,
200
- !! ( startClientId && endClientId )
201
- );
202
-
203
194
  const isSingularSelection = startClientId === endClientId;
204
195
  if ( isSingularSelection ) {
205
196
  if ( ! isMultiSelecting() ) {
206
- if ( getSelectedBlockClientId() !== startClientId ) {
207
- selectBlock( startClientId );
208
- }
197
+ selectBlock( startClientId );
209
198
  } else {
210
199
  multiSelect( startClientId, startClientId );
211
200
  }
@@ -116,33 +116,3 @@ function toPlainText( html ) {
116
116
  // Merge any consecutive line breaks
117
117
  return plainText.replace( /\n\n+/g, '\n\n' );
118
118
  }
119
-
120
- /**
121
- * Gets the current content editable root element based on the selection.
122
- * @param {Document} ownerDocument
123
- * @return {Element|undefined} The content editable root element.
124
- */
125
- export function getSelectionRoot( ownerDocument ) {
126
- const { defaultView } = ownerDocument;
127
- const { anchorNode, focusNode } = defaultView.getSelection();
128
-
129
- if ( ! anchorNode || ! focusNode ) {
130
- return;
131
- }
132
-
133
- const anchorElement = (
134
- anchorNode.nodeType === anchorNode.ELEMENT_NODE
135
- ? anchorNode
136
- : anchorNode.parentElement
137
- ).closest( '[contenteditable]' );
138
-
139
- if ( ! anchorElement ) {
140
- return;
141
- }
142
-
143
- if ( ! anchorElement.contains( focusNode ) ) {
144
- return;
145
- }
146
-
147
- return anchorElement;
148
- }
@@ -11,8 +11,8 @@ import { addFilter } from '@wordpress/hooks';
11
11
  import { getBlockSupport, hasBlockSupport } from '@wordpress/blocks';
12
12
  import { useSelect } from '@wordpress/data';
13
13
  import {
14
- Button,
15
- ButtonGroup,
14
+ __experimentalToggleGroupControl as ToggleGroupControl,
15
+ __experimentalToggleGroupControlOption as ToggleGroupControlOption,
16
16
  ToggleControl,
17
17
  PanelBody,
18
18
  privateApis as componentsPrivateApis,
@@ -315,21 +315,26 @@ export default {
315
315
 
316
316
  function LayoutTypeSwitcher( { type, onChange } ) {
317
317
  return (
318
- <ButtonGroup>
318
+ <ToggleGroupControl
319
+ __next40pxDefaultSize
320
+ isBlock
321
+ label={ __( 'Layout type' ) }
322
+ __nextHasNoMarginBottom
323
+ hideLabelFromVision
324
+ isAdaptiveWidth
325
+ value={ type }
326
+ onChange={ onChange }
327
+ >
319
328
  { getLayoutTypes().map( ( { name, label } ) => {
320
329
  return (
321
- <Button
322
- // TODO: Switch to `true` (40px size) if possible
323
- __next40pxDefaultSize={ false }
330
+ <ToggleGroupControlOption
324
331
  key={ name }
325
- isPressed={ type === name }
326
- onClick={ () => onChange( name ) }
327
- >
328
- { label }
329
- </Button>
332
+ value={ name }
333
+ label={ label }
334
+ />
330
335
  );
331
336
  } ) }
332
- </ButtonGroup>
337
+ </ToggleGroupControl>
333
338
  );
334
339
  }
335
340
 
@@ -103,11 +103,7 @@ export const withBlockBindingSupport = createHigherOrderComponent(
103
103
  const sources = useSelect( ( select ) =>
104
104
  unlock( select( blocksStore ) ).getAllBlockBindingsSources()
105
105
  );
106
- const { name, clientId } = props;
107
- const hasParentPattern = !! props.context[ 'pattern/overrides' ];
108
- const hasPatternOverridesDefaultBinding =
109
- props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ]
110
- ?.source === 'core/pattern-overrides';
106
+ const { name, clientId, context, setAttributes } = props;
111
107
  const blockBindings = useMemo(
112
108
  () =>
113
109
  replacePatternOverrideDefaultBindings(
@@ -121,6 +117,7 @@ export const withBlockBindingSupport = createHigherOrderComponent(
121
117
  // used purposely here to ensure `boundAttributes` is updated whenever
122
118
  // there are attribute updates.
123
119
  // `source.getValues` may also call a selector via `registry.select`.
120
+ const updatedContext = {};
124
121
  const boundAttributes = useSelect( () => {
125
122
  if ( ! blockBindings ) {
126
123
  return;
@@ -139,6 +136,11 @@ export const withBlockBindingSupport = createHigherOrderComponent(
139
136
  continue;
140
137
  }
141
138
 
139
+ // Populate context.
140
+ for ( const key of source.usesContext || [] ) {
141
+ updatedContext[ key ] = blockContext[ key ];
142
+ }
143
+
142
144
  blockBindingsBySource.set( source, {
143
145
  ...blockBindingsBySource.get( source ),
144
146
  [ attributeName ]: {
@@ -149,27 +151,17 @@ export const withBlockBindingSupport = createHigherOrderComponent(
149
151
 
150
152
  if ( blockBindingsBySource.size ) {
151
153
  for ( const [ source, bindings ] of blockBindingsBySource ) {
152
- // Populate context.
153
- const context = {};
154
-
155
- if ( source.usesContext?.length ) {
156
- for ( const key of source.usesContext ) {
157
- context[ key ] = blockContext[ key ];
158
- }
159
- }
160
-
161
154
  // Get values in batch if the source supports it.
162
155
  let values = {};
163
156
  if ( ! source.getValues ) {
164
157
  Object.keys( bindings ).forEach( ( attr ) => {
165
- // Default to the `key` or the source label when `getValues` doesn't exist
166
- values[ attr ] =
167
- bindings[ attr ].args?.key || source.label;
158
+ // Default to the the source label when `getValues` doesn't exist.
159
+ values[ attr ] = source.label;
168
160
  } );
169
161
  } else {
170
162
  values = source.getValues( {
171
163
  registry,
172
- context,
164
+ context: updatedContext,
173
165
  clientId,
174
166
  bindings,
175
167
  } );
@@ -191,9 +183,19 @@ export const withBlockBindingSupport = createHigherOrderComponent(
191
183
  }
192
184
 
193
185
  return attributes;
194
- }, [ blockBindings, name, clientId, blockContext, registry, sources ] );
195
-
196
- const { setAttributes } = props;
186
+ }, [
187
+ blockBindings,
188
+ name,
189
+ clientId,
190
+ updatedContext,
191
+ registry,
192
+ sources,
193
+ ] );
194
+
195
+ const hasParentPattern = !! updatedContext[ 'pattern/overrides' ];
196
+ const hasPatternOverridesDefaultBinding =
197
+ props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ]
198
+ ?.source === 'core/pattern-overrides';
197
199
 
198
200
  const _setAttributes = useCallback(
199
201
  ( nextAttributes ) => {
@@ -237,18 +239,9 @@ export const withBlockBindingSupport = createHigherOrderComponent(
237
239
  source,
238
240
  bindings,
239
241
  ] of blockBindingsBySource ) {
240
- // Populate context.
241
- const context = {};
242
-
243
- if ( source.usesContext?.length ) {
244
- for ( const key of source.usesContext ) {
245
- context[ key ] = blockContext[ key ];
246
- }
247
- }
248
-
249
242
  source.setValues( {
250
243
  registry,
251
- context,
244
+ context: updatedContext,
252
245
  clientId,
253
246
  bindings,
254
247
  } );
@@ -278,7 +271,7 @@ export const withBlockBindingSupport = createHigherOrderComponent(
278
271
  blockBindings,
279
272
  name,
280
273
  clientId,
281
- blockContext,
274
+ updatedContext,
282
275
  setAttributes,
283
276
  sources,
284
277
  hasPatternOverridesDefaultBinding,
@@ -292,6 +285,7 @@ export const withBlockBindingSupport = createHigherOrderComponent(
292
285
  { ...props }
293
286
  attributes={ { ...props.attributes, ...boundAttributes } }
294
287
  setAttributes={ _setAttributes }
288
+ context={ { ...context, ...updatedContext } }
295
289
  />
296
290
  </>
297
291
  );
@@ -8,46 +8,40 @@ import { useEffect, useRef } from '@wordpress/element';
8
8
  * Internal dependencies
9
9
  */
10
10
  import { store as blockEditorStore } from '../store';
11
+ import { unlock } from '../lock-unlock';
11
12
 
12
13
  /**
13
- * A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode.
14
+ * A hook used to set the zoomed out view, invoking the hook sets the mode.
14
15
  *
15
- * @param {boolean} zoomOut If we should enter into zoomOut mode or not
16
+ * @param {boolean} zoomOut If we should zoom out or not.
16
17
  */
17
18
  export function useZoomOut( zoomOut = true ) {
18
- const { __unstableSetEditorMode } = useDispatch( blockEditorStore );
19
- const { __unstableGetEditorMode } = useSelect( blockEditorStore );
19
+ const { setZoomLevel } = unlock( useDispatch( blockEditorStore ) );
20
+ const { isZoomOut } = unlock( useSelect( blockEditorStore ) );
20
21
 
21
- const originalEditingModeRef = useRef( null );
22
- const mode = __unstableGetEditorMode();
22
+ const originalIsZoomOutRef = useRef( null );
23
23
 
24
24
  useEffect( () => {
25
25
  // Only set this on mount so we know what to return to when we unmount.
26
- if ( ! originalEditingModeRef.current ) {
27
- originalEditingModeRef.current = mode;
26
+ if ( ! originalIsZoomOutRef.current ) {
27
+ originalIsZoomOutRef.current = isZoomOut();
28
28
  }
29
29
 
30
- return () => {
31
- // We need to use __unstableGetEditorMode() here and not `mode`, as mode may not update on unmount
32
- if (
33
- __unstableGetEditorMode() === 'zoom-out' &&
34
- __unstableGetEditorMode() !== originalEditingModeRef.current
35
- ) {
36
- __unstableSetEditorMode( originalEditingModeRef.current );
37
- }
38
- };
39
- }, [] );
40
-
41
- // The effect opens the zoom-out view if we want it open and it's not currently in zoom-out mode.
42
- useEffect( () => {
43
- if ( zoomOut && mode !== 'zoom-out' ) {
44
- __unstableSetEditorMode( 'zoom-out' );
30
+ // The effect opens the zoom-out view if we want it open and the canvas is not currently zoomed-out.
31
+ if ( zoomOut && isZoomOut() === false ) {
32
+ setZoomLevel( 50 );
45
33
  } else if (
46
34
  ! zoomOut &&
47
- __unstableGetEditorMode() === 'zoom-out' &&
48
- originalEditingModeRef.current !== mode
35
+ isZoomOut() &&
36
+ originalIsZoomOutRef.current !== isZoomOut()
49
37
  ) {
50
- __unstableSetEditorMode( originalEditingModeRef.current );
38
+ setZoomLevel( originalIsZoomOutRef.current ? 50 : 100 );
51
39
  }
52
- }, [ __unstableGetEditorMode, __unstableSetEditorMode, zoomOut ] ); // Mode is deliberately excluded from the dependencies so that the effect does not run when mode changes.
40
+
41
+ return () => {
42
+ if ( isZoomOut() && isZoomOut() !== originalIsZoomOutRef.current ) {
43
+ setZoomLevel( originalIsZoomOutRef.current ? 50 : 100 );
44
+ }
45
+ };
46
+ }, [ isZoomOut, setZoomLevel, zoomOut ] );
53
47
  }
@@ -383,3 +383,26 @@ export const modifyContentLockBlock =
383
383
  focusModeToRevert
384
384
  );
385
385
  };
386
+
387
+ /**
388
+ * Sets the zoom level.
389
+ *
390
+ * @param {number} zoom the new zoom level
391
+ * @return {Object} Action object.
392
+ */
393
+ export function setZoomLevel( zoom = 100 ) {
394
+ return {
395
+ type: 'SET_ZOOM_LEVEL',
396
+ zoom,
397
+ };
398
+ }
399
+
400
+ /**
401
+ * Resets the Zoom state.
402
+ * @return {Object} Action object.
403
+ */
404
+ export function resetZoomLevel() {
405
+ return {
406
+ type: 'RESET_ZOOM_LEVEL',
407
+ };
408
+ }
@@ -560,3 +560,23 @@ export function isZoomOutMode( state ) {
560
560
  export function getSectionRootClientId( state ) {
561
561
  return state.settings?.[ sectionRootClientIdKey ];
562
562
  }
563
+
564
+ /**
565
+ * Returns the zoom out state.
566
+ *
567
+ * @param {Object} state Global application state.
568
+ * @return {boolean} The zoom out state.
569
+ */
570
+ export function getZoomLevel( state ) {
571
+ return state.zoomLevel;
572
+ }
573
+
574
+ /**
575
+ * Returns whether the editor is considered zoomed out.
576
+ *
577
+ * @param {Object} state Global application state.
578
+ * @return {boolean} Whether the editor is zoomed.
579
+ */
580
+ export function isZoomOut( state ) {
581
+ return getZoomLevel( state ) < 100;
582
+ }
@@ -2085,6 +2085,25 @@ export function hoveredBlockClientId( state = false, action ) {
2085
2085
  return state;
2086
2086
  }
2087
2087
 
2088
+ /**
2089
+ * Reducer setting zoom out state.
2090
+ *
2091
+ * @param {boolean} state Current state.
2092
+ * @param {Object} action Dispatched action.
2093
+ *
2094
+ * @return {boolean} Updated state.
2095
+ */
2096
+ export function zoomLevel( state = 100, action ) {
2097
+ switch ( action.type ) {
2098
+ case 'SET_ZOOM_LEVEL':
2099
+ return action.zoom;
2100
+ case 'RESET_ZOOM_LEVEL':
2101
+ return 100;
2102
+ }
2103
+
2104
+ return state;
2105
+ }
2106
+
2088
2107
  const combinedReducers = combineReducers( {
2089
2108
  blocks,
2090
2109
  isDragging,
@@ -2118,6 +2137,7 @@ const combinedReducers = combineReducers( {
2118
2137
  openedBlockSettingsMenu,
2119
2138
  registeredInserterMediaCategories,
2120
2139
  hoveredBlockClientId,
2140
+ zoomLevel,
2121
2141
  } );
2122
2142
 
2123
2143
  function withAutomaticChangeReset( reducer ) {
@@ -2467,7 +2467,7 @@ export const __experimentalGetPatternsByBlockTypes = createRegistrySelector(
2467
2467
  * Determines the items that appear in the available pattern transforms list.
2468
2468
  *
2469
2469
  * For now we only handle blocks without InnerBlocks and take into account
2470
- * the `__experimentalRole` property of blocks' attributes for the transformation.
2470
+ * the `role` property of blocks' attributes for the transformation.
2471
2471
  *
2472
2472
  * We return the first set of possible eligible block patterns,
2473
2473
  * by checking the `blockTypes` property. We still have to recurse through
@@ -2489,7 +2489,7 @@ export const __experimentalGetPatternTransformItems = createRegistrySelector(
2489
2489
  }
2490
2490
  /**
2491
2491
  * For now we only handle blocks without InnerBlocks and take into account
2492
- * the `__experimentalRole` property of blocks' attributes for the transformation.
2492
+ * the `role` property of blocks' attributes for the transformation.
2493
2493
  * Note that the blocks have been retrieved through `getBlock`, which doesn't
2494
2494
  * return the inner blocks of an inner block controller, so we still need
2495
2495
  * to check for this case too.
@@ -2926,6 +2926,7 @@ export const getBlockEditingMode = createRegistrySelector(
2926
2926
  if ( clientId === null ) {
2927
2927
  clientId = '';
2928
2928
  }
2929
+
2929
2930
  // In zoom-out mode, override the behavior set by
2930
2931
  // __unstableSetBlockEditingMode to only allow editing the top-level
2931
2932
  // sections.
@@ -2943,9 +2944,13 @@ export const getBlockEditingMode = createRegistrySelector(
2943
2944
  state,
2944
2945
  sectionRootClientId
2945
2946
  );
2946
- if ( ! sectionsClientIds?.includes( clientId ) ) {
2947
- return 'disabled';
2947
+
2948
+ // Sections are always contentOnly.
2949
+ if ( sectionsClientIds?.includes( clientId ) ) {
2950
+ return 'contentOnly';
2948
2951
  }
2952
+
2953
+ return 'disabled';
2949
2954
  }
2950
2955
 
2951
2956
  const blockEditingMode = state.blockEditingModes.get( clientId );
@@ -2959,10 +2964,10 @@ export const getBlockEditingMode = createRegistrySelector(
2959
2964
  const templateLock = getTemplateLock( state, rootClientId );
2960
2965
  if ( templateLock === 'contentOnly' ) {
2961
2966
  const name = getBlockName( state, clientId );
2962
- const isContent =
2963
- select( blocksStore ).__experimentalHasContentRoleAttribute(
2964
- name
2965
- );
2967
+ const { hasContentRoleAttribute } = unlock(
2968
+ select( blocksStore )
2969
+ );
2970
+ const isContent = hasContentRoleAttribute( name );
2966
2971
  return isContent ? 'contentOnly' : 'disabled';
2967
2972
  }
2968
2973
  const parentMode = getBlockEditingMode( state, rootClientId );
@@ -124,10 +124,10 @@ describe( 'private selectors', () => {
124
124
  blockEditingModes: new Map( [] ),
125
125
  };
126
126
 
127
- const __experimentalHasContentRoleAttribute = jest.fn( () => false );
127
+ const hasContentRoleAttribute = jest.fn( () => false );
128
128
  getBlockEditingMode.registry = {
129
129
  select: jest.fn( () => ( {
130
- __experimentalHasContentRoleAttribute,
130
+ hasContentRoleAttribute,
131
131
  } ) ),
132
132
  };
133
133
 
@@ -15,6 +15,7 @@ import { select, dispatch } from '@wordpress/data';
15
15
  */
16
16
  import * as selectors from '../selectors';
17
17
  import { store } from '../';
18
+ import { lock } from '../../lock-unlock';
18
19
 
19
20
  const {
20
21
  getBlockName,
@@ -4372,11 +4373,14 @@ describe( 'getBlockEditingMode', () => {
4372
4373
  blockEditingModes: new Map( [] ),
4373
4374
  };
4374
4375
 
4375
- const __experimentalHasContentRoleAttribute = jest.fn( () => false );
4376
+ const hasContentRoleAttribute = jest.fn( () => false );
4377
+
4378
+ const fauxPrivateAPIs = {};
4379
+
4380
+ lock( fauxPrivateAPIs, { hasContentRoleAttribute } );
4381
+
4376
4382
  getBlockEditingMode.registry = {
4377
- select: jest.fn( () => ( {
4378
- __experimentalHasContentRoleAttribute,
4379
- } ) ),
4383
+ select: jest.fn( () => fauxPrivateAPIs ),
4380
4384
  };
4381
4385
 
4382
4386
  it( 'should return default by default', () => {
@@ -4480,7 +4484,7 @@ describe( 'getBlockEditingMode', () => {
4480
4484
  },
4481
4485
  },
4482
4486
  };
4483
- __experimentalHasContentRoleAttribute.mockReturnValueOnce( false );
4487
+ hasContentRoleAttribute.mockReturnValueOnce( false );
4484
4488
  expect(
4485
4489
  getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' )
4486
4490
  ).toBe( 'disabled' );
@@ -4496,7 +4500,7 @@ describe( 'getBlockEditingMode', () => {
4496
4500
  },
4497
4501
  },
4498
4502
  };
4499
- __experimentalHasContentRoleAttribute.mockReturnValueOnce( true );
4503
+ hasContentRoleAttribute.mockReturnValueOnce( true );
4500
4504
  expect(
4501
4505
  getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' )
4502
4506
  ).toBe( 'contentOnly' );