`: Client IDs of the blocks to zoom into.
+
+_Returns_
+
+- `Object`: Action object.
+
diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js
index ffb14ffca8c5eb..658b6ba1f8196f 100644
--- a/packages/block-editor/src/components/block-actions/index.js
+++ b/packages/block-editor/src/components/block-actions/index.js
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { castArray, first, last, every } from 'lodash';
+import { castArray, first, last, every, flatMap } from 'lodash';
/**
* WordPress dependencies
@@ -11,6 +11,7 @@ import { withSelect, withDispatch } from '@wordpress/data';
import { cloneBlock, hasBlockSupport, switchToBlockType } from '@wordpress/blocks';
function BlockActions( {
+ onZoom,
onDuplicate,
onRemove,
onInsertBefore,
@@ -22,6 +23,7 @@ function BlockActions( {
children,
} ) {
return children( {
+ onZoom,
onDuplicate,
onRemove,
onInsertAfter,
@@ -65,6 +67,7 @@ export default compose( [
} = props;
const {
+ zoomBlocks,
insertBlocks,
multiSelect,
removeBlocks,
@@ -73,6 +76,17 @@ export default compose( [
} = dispatch( 'core/block-editor' );
return {
+ onZoom() {
+ const { getSelectedBlockClientIds, getBlockOrder } = select(
+ 'core/block-editor'
+ );
+ zoomBlocks(
+ flatMap( getSelectedBlockClientIds(), ( clientId ) => {
+ const descendants = getBlockOrder( clientId );
+ return descendants.length ? descendants : clientId;
+ } )
+ );
+ },
onDuplicate() {
if ( isLocked || ! canDuplicate ) {
return;
diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js
index 3e65a52fd3b2ec..434253031a1d40 100644
--- a/packages/block-editor/src/components/block-list/index.js
+++ b/packages/block-editor/src/components/block-list/index.js
@@ -12,6 +12,7 @@ import {
/**
* WordPress dependencies
*/
+import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import {
withSelect,
@@ -19,6 +20,7 @@ import {
__experimentalAsyncModeProvider as AsyncModeProvider,
} from '@wordpress/data';
import { compose } from '@wordpress/compose';
+import { IconButton } from '@wordpress/components';
/**
* Internal dependencies
@@ -204,11 +206,24 @@ class BlockList extends Component {
multiSelectedBlockClientIds,
hasMultiSelection,
renderAppender,
+ hasZoomedBlocks,
enableAnimation,
+ onZoomOut,
} = this.props;
return (
+ { hasZoomedBlocks && (
+
+ { __( 'Zoom Out' ) }
+
+ ) }
+
{ blockClientIds.map( ( clientId ) => {
const isBlockInSelection = hasMultiSelection ?
multiSelectedBlockClientIds.includes( clientId ) :
@@ -254,6 +269,7 @@ export default compose( [
withSelect( ( select, ownProps ) => {
const {
getBlockOrder,
+ getZoomedBlockClientIds,
isSelectionEnabled,
isMultiSelecting,
getMultiSelectedBlocksStartClientId,
@@ -265,9 +281,17 @@ export default compose( [
} = select( 'core/block-editor' );
const { rootClientId } = ownProps;
+ let blockClientIds = rootClientId ?
+ getBlockOrder( rootClientId ) :
+ getZoomedBlockClientIds();
+ let hasZoomedBlocks = ! rootClientId;
+ if ( ! blockClientIds ) {
+ blockClientIds = getBlockOrder();
+ hasZoomedBlocks = false;
+ }
return {
- blockClientIds: getBlockOrder( rootClientId ),
+ blockClientIds,
selectionStart: getMultiSelectedBlocksStartClientId(),
selectionEnd: getMultiSelectedBlocksEndClientId(),
isSelectionEnabled: isSelectionEnabled(),
@@ -275,6 +299,7 @@ export default compose( [
selectedBlockClientId: getSelectedBlockClientId(),
multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(),
hasMultiSelection: hasMultiSelection(),
+ hasZoomedBlocks,
enableAnimation: getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD,
};
} ),
@@ -283,12 +308,14 @@ export default compose( [
startMultiSelect,
stopMultiSelect,
multiSelect,
+ clearZoomedBlocks,
} = dispatch( 'core/block-editor' );
return {
onStartMultiSelect: startMultiSelect,
onStopMultiSelect: stopMultiSelect,
onMultiSelect: multiSelect,
+ onZoomOut: clearZoomedBlocks,
};
} ),
] )( BlockList );
diff --git a/packages/block-editor/src/components/block-list/moving-animation.js b/packages/block-editor/src/components/block-list/moving-animation.js
index 393bfa33e2a7ca..d7ffa99f60831a 100644
--- a/packages/block-editor/src/components/block-list/moving-animation.js
+++ b/packages/block-editor/src/components/block-list/moving-animation.js
@@ -39,9 +39,10 @@ function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationO
}
ref.current.style.transform = 'none';
const destination = ref.current.getBoundingClientRect();
+ const defaultPosition = isSelected ? -50 : 0;
const newTransform = {
- x: previous ? previous.left - destination.left : 0,
- y: previous ? previous.top - destination.top : 0,
+ x: previous ? previous.left - destination.left : defaultPosition,
+ y: previous ? previous.top - destination.top : defaultPosition,
};
ref.current.style.transform = newTransform.x === 0 && newTransform.y === 0 ?
undefined :
diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss
index 8ee3558ddf6398..d996a81c697593 100644
--- a/packages/block-editor/src/components/block-list/style.scss
+++ b/packages/block-editor/src/components/block-list/style.scss
@@ -7,6 +7,16 @@
}
}
+.block-editor-block-list__zoom-out {
+ background: $white;
+ border-radius: 0;
+ float: right;
+ margin-top: -50px;
+ position: sticky;
+ top: 0;
+ z-index: z-index(".block-editor-block-list__zoom_out");
+}
+
.block-editor-block-list__layout .block-editor-block-list__block.is-selected { // Needs specificity to override inherited styles.
// While block is being dragged, dim the slot dragged from, and hide some UI.
&.is-dragging {
diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js
index 005be38e380b43..a75be49ba2c946 100644
--- a/packages/block-editor/src/components/block-settings-menu/index.js
+++ b/packages/block-editor/src/components/block-settings-menu/index.js
@@ -32,7 +32,7 @@ export function BlockSettingsMenu( { clientIds } ) {
return (
- { ( { onDuplicate, onRemove, onInsertAfter, onInsertBefore, canDuplicate, isLocked } ) => (
+ { ( { onZoom, onDuplicate, onRemove, onInsertAfter, onInsertBefore, canDuplicate, isLocked } ) => (
(
<>
+
<__experimentalBlockSettingsMenuFirstItem.Slot
fillProps={ { onClose } }
/>
diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js
index 4dafaa291fcf31..6a2c1e74857c98 100644
--- a/packages/block-editor/src/store/actions.js
+++ b/packages/block-editor/src/store/actions.js
@@ -545,6 +545,31 @@ export function replaceInnerBlocks( rootClientId, blocks, updateSelection = true
};
}
+/**
+ * Returns an action object that zooms the editor into a list of specified blocks.
+ *
+ * @param {string[]} clientIds Client IDs of the blocks to zoom into.
+ *
+ * @return {Object} Action object.
+ */
+export function zoomBlocks( clientIds ) {
+ return {
+ type: 'ZOOM_BLOCKS',
+ clientIds,
+ };
+}
+
+/**
+ * Returns an action object that zooms out from any zoomed in blocks.
+ *
+ * @return {Object} Action object.
+ */
+export function clearZoomedBlocks() {
+ return {
+ type: 'CLEAR_ZOOMED_BLOCKS',
+ };
+}
+
/**
* Returns an action object used to toggle the block editing mode between
* visual and HTML modes.
diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js
index 97a366e8884d2f..26e66f6a096514 100644
--- a/packages/block-editor/src/store/reducer.js
+++ b/packages/block-editor/src/store/reducer.js
@@ -1043,6 +1043,25 @@ export function blockSelection( state = BLOCK_SELECTION_INITIAL_STATE, action )
return state;
}
+/**
+ * Reducer returning the block zoom's state.
+ *
+ * @param {Object} state Current state.
+ * @param {Object} action Dispatched action.
+ *
+ * @return {Object} Updated state.
+ */
+export function blockZoom( state, action ) {
+ switch ( action.type ) {
+ case 'ZOOM_BLOCKS':
+ return action.clientIds;
+ case 'CLEAR_ZOOMED_BLOCKS':
+ return null;
+ }
+
+ return state;
+}
+
export function blocksMode( state = {}, action ) {
if ( action.type === 'TOGGLE_BLOCK_MODE' ) {
const { clientId } = action;
@@ -1228,6 +1247,7 @@ export default combineReducers( {
isTyping,
isCaretWithinFormattedText,
blockSelection,
+ blockZoom,
blocksMode,
blockListSettings,
insertionPoint,
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index 5e8771ff94935b..2224a292fca594 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -889,6 +889,17 @@ export function isSelectionEnabled( state ) {
return state.blockSelection.isEnabled;
}
+/**
+ * Returns the zoomed block client IDs, if any.
+ *
+ * @param {Object} state Editor state.
+ *
+ * @return {?(string[])} Client IDs of zoomed blocks.
+ */
+export function getZoomedBlockClientIds( state ) {
+ return state.blockZoom;
+}
+
/**
* Returns the block's editing mode, defaulting to "visual" if not explicitly
* assigned.
@@ -992,6 +1003,10 @@ export function getTemplate( state ) {
* @return {?string} Block Template Lock
*/
export function getTemplateLock( state, rootClientId ) {
+ if ( getZoomedBlockClientIds( state ) ) {
+ return 'all';
+ }
+
if ( ! rootClientId ) {
return state.settings.templateLock;
}
diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js
index 8ea2c9a408de84..3f6dea91387324 100644
--- a/packages/editor/src/store/actions.js
+++ b/packages/editor/src/store/actions.js
@@ -709,7 +709,8 @@ export function redo() {
*
* @return {Object} Action object.
*/
-export function undo() {
+export function* undo() {
+ yield dispatch( 'core/block-editor', 'clearZoomedBlocks' );
return { type: 'UNDO' };
}