Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 17 additions & 8 deletions packages/editor/src/components/resizable-editor/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,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';
import ResizeHandle from './resize-handle';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
Expand Down Expand Up @@ -36,11 +40,10 @@ function ResizableEditor( {
enableResizing,
width = '100%',
height = '100%',
onResizeStart,
onResizeStop,
children,
} ) {
const [ isResizing, setIsResizing ] = useState( false );
const disableMotion = useReducedMotion();
const { setCanvasWidth } = unlock( useDispatch( editorStore ) );

const resizableRef = useRef();
Expand All @@ -63,11 +66,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 @@ -77,6 +80,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 @@ -90,15 +101,13 @@ function ResizableEditor( {
} }
onResizeStart={ () => {
setIsResizing( true );
onResizeStart?.();
} }
onResize={ ( event, direction, element ) => {
updateCanvasWidth( element );
} }
onResizeStop={ ( event, direction, element ) => {
updateCanvasWidth( element );
updateCanvasWidth( element, true );
setIsResizing( false );
onResizeStop?.();
} }
minWidth={ 300 }
maxWidth="100%"
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 @@ -11,7 +11,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';
import PostTitle from '../post-title';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
Expand Down Expand Up @@ -78,6 +82,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.
*/
export function getCanvasHeight( width, containerSize ) {
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 @@ -100,7 +139,6 @@ function VisualEditor( {
styles,
hasCanvasWidth,
canvasWidth,
canvasHeight,
} = useSelect( ( select ) => {
const {
getCurrentPostId,
Expand All @@ -110,7 +148,6 @@ function VisualEditor( {
getRenderingMode,
getDeviceType,
getCanvasWidth,
getCanvasHeight,
} = unlock( select( editorStore ) );
const { getPostType, getEditedEntityRecord } = select( coreStore );
const postTypeSlug = getCurrentPostType();
Expand Down Expand Up @@ -155,7 +192,6 @@ function VisualEditor( {
styles: editorSettings.styles,
hasCanvasWidth: _canvasWidth !== undefined,
canvasWidth: _canvasWidth,
canvasHeight: getCanvasHeight(),
};
}, [] );
const { isCleanNewPost } = useSelect( editorStore );
Expand All @@ -180,9 +216,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 @@ -335,14 +380,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 @@ -400,6 +449,7 @@ function VisualEditor( {

return (
<div
ref={ containerRef }
className={ clsx(
'editor-visual-editor',
// this class is here for backward compatibility reasons.
Expand All @@ -421,13 +471,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { getCanvasHeight } from '../index';

describe( 'getCanvasHeight', () => {
it( 'fills the available height at the full container width', () => {
expect( getCanvasHeight( 1000, { width: 1000, height: 800 } ) ).toBe(
800
);
} );

it( 'uses the target 9:16 aspect ratio at the minimum canvas width', () => {
// 300 / ( 9 / 16 ) = 533.33, rounded to 533.
expect( getCanvasHeight( 300, { width: 1000, height: 800 } ) ).toBe(
533
);
} );

it( 'interpolates between the container and target aspect ratios in between', () => {
// Halfway between 300 and 1000, so halfway between the container ratio
// ( 1.25 ) and the target ratio ( 0.5625 ): 650 / 0.90625 = 717.24.
expect( getCanvasHeight( 650, { width: 1000, height: 800 } ) ).toBe(
717
);
} );

it( 'grows with the canvas width', () => {
const heights = [ 300, 500, 700, 900 ].map( ( width ) =>
getCanvasHeight( width, { width: 1000, height: 800 } )
);
expect( heights ).toEqual( [ 533, 659, 733, 781 ] );
} );

it( 'never exceeds the available height', () => {
// In a short container, the 9:16 height of 533 is clamped to 300.
expect( getCanvasHeight( 300, { width: 1000, height: 300 } ) ).toBe(
300
);
} );

it( 'clamps widths outside the resizable range', () => {
// Wider than the container: the container aspect ratio applies, and the
// height is capped at the container height.
expect( getCanvasHeight( 1200, { width: 1000, height: 800 } ) ).toBe(
800
);
// Narrower than the minimum width: the target aspect ratio applies.
expect( getCanvasHeight( 200, { width: 1000, height: 800 } ) ).toBe(
356
);
} );
} );
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 @@ -25,22 +25,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 @@ -336,45 +322,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
Loading
Loading