diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 4f7c931334893..6ea58ca500443 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -26,19 +26,27 @@ import ListViewDropIndicator from './drop-indicator'; import useBlockSelection from './use-block-selection'; import useListViewClientIds from './use-list-view-client-ids'; import useListViewDropZone from './use-list-view-drop-zone'; +import useListViewExpandSelectedItem from './use-list-view-expand-selected-item'; import { store as blockEditorStore } from '../../store'; const expanded = ( state, action ) => { - switch ( action.type ) { - case 'expand': - return { ...state, ...{ [ action.clientId ]: true } }; - case 'collapse': - return { ...state, ...{ [ action.clientId ]: false } }; - default: - return state; + if ( Array.isArray( action.clientIds ) ) { + return { + ...state, + ...action.clientIds.reduce( + ( newState, id ) => ( { + ...newState, + [ id ]: action.type === 'expand', + } ), + {} + ), + }; } + return state; }; +export const BLOCK_LIST_ITEM_HEIGHT = 36; + /** * Wrap `ListViewRows` with `TreeGrid`. ListViewRows is a * recursive component (it renders itself), so this ensures TreeGrid is only @@ -96,6 +104,17 @@ function ListView( const treeGridRef = useMergeRefs( [ elementRef, dropZoneRef, ref ] ); const isMounted = useRef( false ); + const { setSelectedTreeId } = useListViewExpandSelectedItem( { + firstSelectedBlockClientId: selectedClientIds[ 0 ], + setExpandedState, + } ); + const selectEditorBlock = useCallback( + ( event, clientId ) => { + updateBlockSelection( event, clientId ); + setSelectedTreeId( clientId ); + }, + [ setSelectedTreeId, updateBlockSelection ] + ); useEffect( () => { isMounted.current = true; }, [] ); @@ -105,7 +124,7 @@ function ListView( // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context. const [ fixedListWindow ] = useFixedWindowList( elementRef, - 36, + BLOCK_LIST_ITEM_HEIGHT, visibleBlockCount, { useWindowing: __experimentalPersistentListViewFeatures, @@ -118,7 +137,7 @@ function ListView( if ( ! clientId ) { return; } - setExpandedState( { type: 'expand', clientId } ); + setExpandedState( { type: 'expand', clientIds: [ clientId ] } ); }, [ setExpandedState ] ); @@ -127,7 +146,7 @@ function ListView( if ( ! clientId ) { return; } - setExpandedState( { type: 'collapse', clientId } ); + setExpandedState( { type: 'collapse', clientIds: [ clientId ] } ); }, [ setExpandedState ] ); @@ -196,7 +215,7 @@ function ListView( { + const { getBlockParents } = select( blockEditorStore ); + return { + selectedBlockParentClientIds: getBlockParents( + firstSelectedBlockClientId, + false + ), + }; + }, + [ firstSelectedBlockClientId ] + ); + + const parentClientIds = + Array.isArray( selectedBlockParentClientIds ) && + selectedBlockParentClientIds.length + ? selectedBlockParentClientIds + : null; + + // Expand tree when a block is selected. + useEffect( () => { + // If the selectedTreeId is the same as the selected block, + // it means that the block was selected using the block list tree. + if ( selectedTreeId === firstSelectedBlockClientId ) { + return; + } + + // If the selected block has parents, get the top-level parent. + if ( parentClientIds ) { + // If the selected block has parents, + // expand the tree branch. + setExpandedState( { + type: 'expand', + clientIds: selectedBlockParentClientIds, + } ); + } + }, [ firstSelectedBlockClientId ] ); + + return { + setSelectedTreeId, + }; +} diff --git a/packages/e2e-tests/specs/editor/various/list-view.test.js b/packages/e2e-tests/specs/editor/various/list-view.test.js index aef74c82a2092..9c5ac2327631f 100644 --- a/packages/e2e-tests/specs/editor/various/list-view.test.js +++ b/packages/e2e-tests/specs/editor/various/list-view.test.js @@ -5,6 +5,7 @@ import { createNewPost, insertBlock, getEditedPostContent, + openListView, pressKeyWithModifier, pressKeyTimes, } from '@wordpress/e2e-test-utils'; @@ -20,6 +21,10 @@ async function dragAndDrop( draggableElement, targetElement, offsetY ) { return await page.mouse.dragAndDrop( draggablePoint, targetPoint ); } +async function getBlockListLeafNodes() { + return await page.$$( '.block-editor-list-view-leaf' ); +} + describe( 'List view', () => { beforeAll( async () => { await page.setDragInterception( true ); @@ -97,4 +102,51 @@ describe( 'List view', () => { // https://github.com/WordPress/gutenberg/issues/38763. expect( console ).not.toHaveErrored(); } ); + + it( 'should expand nested list items', async () => { + // Insert some blocks of different types. + await insertBlock( 'Cover' ); + + // Click first color option from the block placeholder's color picker to make the inner blocks appear. + const colorPickerButton = await page.waitForSelector( + '.wp-block-cover__placeholder-background-options .components-circular-option-picker__option-wrapper:first-child button' + ); + await colorPickerButton.click(); + + // Open list view. + await openListView(); + + // Things start off expanded. + expect( await getBlockListLeafNodes() ).toHaveLength( 2 ); + + const blockListExpanders = await page.$$( + '.block-editor-list-view__expander' + ); + + // Collapse the first block + await blockListExpanders[ 0 ].click(); + + // Check that we're collapsed + expect( await getBlockListLeafNodes() ).toHaveLength( 1 ); + + // Focus the cover block. The paragraph is not focussed by default. + const coverBlock = await page.waitForSelector( '.wp-block-cover' ); + + await coverBlock.focus(); + + // Click the cover title placeholder. + const coverTitle = await page.waitForSelector( + '.wp-block-cover .wp-block-paragraph' + ); + + await coverTitle.click(); + + // The block list should contain two leafs and the second should be selected (and be a paragraph). + const selectedElementText = await page.$eval( + '.block-editor-list-view-leaf.is-selected .block-editor-list-view-block-contents', + ( element ) => element.innerText + ); + + expect( selectedElementText ).toContain( 'Paragraph' ); + } ); } );