Skip to content
Next Next commit
Handle focus control
  • Loading branch information
kevin940726 committed May 4, 2021
commit 0b017cf408516c3845b8b3c2fec87be7109c3f0e
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* WordPress dependencies
*/
import { useState, useEffect, createPortal } from '@wordpress/element';

/**
* Internal dependencies
*/
import SidebarBlockEditor from '../sidebar-block-editor';
import FocusControl from '../focus-control';

export default function CustomizeWidgets( {
api,
sidebarControls,
blockEditorSettings,
} ) {
const [ activeSidebar, setActiveSidebar ] = useState( null );

useEffect( () => {
const unsubscribers = sidebarControls.map( ( sidebarControl ) =>
sidebarControl.subscribe( ( expanded ) => {
if ( expanded ) {
setActiveSidebar( sidebarControl );
}
} )
);

return () => {
unsubscribers.forEach( ( unsubscriber ) => unsubscriber() );
};
}, [ sidebarControls ] );

const sidebar =
activeSidebar &&
createPortal(
<SidebarBlockEditor
blockEditorSettings={ blockEditorSettings }
sidebar={ activeSidebar.sidebarAdapter }
inserter={ activeSidebar.inserter }
inspector={ activeSidebar.inspector }
/>,
activeSidebar.container[ 0 ]
);

return (
<FocusControl api={ api } sidebarControls={ sidebarControls }>
{ sidebar }
</FocusControl>
);
}
55 changes: 55 additions & 0 deletions packages/customize-widgets/src/components/focus-control/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* WordPress dependencies
*/
import { createContext, useState, useEffect } from '@wordpress/element';

/**
* Internal dependencies
*/
import { settingIdToWidgetId } from '../../utils';

export const FocusControlContext = createContext( null );

export default function FocusControl( { api, sidebarControls, children } ) {
const [ focusedWidgetId, setFocusedWidgetId ] = useState( null );

useEffect( () => {
function handleFocus( settingId ) {
const widgetId = settingIdToWidgetId( settingId );

for ( const sidebarControl of sidebarControls ) {
const widgets = sidebarControl.setting.get();

if ( widgets.includes( widgetId ) ) {
sidebarControl.sectionInstance.expand();

setFocusedWidgetId( widgetId );
break;
}
}
}

function handleReady() {
api.previewer.preview.bind(
'focus-control-for-setting',
handleFocus
);
}

api.previewer.bind( 'ready', handleReady );

return () => {
api.previewer.unbind( 'ready', handleReady );
api.previewer.preview.unbind(
'focus-control-for-setting',
handleFocus
);
};
}, [ api, sidebarControls ] );

return (
<FocusControlContext.Provider value={ focusedWidgetId }>
{ children }
</FocusControlContext.Provider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* WordPress dependencies
*/
import { useRef, useEffect, useContext } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import { FocusControlContext } from './';
import { getWidgetIdFromBlock } from '../../utils';

export default function useFocusControl( blocks ) {
const { selectBlock } = useDispatch( blockEditorStore );
const focusedWidgetId = useContext( FocusControlContext );

const blocksRef = useRef( blocks );

useEffect( () => {
blocksRef.current = blocks;
}, [ blocks ] );

useEffect( () => {
if ( focusedWidgetId ) {
const focusedBlock = blocksRef.current.find(
( block ) => getWidgetIdFromBlock( block ) === focusedWidgetId
);

if ( focusedBlock ) {
selectBlock( focusedBlock.clientId );
}
}
}, [ focusedWidgetId, blocksRef, selectBlock ] );
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* Internal dependencies
*/
import { settingIdToWidgetId } from '../../utils';

const { wp } = window;

function parseWidgetId( widgetId ) {
Expand All @@ -22,31 +27,10 @@ function widgetIdToSettingId( widgetId ) {
return `widget_${ idBase }`;
}

function parseSettingId( settingId ) {
const matches = settingId.match( /^widget_(.+)(?:\[(\d+)\])$/ );
if ( matches ) {
return {
idBase: matches[ 1 ],
number: parseInt( matches[ 2 ], 10 ),
};
}

return { idBase: settingId };
}

function settingIdToWidgetId( settingId ) {
const { idBase, number } = parseSettingId( settingId );
if ( number ) {
return `${ idBase }-${ number }`;
}

return idBase;
}

export default class SidebarAdapter {
constructor( setting, allSettings ) {
constructor( setting, api ) {
this.setting = setting;
this.allSettings = allSettings;
this.api = api;

this.locked = false;
this.widgetsCache = new WeakMap();
Expand All @@ -72,19 +56,19 @@ export default class SidebarAdapter {
subscribe( callback ) {
if ( ! this.subscribers.size ) {
this.setting.bind( this._handleSettingChange );
this.allSettings.bind( 'change', this._handleAllSettingsChange );
this.api.bind( 'change', this._handleAllSettingsChange );
}

this.subscribers.add( callback );
}

unsubscribe( callback ) {
this.subscribers.delete( callback );
return () => {
this.subscribers.delete( callback );

if ( ! this.subscribers.size ) {
this.setting.unbind( this._handleSettingChange );
this.allSettings.unbind( 'change', this._handleAllSettingsChange );
}
if ( ! this.subscribers.size ) {
this.setting.bind( this._handleSettingChange );
this.api.bind( 'change', this._handleAllSettingsChange );
}
};
}

getWidgets() {
Expand Down Expand Up @@ -170,7 +154,7 @@ export default class SidebarAdapter {
: 'refresh',
previewer: this.setting.previewer,
};
const setting = this.allSettings.create(
const setting = this.api.create(
settingId,
settingId,
'',
Expand Down Expand Up @@ -203,7 +187,7 @@ export default class SidebarAdapter {
prevWidget.idBase === widget.idBase
) {
const settingId = widgetIdToSettingId( widget.id );
this.allSettings( settingId ).set( widget.instance );
this.api( settingId ).set( widget.instance );
return widget.id;
}

Expand All @@ -219,7 +203,7 @@ export default class SidebarAdapter {

const { idBase, number } = parseWidgetId( widgetId );
const settingId = widgetIdToSettingId( widgetId );
const setting = this.allSettings( settingId );
const setting = this.api( settingId );

if ( ! setting ) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BlockEditorProvider } from '@wordpress/block-editor';
* Internal dependencies
*/
import useSidebarBlockEditor from './use-sidebar-block-editor';
import useFocusControl from '../focus-control/use-focus-control';

export default function SidebarEditorProvider( {
sidebar,
Expand All @@ -15,6 +16,8 @@ export default function SidebarEditorProvider( {
} ) {
const [ blocks, onInput, onChange ] = useSidebarBlockEditor( sidebar );

useFocusControl( blocks );

return (
<BlockEditorProvider
value={ blocks }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import { serialize, parse, createBlock } from '@wordpress/blocks';
import { useState, useEffect, useCallback } from '@wordpress/element';
import isShallowEqual from '@wordpress/is-shallow-equal';

/**
* Internal dependencies
*/
import { getWidgetIdFromBlock } from '../../utils';

function addWidgetIdToBlock( block, widgetId ) {
return {
...block,
Expand All @@ -20,10 +25,6 @@ function addWidgetIdToBlock( block, widgetId ) {
};
}

function getWidgetId( block ) {
return block.attributes.__internalWidgetId;
}

function blockToWidget( block, existingWidget = null ) {
let widget;

Expand Down Expand Up @@ -122,7 +123,7 @@ export default function useSidebarBlockEditor( sidebar ) {
);
const prevBlocksMap = new Map(
prevBlocks.map( ( block ) => [
getWidgetId( block ),
getWidgetIdFromBlock( block ),
block,
] )
);
Expand Down Expand Up @@ -157,13 +158,13 @@ export default function useSidebarBlockEditor( sidebar ) {

const prevBlocksMap = new Map(
prevBlocks.map( ( block ) => [
getWidgetId( block ),
getWidgetIdFromBlock( block ),
block,
] )
);

const nextWidgets = nextBlocks.map( ( nextBlock ) => {
const widgetId = getWidgetId( nextBlock );
const widgetId = getWidgetIdFromBlock( nextBlock );

// Update existing widgets.
if ( widgetId && prevBlocksMap.has( widgetId ) ) {
Expand Down
45 changes: 19 additions & 26 deletions packages/customize-widgets/src/controls/sidebar-control.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
/**
* WordPress dependencies
*/
import { render, unmountComponentAtNode } from '@wordpress/element';

/**
* Internal dependencies
*/
import SidebarBlockEditor from '../components/sidebar-block-editor';
import SidebarAdapter from '../components/sidebar-block-editor/sidebar-adapter';
import getInserterOuterSection from './inserter-outer-section';

const getInserterId = ( controlId ) => `widgets-inserter-${ controlId }`;

export default function getSidebarControl( blockEditorSettings ) {
export default function getSidebarControl() {
const {
wp: { customize },
} = window;

return class SidebarControl extends customize.Control {
constructor( ...args ) {
super( ...args );

this.subscribers = new Set();
}

ready() {
const InserterOuterSection = getInserterOuterSection();
this.inserter = new InserterOuterSection(
Expand All @@ -30,34 +30,27 @@ export default function getSidebarControl( blockEditorSettings ) {

this.inspector = this.sectionInstance.inspector;

this.render();
this.sidebarAdapter = new SidebarAdapter( this.setting, customize );
}

subscribe( callback ) {
this.subscribers.add( callback );

return () => {
this.subscribers.delete( callback );
};
}

onChangeSectionExpanded( expanded, args ) {
if ( ! args.unchanged ) {
// Close the inserter when the section collapses.
if ( ! expanded ) {
this.inserter.close();
}

this.render();
}
}
render() {
if ( this.sectionInstance.expanded() ) {
render(
<SidebarBlockEditor
blockEditorSettings={ blockEditorSettings }
sidebar={
new SidebarAdapter( this.setting, customize )
}
inserter={ this.inserter }
inspector={ this.inspector }
/>,
this.container[ 0 ]
this.subscribers.forEach( ( subscriber ) =>
subscriber( expanded, args )
);
} else if ( ! this.sectionInstance.hasSubSectionOpened() ) {
// Don't unmount the node when the sub section (inspector) is opened.
unmountComponentAtNode( this.container[ 0 ] );
}
}
};
Expand Down
Loading