Skip to content

Commit

Permalink
Wrap block support slots in ToolsPanel for dimensions support
Browse files Browse the repository at this point in the history
Draft wrapping block support slots in ToolsPanel

Update tests to check resetAll does not trigger onDeselect
  • Loading branch information
aaronrobertshaw committed Aug 19, 2021
1 parent 83eec08 commit b1b16a2
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 52 deletions.
4 changes: 4 additions & 0 deletions packages/block-editor/src/components/block-inspector/index.js
Expand Up @@ -129,6 +129,10 @@ const BlockInspectorSingleBlock = ( {
</div>
) }
<InspectorControls.Slot bubblesVirtually={ bubblesVirtually } />
<InspectorControls.Slot
group="dimensions"
bubblesVirtually={ false }
/>
<div>
<AdvancedControls bubblesVirtually={ bubblesVirtually } />
</div>
Expand Down
@@ -0,0 +1,76 @@
/**
* WordPress dependencies
*/
import { __experimentalToolsPanel as ToolsPanel } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { cleanEmptyObject } from '../../hooks/utils';

const labels = {
border: {
label: __( 'Border options' ),
header: __( 'Border' ),
},
color: {
label: __( 'Color options' ),
header: __( 'Color' ),
},
dimensions: {
label: __( 'Dimensions options' ),
header: __( 'Dimensions' ),
},
typography: {
label: __( 'Typography options' ),
header: __( 'Typography' ),
},
};

export default function BlockSupportToolsPanel( { group, children } ) {
const { clientId, attributes } = useSelect( ( select ) => {
const { getBlockAttributes, getSelectedBlockClientId } = select(
blockEditorStore
);
const selectedBlockClientId = getSelectedBlockClientId();

return {
clientId: selectedBlockClientId,
attributes: getBlockAttributes( selectedBlockClientId ),
};
} );
const { updateBlockAttributes } = useDispatch( blockEditorStore );

const resetAll = ( resetFilters = [] ) => {
const { style } = attributes;
let newAttributes = { style };

resetFilters.forEach( ( resetFilter ) => {
newAttributes = {
...newAttributes,
...resetFilter( newAttributes ),
};
} );

// Enforce a cleaned style object.
newAttributes = {
...newAttributes,
style: cleanEmptyObject( newAttributes.style ),
};

updateBlockAttributes( clientId, newAttributes );
};

return (
<ToolsPanel
label={ labels[ group ].label }
header={ labels[ group ].header }
resetAll={ resetAll }
>
{ children }
</ToolsPanel>
);
}
Expand Up @@ -6,11 +6,15 @@ import { createSlotFill } from '@wordpress/components';
const InspectorControlsDefault = createSlotFill( 'InspectorControls' );
const InspectorControlsBlock = createSlotFill( 'InspectorControlsBlock' );
const InspectorControlsAdvanced = createSlotFill( 'InspectorAdvancedControls' );
const InspectorControlsDimensions = createSlotFill(
'InspectorDimensionsControls'
);

const groups = {
default: InspectorControlsDefault,
block: InspectorControlsBlock,
advanced: InspectorControlsAdvanced,
dimensions: InspectorControlsDimensions,
};

export default groups;
11 changes: 11 additions & 0 deletions packages/block-editor/src/components/inspector-controls/slot.js
Expand Up @@ -7,8 +7,11 @@ import warning from '@wordpress/warning';
/**
* Internal dependencies
*/
import BlockSupportToolsPanel from './block-support-tools-panel';
import groups from './groups';

const blockSupportGroups = [ 'border', 'color', 'dimensions', 'typography' ];

export default function InspectorControlsSlot( {
group = 'default',
bubblesVirtually = true,
Expand All @@ -26,5 +29,13 @@ export default function InspectorControlsSlot( {
return null;
}

if ( blockSupportGroups.includes( group ) ) {
return (
<BlockSupportToolsPanel group={ group }>
<Slot { ...props } bubblesVirtually={ bubblesVirtually } />
</BlockSupportToolsPanel>
);
}

return <Slot { ...props } bubblesVirtually={ bubblesVirtually } />;
}
90 changes: 42 additions & 48 deletions packages/block-editor/src/hooks/dimensions.js
@@ -1,10 +1,7 @@
/**
* WordPress dependencies
*/
import {
__experimentalToolsPanel as ToolsPanel,
__experimentalToolsPanelItem as ToolsPanelItem,
} from '@wordpress/components';
import { __experimentalToolsPanelItem as ToolsPanelItem } from '@wordpress/components';
import { Platform } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { getBlockSupport } from '@wordpress/blocks';
Expand All @@ -27,7 +24,6 @@ import {
resetPadding,
useIsPaddingDisabled,
} from './padding';
import { cleanEmptyObject } from './utils';

export const SPACING_SUPPORT_KEY = 'spacing';
export const ALL_SIDES = [ 'top', 'right', 'bottom', 'left' ];
Expand Down Expand Up @@ -55,50 +51,48 @@ export function DimensionsPanel( props ) {
'__experimentalDefaultControls',
] );

// Callback to reset all block support attributes controlled via this panel.
const resetAll = () => {
const { style } = props.attributes;

props.setAttributes( {
style: cleanEmptyObject( {
...style,
spacing: {
...style?.spacing,
margin: undefined,
padding: undefined,
},
} ),
} );
};

return (
<InspectorControls key="dimensions">
<ToolsPanel
label={ __( 'Dimensions options' ) }
header={ __( 'Dimensions' ) }
resetAll={ resetAll }
>
{ ! isPaddingDisabled && (
<ToolsPanelItem
hasValue={ () => hasPaddingValue( props ) }
label={ __( 'Padding' ) }
onDeselect={ () => resetPadding( props ) }
isShownByDefault={ defaultSpacingControls?.padding }
>
<PaddingEdit { ...props } />
</ToolsPanelItem>
) }
{ ! isMarginDisabled && (
<ToolsPanelItem
hasValue={ () => hasMarginValue( props ) }
label={ __( 'Margin' ) }
onDeselect={ () => resetMargin( props ) }
isShownByDefault={ defaultSpacingControls?.margin }
>
<MarginEdit { ...props } />
</ToolsPanelItem>
) }
</ToolsPanel>
<InspectorControls group="dimensions">
{ ! isPaddingDisabled && (
<ToolsPanelItem
hasValue={ () => hasPaddingValue( props ) }
label={ __( 'Padding' ) }
onDeselect={ () => resetPadding( props ) }
resetAllFilter={ ( newAttributes ) => ( {
...newAttributes,
style: {
...newAttributes.style,
spacing: {
...newAttributes.style?.spacing,
padding: undefined,
},
},
} ) }
isShownByDefault={ defaultSpacingControls?.padding }
>
<PaddingEdit { ...props } />
</ToolsPanelItem>
) }
{ ! isMarginDisabled && (
<ToolsPanelItem
hasValue={ () => hasMarginValue( props ) }
label={ __( 'Margin' ) }
onDeselect={ () => resetMargin( props ) }
resetAllFilter={ ( newAttributes ) => ( {
...newAttributes,
style: {
...newAttributes.style,
spacing: {
...newAttributes.style?.spacing,
margin: undefined,
},
},
} ) }
isShownByDefault={ defaultSpacingControls?.margin }
>
<MarginEdit { ...props } />
</ToolsPanelItem>
) }
</InspectorControls>
);
}
Expand Down
4 changes: 4 additions & 0 deletions packages/components/src/tools-panel/test/index.js
Expand Up @@ -316,6 +316,10 @@ describe( 'ToolsPanel', () => {
await selectMenuItem( 'Reset all' );

expect( resetAll ).toHaveBeenCalledTimes( 1 );
expect( controlProps.onSelect ).not.toHaveBeenCalled();
expect( controlProps.onDeselect ).not.toHaveBeenCalled();
expect( altControlProps.onSelect ).not.toHaveBeenCalled();
expect( altControlProps.onDeselect ).not.toHaveBeenCalled();
} );
} );

Expand Down
15 changes: 14 additions & 1 deletion packages/components/src/tools-panel/tools-panel-item/hook.js
Expand Up @@ -18,6 +18,7 @@ export function useToolsPanelItem( props ) {
hasValue,
isShownByDefault,
label,
resetAllFilter,
onDeselect = () => undefined,
onSelect = () => undefined,
...otherProps
Expand All @@ -28,7 +29,12 @@ export function useToolsPanelItem( props ) {
return cx( styles.ToolsPanelItem, className );
} );

const { menuItems, registerPanelItem } = useToolsPanelContext();
const {
menuItems,
registerPanelItem,
deregisterPanelItem,
isResetting,
} = useToolsPanelContext();

// Registering the panel item allows the panel to include it in its
// automatically generated menu and determine its initial checked status.
Expand All @@ -37,7 +43,10 @@ export function useToolsPanelItem( props ) {
hasValue,
isShownByDefault,
label,
resetAllFilter,
} );

return () => deregisterPanelItem( label );
}, [] );

const isValueSet = hasValue();
Expand All @@ -50,6 +59,10 @@ export function useToolsPanelItem( props ) {
// Determine if the panel item's corresponding menu is being toggled and
// trigger appropriate callback if it is.
useEffect( () => {
if ( isResetting ) {
return;
}

if ( isMenuItemChecked && ! isValueSet && ! wasMenuItemChecked ) {
onSelect();
}
Expand Down
40 changes: 37 additions & 3 deletions packages/components/src/tools-panel/tools-panel/hook.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { useEffect, useMemo, useState } from '@wordpress/element';
import { useEffect, useMemo, useRef, useState } from '@wordpress/element';

/**
* Internal dependencies
Expand All @@ -21,16 +21,44 @@ export function useToolsPanel( props ) {
return cx( styles.ToolsPanel, className );
}, [ className ] );

const isResetting = useRef( false );

useEffect( () => {
if ( isResetting.current ) {
isResetting.current = false;
}
} );

// Allow panel items to register themselves.
const [ panelItems, setPanelItems ] = useState( [] );

const registerPanelItem = ( item ) => {
setPanelItems( ( items ) => [ ...items, item ] );
};

// Panels need to deregister on unmount to avoid orphans in menu state.
// This is an issue when panel items are being injected via SlotFills.
const deregisterPanelItem = ( label ) => {
setPanelItems( ( items ) =>
items.filter( ( item ) => item.label !== label )
);
};

// Manage and share display state of menu items representing child controls.
const [ menuItems, setMenuItems ] = useState( {} );

const getResetAllFilters = () => {
const filters = [];

panelItems.forEach( ( item ) => {
if ( item.resetAllFilter ) {
filters.push( item.resetAllFilter );
}
} );

return filters;
};

// Setup menuItems state as panel items register themselves.
useEffect( () => {
const items = {};
Expand All @@ -54,7 +82,8 @@ export function useToolsPanel( props ) {
// Resets display of children and executes resetAll callback if available.
const resetAllItems = () => {
if ( typeof resetAll === 'function' ) {
resetAll();
isResetting.current = true;
resetAll( getResetAllFilters() );
}

// Turn off display of all non-default items.
Expand All @@ -67,7 +96,12 @@ export function useToolsPanel( props ) {
setMenuItems( resetMenuItems );
};

const panelContext = { menuItems, registerPanelItem };
const panelContext = {
menuItems,
registerPanelItem,
deregisterPanelItem,
isResetting: isResetting.current,
};

return {
...otherProps,
Expand Down

0 comments on commit b1b16a2

Please sign in to comment.