Skip to content

Commit

Permalink
Try adding a 'spotlight mode' type effect when template part or child…
Browse files Browse the repository at this point in the history
… is selected. (#25656)

* apply mode from blocklist + css

* added class to most active template part

* css rules to support nested/active template parts

* gate template part check behind site editor experiment

* rebrand/refactor to support more entity blocks if necessary

* move logic to selector

* change selector name and add doc comments

* update tests for updated selector

* add test for new selector

* make test state reusable for updated parents tests

* cleanup document-actions useSecondaryText to use new selector

* add dependency to selector for selectionEnd

* remove unnecessary style selectors

* make selector experimental

* refactor list of blocks to editor settings

* selector return early if no names are passed

* update css comment from template part to entity

* rename the settings list to include 'entity'

* remove unused import
  • Loading branch information
Addison-Stavlo authored Oct 9, 2020
1 parent 942f19d commit 4d544b7
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,17 @@ _Returns_

<a name="getBlockParentsByBlockName" href="#getBlockParentsByBlockName">#</a> **getBlockParentsByBlockName**

Given a block client ID and a block name,
returns the list of all its parents from top to bottom,
filtered by the given name.
Given a block client ID and a block name, returns the list of all its parents
from top to bottom, filtered by the given name(s). For example, if passed
'core/group' as the blockName, it will only return parents which are group
blocks. If passed `[ 'core/group', 'core/cover']`, as the blockName, it will
return parents which are group blocks and parents which are cover blocks.

_Parameters_

- _state_ `Object`: Editor state.
- _clientId_ `string`: Block from which to find root client ID.
- _blockName_ `string`: Block name to filter.
- _blockName_ `(string|Array<string>)`: Block name(s) to filter.
- _ascending_ `boolean`: Order results from bottom to top (true) or top to bottom (false).

_Returns_
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function BlockListBlock( {
toggleSelection,
index,
enableAnimation,
activeEntityBlockId,
} ) {
// In addition to withSelect, we should favor using useSelect in this
// component going forward to avoid leaking new props to the public API
Expand Down Expand Up @@ -166,6 +167,7 @@ function BlockListBlock( {
isFocusMode && ( isSelected || isAncestorOfSelectedBlock ),
'is-focus-mode': isFocusMode,
'has-child-selected': isAncestorOfSelectedBlock && ! isDragging,
'is-active-entity': activeEntityBlockId === clientId,
},
className
);
Expand Down
11 changes: 11 additions & 0 deletions packages/block-editor/src/components/block-list/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,21 @@ function Items( {
const {
getBlockOrder,
getBlockListSettings,
getSettings,
getSelectedBlockClientId,
getMultiSelectedBlockClientIds,
hasMultiSelection,
getGlobalBlockCount,
isTyping,
isDraggingBlocks,
__experimentalGetActiveBlockIdByBlockNames,
} = select( 'core/block-editor' );

// Determine if there is an active entity area to spotlight.
const activeEntityBlockId = __experimentalGetActiveBlockIdByBlockNames(
getSettings().__experimentalSpotlightEntityBlocks
);

return {
blockClientIds: getBlockOrder( rootClientId ),
selectedBlockClientId: getSelectedBlockClientId(),
Expand All @@ -82,6 +89,7 @@ function Items( {
! isTyping() &&
getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD,
isDraggingBlocks: isDraggingBlocks(),
activeEntityBlockId,
};
}

Expand All @@ -93,6 +101,7 @@ function Items( {
hasMultiSelection,
enableAnimation,
isDraggingBlocks,
activeEntityBlockId,
} = useSelect( selector, [ rootClientId ] );

const dropTargetIndex = useBlockDropZone( {
Expand Down Expand Up @@ -131,7 +140,9 @@ function Items( {
'is-dropping-horizontally':
isDropTarget &&
orientation === 'horizontal',
'has-active-entity': activeEntityBlockId,
} ) }
activeEntityBlockId={ activeEntityBlockId }
/>
</AsyncModeProvider>
);
Expand Down
15 changes: 15 additions & 0 deletions packages/block-editor/src/components/block-list/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@
opacity: 1;
}
}

// Active entity spotlight.
&.has-active-entity:not(.is-focus-mode) {
opacity: 0.5;
transition: opacity 0.1s linear;
@include reduce-motion("transition");

&.is-active-entity,
&.has-child-selected,
&:not(.has-child-selected) .block-editor-block-list__block,
&.is-active-entity .block-editor-block-list__block,
.is-active-entity .block-editor-block-list__block {
opacity: 1;
}
}
}

.block-editor-block-list__layout .block-editor-block-list__block,
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/store/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const SETTINGS_DEFAULTS = {
__mobileEnablePageTemplates: false,
__experimentalBlockPatterns: [],
__experimentalBlockPatternCategories: [],
__experimentalSpotlightEntityBlocks: [],

// gradients setting is not used anymore now defaults are passed from theme.json on the server and core has its own defaults.
// The setting is only kept for backward compatibility purposes.
Expand Down
69 changes: 61 additions & 8 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,14 +512,16 @@ export const getBlockParents = createSelector(
);

/**
* Given a block client ID and a block name,
* returns the list of all its parents from top to bottom,
* filtered by the given name.
* Given a block client ID and a block name, returns the list of all its parents
* from top to bottom, filtered by the given name(s). For example, if passed
* 'core/group' as the blockName, it will only return parents which are group
* blocks. If passed `[ 'core/group', 'core/cover']`, as the blockName, it will
* return parents which are group blocks and parents which are cover blocks.
*
* @param {Object} state Editor state.
* @param {string} clientId Block from which to find root client ID.
* @param {string} blockName Block name to filter.
* @param {boolean} ascending Order results from bottom to top (true) or top to bottom (false).
* @param {Object} state Editor state.
* @param {string} clientId Block from which to find root client ID.
* @param {string|string[]} blockName Block name(s) to filter.
* @param {boolean} ascending Order results from bottom to top (true) or top to bottom (false).
*
* @return {Array} ClientIDs of the parent blocks.
*/
Expand All @@ -532,7 +534,12 @@ export const getBlockParentsByBlockName = createSelector(
id,
name: getBlockName( state, id ),
} ) ),
{ name: blockName }
( { name } ) => {
if ( Array.isArray( blockName ) ) {
return blockName.includes( name );
}
return name === blockName;
}
),
( { id } ) => id
);
Expand Down Expand Up @@ -1791,3 +1798,49 @@ export function isBlockHighlighted( state, clientId ) {
export function areInnerBlocksControlled( state, clientId ) {
return !! state.blocks.controlledInnerBlocks[ clientId ];
}

/**
* Returns the clientId for the first 'active' block of a given array of block names.
* A block is 'active' if it (or a child) is the selected block.
* Returns the first match moving up the DOM from the selected block.
*
* @param {Object} state Global application state.
* @param {string[]} validBlocksNames The names of block types to check for.
*
* @return {string} The matching block's clientId.
*/
export const __experimentalGetActiveBlockIdByBlockNames = createSelector(
( state, validBlockNames ) => {
if ( ! validBlockNames.length ) {
return null;
}
// Check if selected block is a valid entity area.
const selectedBlockClientId = getSelectedBlockClientId( state );
if (
validBlockNames.includes(
getBlockName( state, selectedBlockClientId )
)
) {
return selectedBlockClientId;
}
// Check if first selected block is a child of a valid entity area.
const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds(
state
);
const entityAreaParents = getBlockParentsByBlockName(
state,
selectedBlockClientId || multiSelectedBlockClientIds[ 0 ],
validBlockNames
);
if ( entityAreaParents ) {
// Last parent closest/most interior.
return last( entityAreaParents );
}
return null;
},
( state, validBlockNames ) => [
state.selectionStart.clientId,
state.selectionEnd.clientId,
validBlockNames,
]
);
Loading

0 comments on commit 4d544b7

Please sign in to comment.