Skip to content

Commit

Permalink
Fix "Select all" behavior in the editor (#33167)
Browse files Browse the repository at this point in the history
* Make isEntirelySelected consider "deep" first and last children in case the selection does not belong to the top-level children directly

* Rollback unit tests – jsdom does not support contentEditable jsdom/jsdom#1670

* Add an e2e test for multi-block selection

* Adjust e2e tests

* Add a separate test for gradual select-all from within the list block

* Cosmetic adjustments to the e2e test

* Update the snapshot
  • Loading branch information
adamziel committed Jul 5, 2021
1 parent 71ae88a commit e0516ac
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 7 deletions.
36 changes: 29 additions & 7 deletions packages/dom/src/dom/is-entirely-selected.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,37 @@ export default function isEntirelySelected( element ) {

const lastChild = element.lastChild;
assertIsDefined( lastChild, 'lastChild' );
const lastChildContentLength =
lastChild.nodeType === lastChild.TEXT_NODE
? /** @type {Text} */ ( lastChild ).data.length
: lastChild.childNodes.length;
const endContainerContentLength =
endContainer.nodeType === endContainer.TEXT_NODE
? /** @type {Text} */ ( endContainer ).data.length
: endContainer.childNodes.length;

return (
startContainer === element.firstChild &&
endContainer === element.lastChild &&
isDeepChild( startContainer, element, 'firstChild' ) &&
isDeepChild( endContainer, element, 'lastChild' ) &&
startOffset === 0 &&
endOffset === lastChildContentLength
endOffset === endContainerContentLength
);
}

/**
* Check whether the contents of the element have been entirely selected.
* Returns true if there is no possibility of selection.
*
* @param {HTMLElement|Node} query The element to check.
* @param {HTMLElement} container The container that we suspect "query" may be a first or last child of.
* @param {"firstChild"|"lastChild"} propName "firstChild" or "lastChild"
*
* @return {boolean} True if query is a deep first/last child of container, false otherwise.
*/
function isDeepChild( query, container, propName ) {
/** @type {HTMLElement | ChildNode | null} */
let candidate = container;
do {
if ( query === candidate ) {
return true;
}
candidate = candidate[ propName ];
} while ( candidate );
return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ exports[`Multi-block selection should gradually multi-select 2`] = `
<!-- /wp:columns -->"
`;
exports[`Multi-block selection should multi-select from within the list block 1`] = `
"<!-- wp:paragraph -->
<p>1</p>
<!-- /wp:paragraph -->
<!-- wp:list -->
<ul><li>1</li></ul>
<!-- /wp:list -->"
`;
exports[`Multi-block selection should not multi select single block 1`] = `
"<!-- wp:paragraph -->
<p></p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -655,4 +655,25 @@ describe( 'Multi-block selection', () => {
// Expect both columns to be deleted.
expect( await getEditedPostContent() ).toMatchSnapshot();
} );

it( 'should multi-select from within the list block', async () => {
await clickBlockAppender();
// Select a paragraph.
await page.keyboard.type( '1' );
await page.keyboard.press( 'Enter' );
// Add a list
await page.keyboard.type( '/list' );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( '1' );

// Confirm correct setup: a paragraph and a list
expect( await getEditedPostContent() ).toMatchSnapshot();

await pressKeyWithModifier( 'primary', 'a' );
await pressKeyWithModifier( 'primary', 'a' );

await page.waitForSelector(
'[data-type="core/paragraph"].is-multi-selected'
);
} );
} );

0 comments on commit e0516ac

Please sign in to comment.