Skip to content

Commit

Permalink
Components: Ensure that SlotFill does not use portals in React Native (
Browse files Browse the repository at this point in the history
  • Loading branch information
gziolo authored Mar 10, 2021
1 parent 9571a7f commit 652c673
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 176 deletions.
162 changes: 3 additions & 159 deletions packages/components/src/slot-fill/context.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
/**
* External dependencies
*/
import { without } from 'lodash';

/**
* WordPress dependencies
*/
import {
Component,
createContext,
useContext,
useState,
useEffect,
} from '@wordpress/element';

/**
* Internal dependencies
*/
import SlotFillBubblesVirtuallyProvider from './bubbles-virtually/slot-fill-provider';
import { createContext } from '@wordpress/element';

const SlotFillContext = createContext( {
export const SlotFillContext = createContext( {
registerSlot: () => {},
unregisterSlot: () => {},
registerFill: () => {},
Expand All @@ -28,145 +12,5 @@ const SlotFillContext = createContext( {
getFills: () => {},
subscribe: () => {},
} );
const { Provider, Consumer } = SlotFillContext;

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.hasFills = this.hasFills.bind( this );
this.subscribe = this.subscribe.bind( this );

this.slots = {};
this.fills = {};
this.listeners = [];
this.contextValue = {
registerSlot: this.registerSlot,
unregisterSlot: this.unregisterSlot,
registerFill: this.registerFill,
unregisterFill: this.unregisterFill,
getSlot: this.getSlot,
getFills: this.getFills,
hasFills: this.hasFills,
subscribe: this.subscribe,
};
}

registerSlot( name, slot ) {
const previousSlot = this.slots[ name ];
this.slots[ name ] = slot;
this.triggerListeners();

// Sometimes the fills are registered after the initial render of slot
// But before the registerSlot call, we need to rerender the slot
this.forceUpdateSlot( name );

// If a new instance of a slot is being mounted while another with the
// same name exists, force its update _after_ the new slot has been
// assigned into the instance, such that its own rendering of children
// will be empty (the new Slot will subsume all fills for this name).
if ( previousSlot ) {
previousSlot.forceUpdate();
}
}

registerFill( name, instance ) {
this.fills[ name ] = [ ...( this.fills[ name ] || [] ), instance ];
this.forceUpdateSlot( name );
}

unregisterSlot( name, instance ) {
// If a previous instance of a Slot by this name unmounts, do nothing,
// as the slot and its fills should only be removed for the current
// known instance.
if ( this.slots[ name ] !== instance ) {
return;
}

delete this.slots[ name ];
this.triggerListeners();
}

unregisterFill( name, instance ) {
this.fills[ name ] = without( this.fills[ name ], instance );
this.forceUpdateSlot( name );
}

getSlot( name ) {
return this.slots[ name ];
}

getFills( name, slotInstance ) {
// Fills should only be returned for the current instance of the slot
// in which they occupy.
if ( this.slots[ name ] !== slotInstance ) {
return [];
}
return this.fills[ name ];
}

hasFills( name ) {
return this.fills[ name ] && !! this.fills[ name ].length;
}

forceUpdateSlot( name ) {
const slot = this.getSlot( name );

if ( slot ) {
slot.forceUpdate();
}
}

triggerListeners() {
this.listeners.forEach( ( listener ) => listener() );
}

subscribe( listener ) {
this.listeners.push( listener );

return () => {
this.listeners = without( this.listeners, listener );
};
}

render() {
return (
<Provider value={ this.contextValue }>
<SlotFillBubblesVirtuallyProvider>
{ this.props.children }
</SlotFillBubblesVirtuallyProvider>
</Provider>
);
}
}

/**
* React hook returning the active slot given a name.
*
* @param {string} name Slot name.
* @return {Object} Slot object.
*/
export const useSlot = ( name ) => {
const { getSlot, subscribe } = useContext( SlotFillContext );
const [ slot, setSlot ] = useState( getSlot( name ) );

useEffect( () => {
setSlot( getSlot( name ) );
const unsubscribe = subscribe( () => {
setSlot( getSlot( name ) );
} );

return unsubscribe;
}, [ name ] );

return slot;
};

export default SlotFillProvider;
export { Consumer };
export default SlotFillContext;
7 changes: 4 additions & 3 deletions packages/components/src/slot-fill/fill.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { createPortal, useLayoutEffect, useRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import { Consumer, useSlot } from './context';
import SlotFillContext from './context';
import useSlot from './use-slot';

function FillComponent( { name, children, registerFill, unregisterFill } ) {
const slot = useSlot( name );
Expand Down Expand Up @@ -56,15 +57,15 @@ function FillComponent( { name, children, registerFill, unregisterFill } ) {
}

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

export default Fill;
33 changes: 22 additions & 11 deletions packages/components/src/slot-fill/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
/**
* Internal dependencies
*/
import BaseSlot from './slot';
import BaseFill from './fill';
import Provider from './context';
import BubblesVirtuallySlot from './bubbles-virtually/slot';
import BaseSlot from './slot';
import BubblesVirtuallyFill from './bubbles-virtually/fill';
import BubblesVirtuallySlot from './bubbles-virtually/slot';
import BubblesVirtuallySlotFillProvider from './bubbles-virtually/slot-fill-provider';
import SlotFillProvider from './provider';
import useSlot from './bubbles-virtually/use-slot';

export function Slot( { bubblesVirtually, ...props } ) {
if ( bubblesVirtually ) {
return <BubblesVirtuallySlot { ...props } />;
}
return <BaseSlot { ...props } />;
}

export function Fill( props ) {
// We're adding both Fills here so they can register themselves before
// their respective slot has been registered. Only the Fill that has a slot
Expand All @@ -27,6 +21,23 @@ export function Fill( props ) {
);
}

export function Slot( { bubblesVirtually, ...props } ) {
if ( bubblesVirtually ) {
return <BubblesVirtuallySlot { ...props } />;
}
return <BaseSlot { ...props } />;
}

export function Provider( { children, ...props } ) {
return (
<SlotFillProvider { ...props }>
<BubblesVirtuallySlotFillProvider>
{ children }
</BubblesVirtuallySlotFillProvider>
</SlotFillProvider>
);
}

export function createSlotFill( name ) {
const FillComponent = ( props ) => <Fill name={ name } { ...props } />;
FillComponent.displayName = name + 'Fill';
Expand All @@ -40,4 +51,4 @@ export function createSlotFill( name ) {
};
}

export { useSlot, Provider };
export { useSlot };
30 changes: 30 additions & 0 deletions packages/components/src/slot-fill/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* External dependencies
*/
import { omit } from 'lodash';

/**
* Internal dependencies
*/
import BaseSlot from './slot';
import Fill from './fill';
import Provider from './provider';

export { Fill, Provider };

export function Slot( props ) {
return <BaseSlot { ...omit( props, 'bubblesVirtually' ) } />;
}

export function createSlotFill( name ) {
const FillComponent = ( props ) => <Fill name={ name } { ...props } />;
FillComponent.displayName = name + 'Fill';

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

return {
Fill: FillComponent,
Slot: SlotComponent,
};
}
Loading

0 comments on commit 652c673

Please sign in to comment.