Skip to content

Commit

Permalink
Components: Add slots proxying support to SlotFillProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Jun 4, 2019
1 parent 210dc42 commit 71c3ec2
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 48 deletions.
80 changes: 64 additions & 16 deletions packages/components/src/slot-fill/context.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/**
* External dependencies
*/
import { sortBy, forEach, without } from 'lodash';
import { sortBy, forEach, without, some } from 'lodash';

/**
* WordPress dependencies
*/
import { Component, createContext } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';

const { Provider, Consumer } = createContext( {
registerSlot: () => {},
Expand All @@ -21,22 +22,49 @@ class SlotFillProvider extends Component {
constructor() {
super( ...arguments );

this.registerSlot = this.registerSlot.bind( this );
this.registerFill = this.registerFill.bind( this );
this.unregisterSlot = this.unregisterSlot.bind( this );
this.unregisterFill = this.unregisterFill.bind( this );
this.getSlot = this.getSlot.bind( this );
this.getFills = this.getFills.bind( this );

this.slots = {};
this.fills = {};
this.state = {
registerSlot: this.registerSlot,
unregisterSlot: this.unregisterSlot,
registerFill: this.registerFill,
unregisterFill: this.unregisterFill,
getSlot: this.getSlot,
getFills: this.getFills,
registerSlot: this.proxy( 'registerSlot' ).bind( this ),
registerFill: this.proxy( 'registerFill' ).bind( this ),
unregisterSlot: this.proxy( 'unregisterSlot' ).bind( this ),
unregisterFill: this.proxy( 'unregisterFill' ).bind( this ),
getSlot: this.proxy( 'getSlot' ).bind( this ),
getFills: this.proxy( 'getFills' ).bind( this ),
};
}

/**
* Given a function name for a function on the SlotFillProvider prototype,
* returns a new function which either passes the arguments to the current
* instance, or to the context ancestor, dependent on whether the provider
* is configured to handle the given slot.
*
* @param {string} functionName SlotFillProvider function name.
*
* @return {Function} Proxying function.
*/
proxy( functionName ) {
return ( name, ...args ) => {
const { slots: handledSlots } = this.props;

let handler = this[ functionName ];

if ( Array.isArray( handledSlots ) ) {
const isHandled = some( handledSlots, ( slotNameOrComponent ) => {
const { slotName = slotNameOrComponent } = slotNameOrComponent;
return typeof slotName === 'string' && (
slotName === name ||
name.startsWith( slotName + '.' )
);
} );

if ( ! isHandled ) {
handler = this.props[ functionName ];
}
}

return handler.call( this, name, ...args );
};
}

Expand Down Expand Up @@ -129,5 +157,25 @@ class SlotFillProvider extends Component {
}
}

export default SlotFillProvider;
export { Consumer };
const withConsumerContext = createHigherOrderComponent(
( WrappedComponent ) => ( props ) => (
<Consumer>
{ ( context ) => (
<WrappedComponent
{ ...props }
registerSlot={ context.registerSlot }
unregisterSlot={ context.unregisterSlot }
registerFill={ context.registerFill }
unregisterFill={ context.unregisterFill }
getSlot={ context.getSlot }
getFills={ context.getFills }
/>
) }
</Consumer>
),
'withConsumerContext'
);

export default withConsumerContext( SlotFillProvider );

export { Consumer, withConsumerContext };
19 changes: 3 additions & 16 deletions packages/components/src/slot-fill/fill.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import { createPortal, useLayoutEffect, useRef, useState } from '@wordpress/elem
/**
* Internal dependencies
*/
import { Consumer } from './context';
import { withConsumerContext } from './context';

let occurrences = 0;

function FillComponent( { name, getSlot, children, registerFill, unregisterFill } ) {
function Fill( { name, getSlot, children, registerFill, unregisterFill } ) {
// Random state used to rerender the component if needed, ideally we don't need this
const [ , updateRerenderState ] = useState( {} );
const rerender = () => updateRerenderState( {} );
Expand Down Expand Up @@ -67,17 +67,4 @@ function FillComponent( { name, getSlot, children, registerFill, unregisterFill
return createPortal( children, slot.node );
}

const Fill = ( props ) => (
<Consumer>
{ ( { getSlot, registerFill, unregisterFill } ) => (
<FillComponent
{ ...props }
getSlot={ getSlot }
registerFill={ registerFill }
unregisterFill={ unregisterFill }
/>
) }
</Consumer>
);

export default Fill;
export default withConsumerContext( Fill );
1 change: 1 addition & 0 deletions packages/components/src/slot-fill/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function createSlotFill( name ) {

const SlotComponent = ( props ) => <Slot name={ name } { ...props } />;
SlotComponent.displayName = name + 'Slot';
SlotComponent.slotName = name;

return {
Fill: FillComponent,
Expand Down
19 changes: 3 additions & 16 deletions packages/components/src/slot-fill/slot.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import {
/**
* Internal dependencies
*/
import { Consumer } from './context';
import { withConsumerContext } from './context';

class SlotComponent extends Component {
class Slot extends Component {
constructor() {
super( ...arguments );

Expand Down Expand Up @@ -89,17 +89,4 @@ class SlotComponent extends Component {
}
}

const Slot = ( props ) => (
<Consumer>
{ ( { registerSlot, unregisterSlot, getFills } ) => (
<SlotComponent
{ ...props }
registerSlot={ registerSlot }
unregisterSlot={ unregisterSlot }
getFills={ getFills }
/>
) }
</Consumer>
);

export default Slot;
export default withConsumerContext( Slot );

0 comments on commit 71c3ec2

Please sign in to comment.