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

Multi select: set focus back after attempt #19720

Merged
merged 2 commits into from
Jan 17, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ function selector( select ) {
};
}

function toggleRichText( container, toggle ) {
Array
.from( container.querySelectorAll( '.rich-text' ) )
.forEach( ( node ) => {
if ( toggle ) {
node.setAttribute( 'contenteditable', true );
} else {
node.removeAttribute( 'contenteditable' );
}
} );
}

export default function useMultiSelection( ref ) {
const {
isSelectionEnabled,
Expand All @@ -72,14 +84,15 @@ export default function useMultiSelection( ref ) {
} = useDispatch( 'core/block-editor' );
const rafId = useRef();
const startClientId = useRef();
const anchorElement = useRef();

/**
* When the component updates, and there is multi selection, we need to
* select the entire block contents.
*/
useEffect( () => {
if ( ! hasMultiSelection || isMultiSelecting ) {
if ( ! selectedBlockClientId ) {
if ( ! selectedBlockClientId || isMultiSelecting ) {
return;
}

Expand Down Expand Up @@ -167,6 +180,19 @@ export default function useMultiSelection( ref ) {
rafId.current = window.requestAnimationFrame( () => {
onSelectionChange();
stopMultiSelect();
toggleRichText( ref.current, true );

const selection = window.getSelection();

// If the anchor element contains the selection, set focus back to
// the anchor element.
if ( selection.rangeCount ) {
const { commonAncestorContainer } = selection.getRangeAt( 0 );

if ( anchorElement.current.contains( commonAncestorContainer ) ) {
anchorElement.current.focus();
}
}
} );
}, [ onSelectionChange, stopMultiSelect ] );

Expand All @@ -187,6 +213,7 @@ export default function useMultiSelection( ref ) {
}

startClientId.current = clientId;
anchorElement.current = document.activeElement;
startMultiSelect();

// `onSelectionStart` is called after `mousedown` and `mouseleave`
Expand All @@ -203,7 +230,6 @@ export default function useMultiSelection( ref ) {
// especially in Safari for the blocks that are asynchonously rendered.
// To ensure the browser instantly removes the selection boundaries, we
// remove the contenteditable attributes manually.
Array.from( ref.current.querySelectorAll( '.rich-text' ) )
.forEach( ( node ) => node.removeAttribute( 'contenteditable' ) );
toggleRichText( ref.current, false );
}, [ isSelectionEnabled, startMultiSelect, onSelectionEnd ] );
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ exports[`Multi-block selection should only trigger multi-selection when at the e
<!-- /wp:paragraph -->"
`;

exports[`Multi-block selection should return original focus after failed multi selection attempt 1`] = `
"<!-- wp:paragraph -->
<p>2</p>
<!-- /wp:paragraph -->"
`;

exports[`Multi-block selection should use selection direction to determine vertical edge 1`] = `
"<!-- wp:paragraph -->
<p>1<br>2.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,49 @@ describe( 'Multi-block selection', () => {

expect( await getEditedPostContent() ).toMatchSnapshot();
} );

it( 'should return original focus after failed multi selection attempt', async () => {
await clickBlockAppender();
await page.keyboard.type( '1' );
await page.keyboard.type( '2' );
await page.keyboard.press( 'ArrowLeft' );

const [ coord1, coord2 ] = await page.evaluate( () => {
const selection = window.getSelection();

if ( ! selection.rangeCount ) {
return;
}

const range = selection.getRangeAt( 0 );
const rect1 = range.getClientRects()[ 0 ];
const element = document.querySelector( '.wp-block-paragraph' );
const rect2 = element.getBoundingClientRect();

return [
{
x: rect1.x,
y: rect1.y + ( rect1.height / 2 ),
},
{
// Move a bit outside the paragraph.
x: rect2.x - 10,
y: rect2.y + ( rect2.height / 2 ),
},
];
} );

await page.mouse.move( coord1.x, coord1.y );
await page.mouse.down();
await page.mouse.move( coord2.x, coord2.y, { steps: 10 } );
await page.mouse.up();

// Wait for the selection to update.
await page.evaluate( () => new Promise( window.requestAnimationFrame ) );

// Only "1" should be deleted.
await page.keyboard.press( 'Backspace' );

expect( await getEditedPostContent() ).toMatchSnapshot();
} );
} );