Skip to content

Commit

Permalink
List View: Add keyboard shortcut to collapse list view items other th…
Browse files Browse the repository at this point in the history
…an the focused item (#59978)

* List View: Add keyboard shortcut to collapse list view items other than the focused item

* Remove keyboard shortcut info from customize-widgets as there is no list view in that context

* Add e2e test coverage

* Try adding some state to get it working from the editor canvas

* Skip keyboard shortcut in the editor canvas if the event is fired from a text field

* Add a few tests

Unlinked contributors: jarekmorawski.

Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
Co-authored-by: jasmussen <joen@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: aaronrobertshaw <aaronrobertshaw@git.wordpress.org>
Co-authored-by: richtabor <richtabor@git.wordpress.org>
  • Loading branch information
6 people authored Mar 27, 2024
1 parent a7d3a88 commit e30a12b
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 6 deletions.
19 changes: 18 additions & 1 deletion packages/block-editor/src/components/block-tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { isTextField } from '@wordpress/dom';
import { Popover } from '@wordpress/components';
import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts';
import { useRef } from '@wordpress/element';
Expand All @@ -20,6 +21,7 @@ import { store as blockEditorStore } from '../../store';
import usePopoverScroll from '../block-popover/use-popover-scroll';
import ZoomOutModeInserters from './zoom-out-mode-inserters';
import { useShowBlockTools } from './use-show-block-tools';
import { unlock } from '../../lock-unlock';

function selector( select ) {
const {
Expand Down Expand Up @@ -79,7 +81,8 @@ export default function BlockTools( {
selectBlock,
moveBlocksUp,
moveBlocksDown,
} = useDispatch( blockEditorStore );
expandBlock,
} = unlock( useDispatch( blockEditorStore ) );

function onKeyDown( event ) {
if ( event.defaultPrevented ) return;
Expand Down Expand Up @@ -140,6 +143,20 @@ export default function BlockTools( {
// In effect, to the user this feels like deselecting the multi-selection.
selectBlock( clientIds[ 0 ] );
}
} else if ( isMatch( 'core/block-editor/collapse-list-view', event ) ) {
// If focus is currently within a text field, such as a rich text block or other editable field,
// skip collapsing the list view, and allow the keyboard shortcut to be handled by the text field.
// This condition checks for both the active element and the active element within an iframed editor.
if (
isTextField( event.target ) ||
isTextField(
event.target?.contentWindow?.document?.activeElement
)
) {
return;
}
event.preventDefault();
expandBlock( clientId );
}
}

Expand Down
11 changes: 11 additions & 0 deletions packages/block-editor/src/components/keyboard-shortcuts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ function KeyboardShortcutsRegister() {
character: 'y',
},
} );

// List view shortcuts.
registerShortcut( {
name: 'core/block-editor/collapse-list-view',
category: 'list-view',
description: __( 'Collapse all other items.' ),
keyCombination: {
modifier: 'alt',
character: 'l',
},
} );
}, [ registerShortcut ] );

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function ListViewBlockSelectButton(
getPreviousBlockClientId,
getBlockRootClientId,
getBlockOrder,
getBlockParents,
getBlocksByClientId,
canRemoveBlocks,
} = useSelect( blockEditorStore );
Expand All @@ -72,7 +73,7 @@ function ListViewBlockSelectButton(
const isMatch = useShortcutEventMatch();
const isSticky = blockInformation?.positionType === 'sticky';
const images = useListViewImages( { clientId, isExpanded } );
const { rootClientId } = useListViewContext();
const { collapseAll, expand, rootClientId } = useListViewContext();

const positionLabel = blockInformation?.positionLabel
? sprintf(
Expand Down Expand Up @@ -227,6 +228,17 @@ function ListViewBlockSelectButton(
blockClientIds[ blockClientIds.length - 1 ],
null
);
} else if ( isMatch( 'core/block-editor/collapse-list-view', event ) ) {
if ( event.defaultPrevented ) {
return;
}
event.preventDefault();
const { firstBlockClientId } = getBlocksToUpdate();
const blockParents = getBlockParents( firstBlockClientId, false );
// Collapse all blocks.
collapseAll();
// Expand all parents of the current block.
expand( blockParents );
}
}

Expand Down
19 changes: 18 additions & 1 deletion packages/block-editor/src/components/list-view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import ListViewDropIndicatorPreview from './drop-indicator';
import useBlockSelection from './use-block-selection';
import useListViewBlockIndexes from './use-list-view-block-indexes';
import useListViewClientIds from './use-list-view-client-ids';
import useListViewCollapseItems from './use-list-view-collapse-items';
import useListViewDropZone from './use-list-view-drop-zone';
import useListViewExpandSelectedItem from './use-list-view-expand-selected-item';
import { store as blockEditorStore } from '../../store';
Expand All @@ -45,6 +46,9 @@ import { focusListItem } from './utils';
import useClipboardHandler from './use-clipboard-handler';

const expanded = ( state, action ) => {
if ( action.type === 'clear' ) {
return {};
}
if ( Array.isArray( action.clientIds ) ) {
return {
...state,
Expand Down Expand Up @@ -194,7 +198,10 @@ function ListViewComponent(
if ( ! clientId ) {
return;
}
setExpandedState( { type: 'expand', clientIds: [ clientId ] } );
const clientIds = Array.isArray( clientId )
? clientId
: [ clientId ];
setExpandedState( { type: 'expand', clientIds } );
},
[ setExpandedState ]
);
Expand All @@ -207,6 +214,9 @@ function ListViewComponent(
},
[ setExpandedState ]
);
const collapseAll = useCallback( () => {
setExpandedState( { type: 'clear' } );
}, [ setExpandedState ] );
const expandRow = useCallback(
( row ) => {
expand( row?.dataset?.block );
Expand All @@ -232,6 +242,11 @@ function ListViewComponent(
[ updateBlockSelection ]
);

useListViewCollapseItems( {
collapseAll,
expand,
} );

const firstDraggedBlockClientId = draggedClientIds?.[ 0 ];

// Convert a blockDropTarget into indexes relative to the blocks in the list view.
Expand Down Expand Up @@ -282,6 +297,7 @@ function ListViewComponent(
expand,
firstDraggedBlockIndex,
collapse,
collapseAll,
BlockSettingsMenu,
listViewInstanceId: instanceId,
AdditionalBlockContent,
Expand All @@ -299,6 +315,7 @@ function ListViewComponent(
expand,
firstDraggedBlockIndex,
collapse,
collapseAll,
BlockSettingsMenu,
instanceId,
AdditionalBlockContent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';

export default function useListViewCollapseItems( { collapseAll, expand } ) {
const { expandedBlock, getBlockParents } = useSelect( ( select ) => {
const { getBlockParents: _getBlockParents, getExpandedBlock } = unlock(
select( blockEditorStore )
);
return {
expandedBlock: getExpandedBlock(),
getBlockParents: _getBlockParents,
};
}, [] );

// Collapse all but the specified block when the expanded block client Id changes.
useEffect( () => {
if ( expandedBlock ) {
const blockParents = getBlockParents( expandedBlock, false );
// Collapse all blocks and expand the block's parents.
collapseAll();
expand( blockParents );
}
}, [ collapseAll, expand, expandedBlock, getBlockParents ] );
}
12 changes: 12 additions & 0 deletions packages/block-editor/src/store/private-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,15 @@ export function stopDragging() {
type: 'STOP_DRAGGING',
};
}

/**
* @param {string|null} clientId The block's clientId, or `null` to clear.
*
* @return {Object} Action object.
*/
export function expandBlock( clientId ) {
return {
type: 'SET_BLOCK_EXPANDED_IN_LIST_VIEW',
clientId,
};
}
11 changes: 11 additions & 0 deletions packages/block-editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,14 @@ export function getLastFocus( state ) {
export function isDragging( state ) {
return state.isDragging;
}

/**
* Retrieves the expanded block from the state.
*
* @param {Object} state Block editor state.
*
* @return {string|null} The client ID of the expanded block, if set.
*/
export function getExpandedBlock( state ) {
return state.expandedBlock;
}
22 changes: 22 additions & 0 deletions packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,27 @@ export function highlightedBlock( state, action ) {
return state;
}

/**
* Reducer returning current expanded block in the list view.
*
* @param {string|null} state Current expanded block.
* @param {Object} action Dispatched action.
*
* @return {string|null} Updated state.
*/
export function expandedBlock( state = null, action ) {
switch ( action.type ) {
case 'SET_BLOCK_EXPANDED_IN_LIST_VIEW':
return action.clientId;
case 'SELECT_BLOCK':
if ( action.clientId !== state ) {
return null;
}
}

return state;
}

/**
* Reducer returning the block insertion event list state.
*
Expand Down Expand Up @@ -2064,6 +2085,7 @@ const combinedReducers = combineReducers( {
lastFocus,
editorMode,
hasBlockMovingClientId,
expandedBlock,
highlightedBlock,
lastBlockInserted,
temporarilyEditingAsBlocks,
Expand Down
10 changes: 10 additions & 0 deletions packages/block-editor/src/store/test/private-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import {
hideBlockInterface,
showBlockInterface,
expandBlock,
__experimentalUpdateSettings,
setOpenedBlockSettingsMenu,
startDragging,
Expand Down Expand Up @@ -113,4 +114,13 @@ describe( 'private actions', () => {
} );
} );
} );

describe( 'expandBlock', () => {
it( 'should return the SET_BLOCK_EXPANDED_IN_LIST_VIEW action', () => {
expect( expandBlock( 'block-1' ) ).toEqual( {
type: 'SET_BLOCK_EXPANDED_IN_LIST_VIEW',
clientId: 'block-1',
} );
} );
} );
} );
13 changes: 13 additions & 0 deletions packages/block-editor/src/store/test/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
isBlockSubtreeDisabled,
getEnabledClientIdsTree,
getEnabledBlockParents,
getExpandedBlock,
isDragging,
} from '../private-selectors';
import { getBlockEditingMode } from '../selectors';
Expand Down Expand Up @@ -496,4 +497,16 @@ describe( 'private selectors', () => {
expect( isDragging( state ) ).toBe( false );
} );
} );

describe( 'getExpandedBlock', () => {
it( 'should return the expanded block', () => {
const state = {
expandedBlock: '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f',
};

expect( getExpandedBlock( state ) ).toBe(
'9b9c5c3f-2e46-4f02-9e14-9fe9515b958f'
);
} );
} );
} );
26 changes: 26 additions & 0 deletions packages/block-editor/src/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
lastBlockInserted,
blockEditingModes,
openedBlockSettingsMenu,
expandedBlock,
} from '../reducer';

const noop = () => {};
Expand Down Expand Up @@ -3459,4 +3460,29 @@ describe( 'state', () => {
expect( state ).toBe( null );
} );
} );

describe( 'expandedBlock', () => {
it( 'should return null by default', () => {
expect( expandedBlock( undefined, {} ) ).toBe( null );
} );

it( 'should set client id for expanded block', () => {
const state = expandedBlock( null, {
type: 'SET_BLOCK_EXPANDED_IN_LIST_VIEW',
clientId: '14501cc2-90a6-4f52-aa36-ab6e896135d1',
} );
expect( state ).toBe( '14501cc2-90a6-4f52-aa36-ab6e896135d1' );
} );

it( 'should clear the state when a block is selected', () => {
const state = expandedBlock(
'14501cc2-90a6-4f52-aa36-ab6e896135d1',
{
type: 'SELECT_BLOCK',
clientId: 'a-different-block',
}
);
expect( state ).toBe( null );
} );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ export function KeyboardShortcutHelpModal( { isModalActive, toggleModal } ) {
title={ __( 'Text formatting' ) }
shortcuts={ textFormattingShortcuts }
/>
<ShortcutCategorySection
title={ __( 'List View shortcuts' ) }
categoryName="list-view"
/>
</Modal>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ export default function KeyboardShortcutHelpModal() {
title={ __( 'Text formatting' ) }
shortcuts={ textFormattingShortcuts }
/>
<ShortcutCategorySection
title={ __( 'List View shortcuts' ) }
categoryName="list-view"
/>
</Modal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ export default function KeyboardShortcutHelpModal( {
title={ __( 'Text formatting' ) }
shortcuts={ textFormattingShortcuts }
/>
<ShortcutCategorySection
title={ __( 'List View shortcuts' ) }
categoryName="list-view"
/>
</Modal>
);
}
Loading

0 comments on commit e30a12b

Please sign in to comment.