Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions packages/editor/src/components/resizable-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import clsx from 'clsx';
*/
import { useDispatch } from '@wordpress/data';
import { useRef, useCallback, useState } from '@wordpress/element';
import { ResizableBox } from '@wordpress/components';
import {
ResizableBox,
__unstableMotion as motion,
} from '@wordpress/components';
import { useReducedMotion } from '@wordpress/compose';

/**
* Internal dependencies
Expand Down Expand Up @@ -52,6 +56,7 @@ function ResizableEditor( {
children,
} ) {
const [ isResizing, setIsResizing ] = useState( false );
const disableMotion = useReducedMotion();
const { setCanvasWidth } = unlock( useDispatch( editorStore ) );

const resizableRef = useRef();
Expand All @@ -74,11 +79,11 @@ function ResizableEditor( {
);

const updateCanvasWidth = useCallback(
( element ) => {
( element, isResizeEnd ) => {
const currentWidth = element.offsetWidth;
const containerWidth = element.parentElement?.offsetWidth ?? 0;
setCanvasWidth(
isAtMaxWidth( currentWidth, containerWidth, 80 )
isResizeEnd && isAtMaxWidth( currentWidth, containerWidth, 80 )
? undefined
: currentWidth
);
Expand All @@ -88,6 +93,14 @@ function ResizableEditor( {

return (
<ResizableBox
as={ motion.div }
initial={ false }
animate={ { height } }
transition={ {
type: 'tween',
duration: isResizing || disableMotion ? 0 : 0.2,
ease: 'easeOut',
} }
className={ clsx( 'editor-resizable-editor', className, {
'is-resizable': enableResizing,
'is-resizing': isResizing,
Expand All @@ -107,7 +120,7 @@ function ResizableEditor( {
updateCanvasWidth( element );
} }
onResizeStop={ ( event, direction, element ) => {
updateCanvasWidth( element );
updateCanvasWidth( element, true );
setIsResizing( false );
onResizeStop?.();
} }
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/components/resizable-editor/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
@use "@wordpress/base-styles/variables" as *;

// Don't apply transition when resized using the resizer handle.
// The height is animated in the component instead.
.editor-resizable-editor:not(.is-resizing) {
@media (prefers-reduced-motion: no-preference) {
transition: width 0.2s ease-out, height 0.2s ease-out;
transition: width 0.2s ease-out;
}
}

Expand Down
82 changes: 63 additions & 19 deletions packages/editor/src/components/visual-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import { useEffect, useRef, useMemo, useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { parse } from '@wordpress/blocks';
import { store as coreStore } from '@wordpress/core-data';
import { useMergeRefs, useViewportMatch } from '@wordpress/compose';
import {
useMergeRefs,
useResizeObserver,
useViewportMatch,
} from '@wordpress/compose';

/**
* Internal dependencies
Expand Down Expand Up @@ -89,6 +93,41 @@ function checkForPostContentAtRootLevel( blocks ) {
return false;
}

const CANVAS_MIN_WIDTH = 300;
const CANVAS_TARGET_ASPECT_RATIO = 9 / 16;

/**
* Returns the canvas height, keeping the aspect ratio of the available space
* without exceeding it.
*
* @param {number} width The canvas width in pixels.
* @param {Object} containerSize The available space, as `{ width, height }` in pixels.
* @return {number} The canvas height in pixels.
*/
function getCanvasHeight( width, containerSize ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth adding some tests for the new function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 55fd27d

const lerp = ( a, b, amount ) => a + ( b - a ) * amount;

// The narrower the canvas within the available space, the closer to the
// target aspect ratio.
const lerpFactor =
1 -
Math.max(
0,
Math.min(
1,
( width - CANVAS_MIN_WIDTH ) /
( containerSize.width - CANVAS_MIN_WIDTH )
)
);
const aspectRatio = lerp(
containerSize.width / containerSize.height,
CANVAS_TARGET_ASPECT_RATIO,
lerpFactor
);

return Math.min( Math.round( width / aspectRatio ), containerSize.height );
}

function VisualEditor( {
// Ideally as we unify post and site editors, we won't need these props.
autoFocus,
Expand All @@ -111,7 +150,6 @@ function VisualEditor( {
styles,
hasCanvasWidth,
canvasWidth,
canvasHeight,
} = useSelect( ( select ) => {
const {
getCurrentPostId,
Expand All @@ -121,7 +159,6 @@ function VisualEditor( {
getRenderingMode,
getDeviceType,
getCanvasWidth,
getCanvasHeight,
} = unlock( select( editorStore ) );
const { getPostType, getEditedEntityRecord } = select( coreStore );
const postTypeSlug = getCurrentPostType();
Expand Down Expand Up @@ -166,7 +203,6 @@ function VisualEditor( {
styles: editorSettings.styles,
hasCanvasWidth: _canvasWidth !== undefined,
canvasWidth: _canvasWidth,
canvasHeight: getCanvasHeight(),
};
}, [] );
const { isCleanNewPost } = useSelect( editorStore );
Expand All @@ -191,9 +227,18 @@ function VisualEditor( {
}, [] );

const localRef = useRef();
const [ isResizingCanvas, setIsResizingCanvas ] = useState( false );
const [ globalLayoutSettings ] = useSettings( 'layout' );

const [ containerSize, setContainerSize ] = useState();
const containerRef = useResizeObserver( ( entries ) => {
const { width, height } = entries[ 0 ].contentRect;
setContainerSize( ( size ) =>
size?.width === width && size?.height === height
? size
: { width, height }
);
} );

// fallbackLayout is used if there is no Post Content,
// and for Post Title.
const fallbackLayout = useMemo( () => {
Expand Down Expand Up @@ -346,14 +391,18 @@ function VisualEditor( {
! isPreview && renderingMode === 'post-only' && ! isDesignPostType
);

const shouldConstrainCanvasHeight =
enableResizing &&
canvasWidth &&
containerSize?.height > 0 &&
containerSize?.width > CANVAS_MIN_WIDTH;
const canvasHeight = shouldConstrainCanvasHeight
? getCanvasHeight( canvasWidth, containerSize )
: '100%';

const centerContentCSS = `display:flex;align-items:center;justify-content:center;`;
const shouldExpandIframeBody =
canvasHeight !== undefined &&
! isResizablePostType &&
! isResizingCanvas;
const iframeBodyMinHeightCSS = shouldExpandIframeBody
? 'min-height:100vh;'
: '';
const iframeBodyMinHeightCSS =
hasCanvasWidth && ! isResizablePostType ? 'min-height:100vh;' : '';

const iframeStyles = useMemo( () => {
return [
Expand Down Expand Up @@ -411,6 +460,7 @@ function VisualEditor( {

return (
<div
ref={ containerRef }
className={ clsx(
'editor-visual-editor',
// this class is here for backward compatibility reasons.
Expand All @@ -432,13 +482,7 @@ function VisualEditor( {
width={
enableResizing && canvasWidth ? canvasWidth + 'px' : '100%'
}
height={
enableResizing && canvasHeight && ! isResizingCanvas
? canvasHeight + 'px'
: '100%'
}
onResizeStart={ () => setIsResizingCanvas( true ) }
onResizeStop={ () => setIsResizingCanvas( false ) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like these two props were introduced recently in #80553; do we need to keep them around now that we're not using them anymore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, fixed in 9e1fafe. Since ResizableEditor is a private component, we can safely remove those props.

height={ canvasHeight }
>
<BlockCanvas
shouldIframe
Expand Down
53 changes: 0 additions & 53 deletions packages/editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,8 @@ import {
getEntityFields as _getEntityFields,
isEntityReady as _isEntityReady,
} from '../dataviews/store/private-selectors';
import {
getDeviceTypeByCanvasWidth,
getCanvasWidthByDeviceType,
} from '../utils/device-type';
import { unlock } from '../lock-unlock';

// Device preview aspect ratios (height / width). Used to give the editor
// canvas a device-shaped frame in mobile and tablet previews. These are
// display ratios for the preview frame, not responsive breakpoints. Mobile
// and tablet are both portrait (taller than wide) to match the WordPress 7.0
// preview behavior.
const DEVICE_ASPECT_RATIO_BY_DEVICE_TYPE = {
Mobile: 8 / 5,
Tablet: 4 / 3,
};

const EMPTY_INSERTION_POINT = {
rootClientId: undefined,
insertionIndex: undefined,
Expand Down Expand Up @@ -343,45 +329,6 @@ export const getCanvasWidth = createRegistrySelector(
}
);

/**
* Returns the device preview canvas height in pixels, derived from the canvas
* width using the device aspect ratio. Only applies when the canvas width
* matches the device preset (set via the Preview dropdown), so dragging away
* from the preset frees the frame to fill the editor. Returns `undefined` for
* desktop, zoom-out, or when no device height applies.
*
* @param {Object} state Global application state.
* @return {number|undefined} The canvas height in pixels, or undefined.
*/
export const getCanvasHeight = createRegistrySelector(
( select ) => ( state ) => {
const blockEditorSelect = unlock( select( blockEditorStore ) );
if ( blockEditorSelect.isZoomOut() ) {
return undefined;
}
const canvasWidth = state.canvasWidth;
const viewportSettings =
blockEditorSelect.getSettings().__experimentalFeatures?.viewport;
const deviceType = getDeviceTypeByCanvasWidth(
canvasWidth,
viewportSettings
);
// Only apply the device height at the preset width; a dragged width
// within a band frees the canvas to fill the editor.
if (
canvasWidth !==
getCanvasWidthByDeviceType( deviceType, viewportSettings )
) {
return undefined;
}
const ratio = DEVICE_ASPECT_RATIO_BY_DEVICE_TYPE[ deviceType ];
if ( ratio && canvasWidth > 0 ) {
return Math.round( canvasWidth * ratio );
}
return undefined;
}
);

/**
* Returns the current revisions page number.
*
Expand Down
71 changes: 0 additions & 71 deletions packages/editor/src/store/test/private-selectors.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
/**
* WordPress dependencies
*/
import { store as blockEditorStore } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
import { store as preferencesStore } from '@wordpress/preferences';

/**
* Internal dependencies
*/
import {
getCanvasHeight,
getDefaultRenderingMode,
getPostBlocksByName,
isCollaborationEnabledForCurrentPost,
Expand Down Expand Up @@ -253,72 +251,3 @@ describe( 'getDefaultRenderingMode', () => {
} );
} );
} );

describe( 'getCanvasHeight', () => {
// Default viewport breakpoints: Mobile 480, Tablet 782.
function setupRegistry( { isZoomOut = false, viewport } = {} ) {
getCanvasHeight.registry = {
select: ( store ) => {
if ( store === blockEditorStore ) {
// In a real registry, `unlock( select( blockEditorStore ) )`
// returns the combined public + private selectors, so both
// getSettings and isZoomOut live in the unlocked map.
const selectors = {};
lock( selectors, {
getSettings: () => ( {
__experimentalFeatures: viewport
? { viewport }
: {},
} ),
isZoomOut: () => isZoomOut,
} );
return selectors;
}
},
};
}

it( 'keeps the portrait aspect ratio at the inset mobile preview width', () => {
// Mobile aspect ratio is 8/5 (portrait): 479 * 8/5 = 766 (rounded).
setupRegistry();
expect( getCanvasHeight( { canvasWidth: 479 } ) ).toBe( 766 );
} );

it( 'keeps the portrait aspect ratio at the inset tablet preview width', () => {
// Tablet aspect ratio is 4/3 (portrait): 781 * 4/3 = 1041 (rounded).
setupRegistry();
expect( getCanvasHeight( { canvasWidth: 781 } ) ).toBe( 1041 );
} );

it( 'returns undefined for desktop (no aspect ratio applies)', () => {
setupRegistry();
expect( getCanvasHeight( { canvasWidth: 1200 } ) ).toBeUndefined();
} );

it( 'returns undefined when zoom-out is active', () => {
setupRegistry( { isZoomOut: true } );
expect( getCanvasHeight( { canvasWidth: 479 } ) ).toBeUndefined();
} );

it( 'returns undefined when the width is dragged within a device band but is not the preset', () => {
// 400 resolves to Mobile (at or below the 480 breakpoint) but is not the
// 479 preset, so the device height does not apply and the canvas fills.
setupRegistry();
expect( getCanvasHeight( { canvasWidth: 400 } ) ).toBeUndefined();
} );

it( 'returns undefined when canvasWidth is not set', () => {
setupRegistry();
expect( getCanvasHeight( {} ) ).toBeUndefined();
} );

it( 'uses custom viewport breakpoints when provided', () => {
// Custom mobile preset 639: 639 * 8/5 = 1022 (rounded).
setupRegistry( {
viewport: { mobile: '640px', tablet: '1024px' },
} );
expect( getCanvasHeight( { canvasWidth: 639 } ) ).toBe( 1022 );
// Custom tablet preset 1023: 1023 * 4/3 = 1364.
expect( getCanvasHeight( { canvasWidth: 1023 } ) ).toBe( 1364 );
} );
} );
Loading