Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ResizableFrame: Make keyboard accessible #52443

Merged
merged 14 commits into from
Jul 13, 2023
116 changes: 87 additions & 29 deletions packages/edit-site/src/components/resizable-frame/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import classnames from 'classnames';
import { useState, useRef, useEffect } from '@wordpress/element';
import {
ResizableBox,
Tooltip,
VisuallyHidden,
__unstableMotion as motion,
} from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
import { useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
Expand Down Expand Up @@ -42,6 +46,8 @@ const FRAME_TARGET_ASPECT_RATIO = 9 / 19.5;
// viewport's edge. If the frame is resized to be closer to the viewport's edge
// than this distance, then "canvas mode" will be enabled.
const SNAP_TO_EDIT_CANVAS_MODE_THRESHOLD = 200;
// Default size for the `frameSize` state.
const INITIAL_FRAME_SIZE = { width: '100%', height: '100%' };

function calculateNewHeight( width, initialAspectRatio ) {
const lerp = ( a, b, amount ) => {
Expand Down Expand Up @@ -78,14 +84,11 @@ function ResizableFrame( {
oversizedClassName,
innerContentStyle,
} ) {
const [ frameSize, setFrameSize ] = useState( {
width: '100%',
height: '100%',
} );
const [ frameSize, setFrameSize ] = useState( INITIAL_FRAME_SIZE );
// The width of the resizable frame when a new resize gesture starts.
const [ startingWidth, setStartingWidth ] = useState();
const [ isResizing, setIsResizing ] = useState( false );
const [ isHovering, setIsHovering ] = useState( false );
const [ shouldShowHandle, setShouldShowHandle ] = useState( false );
const [ isOversized, setIsOversized ] = useState( false );
const [ resizeRatio, setResizeRatio ] = useState( 1 );
const { setCanvasMode } = unlock( useDispatch( editSiteStore ) );
Expand All @@ -94,6 +97,10 @@ function ResizableFrame( {
const initialComputedWidthRef = useRef( null );
const FRAME_TRANSITION = { type: 'tween', duration: isResizing ? 0 : 0.5 };
const frameRef = useRef( null );
const resizableHandleHelpId = useInstanceId(
ResizableFrame,
'edit-site-resizable-frame-handle-help'
);

// Remember frame dimensions on initial render.
useEffect( () => {
Expand Down Expand Up @@ -154,13 +161,43 @@ function ResizableFrame( {
if ( remainingWidth > SNAP_TO_EDIT_CANVAS_MODE_THRESHOLD ) {
// Reset the initial aspect ratio if the frame is resized slightly
// above the sidebar but not far enough to trigger full screen.
setFrameSize( { width: '100%', height: '100%' } );
setFrameSize( INITIAL_FRAME_SIZE );
} else {
// Trigger full screen if the frame is resized far enough to the left.
setCanvasMode( 'edit' );
}
};

// Handle resize by arrow keys
const handleResizableHandleKeyDown = ( event ) => {
if ( ! [ 'ArrowLeft', 'ArrowRight' ].includes( event.key ) ) {
return;
}

mirka marked this conversation as resolved.
Show resolved Hide resolved
const step = 20 * ( event.shiftKey ? 5 : 1 );
mirka marked this conversation as resolved.
Show resolved Hide resolved
const delta = step * ( event.key === 'ArrowLeft' ? 1 : -1 );
const newWidth = Math.max(
FRAME_MIN_WIDTH,
frameRef.current.resizable.offsetWidth + delta
);

setFrameSize( {
width: newWidth,
height: calculateNewHeight(
newWidth,
initialAspectRatioRef.current
),
} );

// If oversized, immediately switch to edit mode
if ( newWidth > initialComputedWidthRef.current ) {
setFrameSize( INITIAL_FRAME_SIZE );
setCanvasMode( 'edit' );
// TODO: Confirm if this is the focus behavior we want
document.querySelector( 'iframe[name=editor-canvas]' ).focus();
Copy link
Member Author

@mirka mirka Jul 8, 2023

Choose a reason for hiding this comment

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

Needs accessibility feedback: Do we want some kind of focus manipulation after the frame is resized to full width? (cc @afercia @andrewhayward)

To test

  1. Focus on the resize handle.
  2. Hit the left arrow key until the editor canvas is in full width (edit mode).
  3. Hit the tab key to see where the focus is.

Comment out line 197 to see the default behavior.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be good if focus behaved the same in this case as it does when moving to edit mode by pressing Enter after focusing the frame, where we land at the top of the editing canvas and Tab focuses the first block in Select mode.

One thing I'm noticing is that focusing on resize handle and pressing left arrow once sometimes takes me into edit mode straightaway. I'm not sure if that's desirable, as currently the drag handle, when dragged with a mouse, allows us to expand the canvas until the sidebar is about 100px wide, and only if we try expanding further than that does it switch to edit mode. Dragging with a mouse is also behaving a bit weirdly in this branch:

draghandle.mov

(I can't reproduce this 100% of the time, but it's happening consistently on Firefox and about half the time on Chrome)

Copy link
Contributor

Choose a reason for hiding this comment

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

Hello @mirka @tellthemachines I'd agree focus management should work in the same way it works when switching to the Edit view when pressing Enter on the iframe. However, I'm not sure it should land on the first block... I created #51570 a few days ago to reconsider initial focus on the Site editor > Edit view.

}
};

const frameAnimationVariants = {
default: {
flexGrow: 0,
Expand All @@ -173,16 +210,26 @@ function ResizableFrame( {
};

const resizeHandleVariants = {
default: {
hidden: {
opacity: 0,
left: 0,
},
visible: {
opacity: 1,
left: -16,
},
resizing: {
active: {
opacity: 1,
left: -16,
scaleY: 1.3,
},
};
const currentResizeHandleVariant = ( () => {
if ( isResizing ) {
return 'active';
}
return shouldShowHandle ? 'visible' : 'hidden';
} )();

return (
<ResizableBox
Expand Down Expand Up @@ -217,28 +264,39 @@ function ResizableFrame( {
minWidth={ FRAME_MIN_WIDTH }
maxWidth={ isFullWidth ? '100%' : '150%' }
maxHeight={ '100%' }
onMouseOver={ () => setIsHovering( true ) }
onMouseOut={ () => setIsHovering( false ) }
onFocus={ () => setShouldShowHandle( true ) }
onBlur={ () => setShouldShowHandle( false ) }
onMouseOver={ () => setShouldShowHandle( true ) }
onMouseOut={ () => setShouldShowHandle( false ) }
handleComponent={ {
left:
isHovering || isResizing ? (
<motion.div
key="handle"
className="edit-site-resizable-frame__handle"
variants={ resizeHandleVariants }
animate={ isResizing ? 'resizing' : 'default' }
title="Drag to resize"
initial={ {
opacity: 0,
left: 0,
} }
exit={ {
opacity: 0,
left: 0,
} }
whileHover={ { scaleY: 1.3 } }
/>
) : null,
left: (
<>
<Tooltip text={ __( 'Drag to resize' ) }>
<motion.button
key="handle"
type="button"
className={ classnames(
'edit-site-resizable-frame__handle',
{ 'is-resizing': isResizing }
) }
variants={ resizeHandleVariants }
animate={ currentResizeHandleVariant }
aria-label={ __( 'Drag to resize' ) }
aria-describedby={ resizableHandleHelpId }
onKeyDown={ handleResizableHandleKeyDown }
initial="hidden"
exit="hidden"
whileFocus="active"
whileHover="active"
/>
</Tooltip>
<VisuallyHidden id={ resizableHandleHelpId }>
mirka marked this conversation as resolved.
Show resolved Hide resolved
{ __(
'Use left and right arrow keys to resize the canvas.'
) }
</VisuallyHidden>
</>
),
} }
onResizeStart={ handleResizeStart }
onResize={ handleResize }
Expand Down
18 changes: 9 additions & 9 deletions packages/edit-site/src/components/resizable-frame/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
.edit-site-resizable-frame__handle {
align-items: center;
background-color: rgba($gray-700, 0.4);
border: 0;
border-radius: $grid-unit-05;
cursor: col-resize;
display: flex;
height: $grid-unit-80;
justify-content: flex-end;
padding: 0;
Comment on lines +33 to +39
Copy link
Member Author

Choose a reason for hiding this comment

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

Resets user agent button styles.

position: absolute;
top: calc(50% - #{$grid-unit-40});
width: $grid-unit-05;
Expand All @@ -56,16 +58,14 @@
width: $grid-unit-40;
}

&:hover,
.is-resizing & {
background-color: var(--wp-admin-theme-color);
&:focus-visible {
// Works with Windows high contrast mode while also hiding weird outline in Safari.
outline: 2px solid transparent;
}

.edit-site-resizable-frame__handle-label {
background: var(--wp-admin-theme-color);
border-radius: 2px;
color: #fff;
margin-right: $grid-unit-10;
padding: 4px 8px;
Comment on lines -64 to -69
Copy link
Member Author

Choose a reason for hiding this comment

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

Unused styles 🧹

&:hover,
&:focus,
&.is-resizing {
background-color: var(--wp-admin-theme-color);
}
}
Loading