diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js
index bd97fec4ba0073..d64e72f6a50e63 100644
--- a/packages/block-editor/src/components/link-control/test/index.js
+++ b/packages/block-editor/src/components/link-control/test/index.js
@@ -3,7 +3,7 @@
*/
import {
fireEvent,
- render,
+ render as baseRender,
screen,
waitFor,
within,
@@ -13,6 +13,7 @@ import userEvent from '@testing-library/user-event';
/**
* WordPress dependencies
*/
+import { SlotFillProvider } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
@@ -67,6 +68,10 @@ afterEach( () => {
mockFetchRichUrlData?.mockReset(); // Conditionally reset as it may NOT be a mock.
} );
+function render( ui, options ) {
+ return baseRender( ui, { wrapper: SlotFillProvider, ...options } );
+}
+
/**
* Workaround to trigger an arrow up keypress event.
*
diff --git a/packages/block-editor/src/components/media-replace-flow/test/index.js b/packages/block-editor/src/components/media-replace-flow/test/index.js
index dace470aa67b0f..d802efa4795fdb 100644
--- a/packages/block-editor/src/components/media-replace-flow/test/index.js
+++ b/packages/block-editor/src/components/media-replace-flow/test/index.js
@@ -8,7 +8,7 @@ import userEvent from '@testing-library/user-event';
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
-
+import { SlotFillProvider } from '@wordpress/components';
/**
* Internal dependencies
*/
@@ -19,16 +19,18 @@ const noop = () => {};
function TestWrapper() {
const [ mediaURL, setMediaURL ] = useState( 'https://example.media' );
return (
-
+
+
+
);
}
diff --git a/packages/block-library/src/cover/test/edit.js b/packages/block-library/src/cover/test/edit.js
index f5d6a5301ef6d2..e0f2a9cd44794b 100644
--- a/packages/block-library/src/cover/test/edit.js
+++ b/packages/block-library/src/cover/test/edit.js
@@ -200,7 +200,7 @@ describe( 'Cover block', () => {
await selectBlock( 'Block: Cover' );
expect(
- screen.getByRole( 'heading', {
+ await screen.findByRole( 'heading', {
name: 'Settings',
} )
).toBeInTheDocument();
@@ -216,7 +216,7 @@ describe( 'Cover block', () => {
);
await selectBlock( 'Block: Cover' );
await userEvent.click(
- screen.getByLabelText( 'Fixed background' )
+ await screen.findByLabelText( 'Fixed background' )
);
expect( screen.getByLabelText( 'Block: Cover' ) ).toHaveClass(
'has-parallax'
@@ -232,7 +232,7 @@ describe( 'Cover block', () => {
);
await selectBlock( 'Block: Cover' );
await userEvent.click(
- screen.getByLabelText( 'Repeated background' )
+ await screen.findByLabelText( 'Repeated background' )
);
expect( screen.getByLabelText( 'Block: Cover' ) ).toHaveClass(
'is-repeated'
@@ -245,7 +245,7 @@ describe( 'Cover block', () => {
} );
await selectBlock( 'Block: Cover' );
- await userEvent.clear( screen.getByLabelText( 'Left' ) );
+ await userEvent.clear( await screen.findByLabelText( 'Left' ) );
await userEvent.type( screen.getByLabelText( 'Left' ), '100' );
expect(
@@ -262,7 +262,7 @@ describe( 'Cover block', () => {
await selectBlock( 'Block: Cover' );
await userEvent.type(
- screen.getByLabelText( 'Alternative text' ),
+ await screen.findByLabelText( 'Alternative text' ),
'Me'
);
expect( screen.getByAltText( 'Me' ) ).toBeInTheDocument();
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 7b5ec64bd44ca5..be58d9a9129963 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -24,6 +24,7 @@
### Internal
- `SlotFill`: rewrite the non-portal version to use `observableMap` ([#67400](https://github.com/WordPress/gutenberg/pull/67400)).
+- `SlotFill`: unify context providers and `Fill` implementations ([#68056](https://github.com/WordPress/gutenberg/pull/68056)).
## 29.0.0 (2024-12-11)
diff --git a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx
deleted file mode 100644
index ef7bc94ff540bd..00000000000000
--- a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { useObservableValue } from '@wordpress/compose';
-import {
- useContext,
- useRef,
- useEffect,
- createPortal,
-} from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import SlotFillContext from './slot-fill-context';
-import StyleProvider from '../../style-provider';
-import type { FillComponentProps } from '../types';
-
-export default function Fill( { name, children }: FillComponentProps ) {
- const registry = useContext( SlotFillContext );
- const slot = useObservableValue( registry.slots, name );
- const instanceRef = useRef( {} );
-
- // We register fills so we can keep track of their existence.
- // Slots can use the `useSlotFills` hook to know if there're already fills
- // registered so they can choose to render themselves or not.
- useEffect( () => {
- const instance = instanceRef.current;
- registry.registerFill( name, instance );
- return () => registry.unregisterFill( name, instance );
- }, [ registry, name ] );
-
- if ( ! slot || ! slot.ref.current ) {
- return null;
- }
-
- // When using a `Fill`, the `children` will be rendered in the document of the
- // `Slot`. This means that we need to wrap the `children` in a `StyleProvider`
- // to make sure we're referencing the right document/iframe (instead of the
- // context of the `Fill`'s parent).
- const wrappedChildren = (
-
- { typeof children === 'function'
- ? children( slot.fillProps ?? {} )
- : children }
-
- );
-
- return createPortal( wrappedChildren, slot.ref.current );
-}
diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-context.ts b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-context.ts
deleted file mode 100644
index a144a7dc33f464..00000000000000
--- a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-context.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { createContext } from '@wordpress/element';
-import warning from '@wordpress/warning';
-import { observableMap } from '@wordpress/compose';
-
-/**
- * Internal dependencies
- */
-import type { SlotFillBubblesVirtuallyContext } from '../types';
-
-const initialContextValue: SlotFillBubblesVirtuallyContext = {
- slots: observableMap(),
- fills: observableMap(),
- registerSlot: () => {
- warning(
- 'Components must be wrapped within `SlotFillProvider`. ' +
- 'See https://developer.wordpress.org/block-editor/components/slot-fill/'
- );
- },
- updateSlot: () => {},
- unregisterSlot: () => {},
- registerFill: () => {},
- unregisterFill: () => {},
-
- // This helps the provider know if it's using the default context value or not.
- isDefault: true,
-};
-
-const SlotFillContext = createContext( initialContextValue );
-
-export default SlotFillContext;
diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx
deleted file mode 100644
index cf692700eef79c..00000000000000
--- a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { useState } from '@wordpress/element';
-import isShallowEqual from '@wordpress/is-shallow-equal';
-import { observableMap } from '@wordpress/compose';
-
-/**
- * Internal dependencies
- */
-import SlotFillContext from './slot-fill-context';
-import type {
- SlotFillProviderProps,
- SlotFillBubblesVirtuallyContext,
-} from '../types';
-
-function createSlotRegistry(): SlotFillBubblesVirtuallyContext {
- const slots: SlotFillBubblesVirtuallyContext[ 'slots' ] = observableMap();
- const fills: SlotFillBubblesVirtuallyContext[ 'fills' ] = observableMap();
-
- const registerSlot: SlotFillBubblesVirtuallyContext[ 'registerSlot' ] = (
- name,
- ref,
- fillProps
- ) => {
- slots.set( name, { ref, fillProps } );
- };
-
- const unregisterSlot: SlotFillBubblesVirtuallyContext[ 'unregisterSlot' ] =
- ( name, ref ) => {
- const slot = slots.get( name );
- if ( ! slot ) {
- return;
- }
-
- // Make sure we're not unregistering a slot registered by another element
- // See https://github.com/WordPress/gutenberg/pull/19242#issuecomment-590295412
- if ( slot.ref !== ref ) {
- return;
- }
-
- slots.delete( name );
- };
-
- const updateSlot: SlotFillBubblesVirtuallyContext[ 'updateSlot' ] = (
- name,
- ref,
- fillProps
- ) => {
- const slot = slots.get( name );
- if ( ! slot ) {
- return;
- }
-
- if ( slot.ref !== ref ) {
- return;
- }
-
- if ( isShallowEqual( slot.fillProps, fillProps ) ) {
- return;
- }
-
- slots.set( name, { ref, fillProps } );
- };
-
- const registerFill: SlotFillBubblesVirtuallyContext[ 'registerFill' ] = (
- name,
- ref
- ) => {
- fills.set( name, [ ...( fills.get( name ) || [] ), ref ] );
- };
-
- const unregisterFill: SlotFillBubblesVirtuallyContext[ 'unregisterFill' ] =
- ( name, ref ) => {
- const fillsForName = fills.get( name );
- if ( ! fillsForName ) {
- return;
- }
-
- fills.set(
- name,
- fillsForName.filter( ( fillRef ) => fillRef !== ref )
- );
- };
-
- return {
- slots,
- fills,
- registerSlot,
- updateSlot,
- unregisterSlot,
- registerFill,
- unregisterFill,
- };
-}
-
-export default function SlotFillProvider( {
- children,
-}: SlotFillProviderProps ) {
- const [ registry ] = useState( createSlotRegistry );
- return (
-
- { children }
-
- );
-}
diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot.tsx
index e65c055c410a6b..430f98a795c9e1 100644
--- a/packages/components/src/slot-fill/bubbles-virtually/slot.tsx
+++ b/packages/components/src/slot-fill/bubbles-virtually/slot.tsx
@@ -18,7 +18,7 @@ import { useMergeRefs } from '@wordpress/compose';
* Internal dependencies
*/
import { View } from '../../view';
-import SlotFillContext from './slot-fill-context';
+import SlotFillContext from '../context';
import type { WordPressComponentProps } from '../../context';
import type { SlotComponentProps } from '../types';
@@ -40,25 +40,32 @@ function Slot(
} = props;
const registry = useContext( SlotFillContext );
-
+ const instanceRef = useRef( {} );
const ref = useRef< HTMLElement >( null );
- // We don't want to unregister and register the slot whenever
- // `fillProps` change, which would cause the fill to be re-mounted. Instead,
- // we can just update the slot (see hook below).
- // For more context, see https://github.com/WordPress/gutenberg/pull/44403#discussion_r994415973
const fillPropsRef = useRef( fillProps );
useLayoutEffect( () => {
fillPropsRef.current = fillProps;
}, [ fillProps ] );
useLayoutEffect( () => {
- registry.registerSlot( name, ref, fillPropsRef.current );
- return () => registry.unregisterSlot( name, ref );
+ const instance = instanceRef.current;
+ registry.registerSlot( name, {
+ type: 'portal',
+ instance,
+ ref,
+ fillProps: fillPropsRef.current,
+ } );
+ return () => registry.unregisterSlot( name, instance );
}, [ registry, name ] );
useLayoutEffect( () => {
- registry.updateSlot( name, ref, fillPropsRef.current );
+ registry.updateSlot( name, {
+ type: 'portal',
+ instance: instanceRef.current,
+ ref,
+ fillProps: fillPropsRef.current,
+ } );
} );
return (
diff --git a/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts b/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts
index 6229d20f2da513..c597d68a567835 100644
--- a/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts
+++ b/packages/components/src/slot-fill/bubbles-virtually/use-slot-fills.ts
@@ -7,7 +7,7 @@ import { useObservableValue } from '@wordpress/compose';
/**
* Internal dependencies
*/
-import SlotFillContext from './slot-fill-context';
+import SlotFillContext from '../context';
import type { SlotKey } from '../types';
export default function useSlotFills( name: SlotKey ) {
diff --git a/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts b/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts
index cac57a024e4ee2..68114f5e09a167 100644
--- a/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts
+++ b/packages/components/src/slot-fill/bubbles-virtually/use-slot.ts
@@ -7,11 +7,15 @@ import { useObservableValue } from '@wordpress/compose';
/**
* Internal dependencies
*/
-import SlotFillContext from './slot-fill-context';
-import type { SlotKey } from '../types';
+import SlotFillContext from '../context';
+import type { SlotKey, SlotRef } from '../types';
export default function useSlot( name: SlotKey ) {
const registry = useContext( SlotFillContext );
const slot = useObservableValue( registry.slots, name );
- return { ...slot };
+ let ref: SlotRef | undefined;
+ if ( slot && slot.type === 'portal' ) {
+ ref = slot.ref;
+ }
+ return { ref };
}
diff --git a/packages/components/src/slot-fill/context.ts b/packages/components/src/slot-fill/context.ts
index b1f0718180e9eb..3f51b2f1ec17d4 100644
--- a/packages/components/src/slot-fill/context.ts
+++ b/packages/components/src/slot-fill/context.ts
@@ -3,21 +3,30 @@
*/
import { observableMap } from '@wordpress/compose';
import { createContext } from '@wordpress/element';
+import warning from '@wordpress/warning';
/**
* Internal dependencies
*/
-import type { BaseSlotFillContext } from './types';
+import type { SlotFillRegistry } from './types';
-const initialValue: BaseSlotFillContext = {
+const initialValue: SlotFillRegistry = {
slots: observableMap(),
fills: observableMap(),
- registerSlot: () => {},
+ registerSlot: () => {
+ warning(
+ 'Components must be wrapped within `SlotFillProvider`. ' +
+ 'See https://developer.wordpress.org/block-editor/components/slot-fill/'
+ );
+ },
unregisterSlot: () => {},
+ updateSlot: () => {},
registerFill: () => {},
unregisterFill: () => {},
updateFill: () => {},
+
+ // This helps the provider know if it's using the default context value or not.
+ isDefault: true,
};
-export const SlotFillContext = createContext( initialValue );
-export default SlotFillContext;
+export default createContext( initialValue );
diff --git a/packages/components/src/slot-fill/fill.ts b/packages/components/src/slot-fill/fill.ts
deleted file mode 100644
index 0bd1aec8fa3e0e..00000000000000
--- a/packages/components/src/slot-fill/fill.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { useContext, useLayoutEffect, useRef } from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import SlotFillContext from './context';
-import type { FillComponentProps } from './types';
-
-export default function Fill( { name, children }: FillComponentProps ) {
- const registry = useContext( SlotFillContext );
- const instanceRef = useRef( {} );
- const childrenRef = useRef( children );
-
- useLayoutEffect( () => {
- childrenRef.current = children;
- }, [ children ] );
-
- useLayoutEffect( () => {
- const instance = instanceRef.current;
- registry.registerFill( name, instance, childrenRef.current );
- return () => registry.unregisterFill( name, instance );
- }, [ registry, name ] );
-
- useLayoutEffect( () => {
- registry.updateFill( name, instanceRef.current, childrenRef.current );
- } );
-
- return null;
-}
diff --git a/packages/components/src/slot-fill/fill.tsx b/packages/components/src/slot-fill/fill.tsx
new file mode 100644
index 00000000000000..6de62c031deec2
--- /dev/null
+++ b/packages/components/src/slot-fill/fill.tsx
@@ -0,0 +1,74 @@
+/**
+ * WordPress dependencies
+ */
+import { useObservableValue } from '@wordpress/compose';
+import {
+ useContext,
+ useLayoutEffect,
+ useRef,
+ createPortal,
+} from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import SlotFillContext from './context';
+import type { FillComponentProps } from './types';
+import StyleProvider from '../style-provider';
+
+export default function Fill( { name, children }: FillComponentProps ) {
+ const registry = useContext( SlotFillContext );
+ const instanceRef = useRef( {} );
+ const childrenRef = useRef( children );
+
+ useLayoutEffect( () => {
+ childrenRef.current = children;
+ }, [ children ] );
+
+ useLayoutEffect( () => {
+ const instance = instanceRef.current;
+ registry.registerFill( name, {
+ instance,
+ children: childrenRef.current,
+ } );
+ return () => registry.unregisterFill( name, instance );
+ }, [ registry, name ] );
+
+ useLayoutEffect( () => {
+ registry.updateFill( name, {
+ instance: instanceRef.current,
+ children: childrenRef.current,
+ } );
+ } );
+
+ const slot = useObservableValue( registry.slots, name );
+
+ if ( ! slot ) {
+ return null;
+ }
+
+ if ( slot.type === 'children' ) {
+ return null;
+ }
+
+ const portalEl = slot.ref.current;
+ if ( ! portalEl ) {
+ return null;
+ }
+
+ const wrappedChildren =
+ typeof children === 'function'
+ ? children( slot.fillProps ?? {} )
+ : children;
+
+ // When using a `Fill`, the `children` will be rendered in the document of the
+ // `Slot`. This means that we need to wrap the `children` in a `StyleProvider`
+ // to make sure we're referencing the right document/iframe (instead of the
+ // context of the `Fill`'s parent).
+ return createPortal(
+
+ { wrappedChildren }
+ ,
+ portalEl
+ );
+}
diff --git a/packages/components/src/slot-fill/index.tsx b/packages/components/src/slot-fill/index.tsx
index caf97091b67ac8..23e8fa9807457c 100644
--- a/packages/components/src/slot-fill/index.tsx
+++ b/packages/components/src/slot-fill/index.tsx
@@ -11,13 +11,11 @@ import { forwardRef, useContext } from '@wordpress/element';
/**
* Internal dependencies
*/
-import BaseFill from './fill';
+import Fill from './fill';
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 SlotFillContext from './bubbles-virtually/slot-fill-context';
+import SlotFillContext from './context';
import type { WordPressComponentProps } from '../context';
export { default as useSlot } from './bubbles-virtually/use-slot';
@@ -30,30 +28,21 @@ import type {
SlotKey,
} from './types';
-export function Fill( props: FillComponentProps ) {
- // 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
- // will render. The other one will return null.
- return (
- <>
-
-
- >
- );
-}
+export { Fill };
-export function UnforwardedSlot(
- props: SlotComponentProps &
- Omit< WordPressComponentProps< {}, 'div' >, 'className' >,
- ref: ForwardedRef< any >
-) {
- const { bubblesVirtually, ...restProps } = props;
- if ( bubblesVirtually ) {
- return ;
+export const Slot = forwardRef(
+ (
+ props: SlotComponentProps &
+ Omit< WordPressComponentProps< {}, 'div' >, 'className' >,
+ ref: ForwardedRef< any >
+ ) => {
+ const { bubblesVirtually, ...restProps } = props;
+ if ( bubblesVirtually ) {
+ return ;
+ }
+ return ;
}
- return ;
-}
-export const Slot = forwardRef( UnforwardedSlot );
+);
export function Provider( {
children,
@@ -63,13 +52,7 @@ export function Provider( {
if ( ! parent.isDefault && passthrough ) {
return <>{ children }>;
}
- return (
-
-
- { children }
-
-
- );
+ return { children };
}
Provider.displayName = 'SlotFillProvider';
diff --git a/packages/components/src/slot-fill/provider.tsx b/packages/components/src/slot-fill/provider.tsx
index e5319bc7f33e44..34a92c06c1e487 100644
--- a/packages/components/src/slot-fill/provider.tsx
+++ b/packages/components/src/slot-fill/provider.tsx
@@ -2,55 +2,72 @@
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
+import isShallowEqual from '@wordpress/is-shallow-equal';
/**
* Internal dependencies
*/
import SlotFillContext from './context';
import type {
- FillInstance,
- FillChildren,
- BaseSlotInstance,
- BaseSlotFillContext,
+ SlotRecord,
+ FillRecord,
+ SlotFillInstance,
+ SlotFillRegistry,
SlotFillProviderProps,
SlotKey,
} from './types';
import { observableMap } from '@wordpress/compose';
-function createSlotRegistry(): BaseSlotFillContext {
- const slots = observableMap< SlotKey, BaseSlotInstance >();
- const fills = observableMap<
- SlotKey,
- { instance: FillInstance; children: FillChildren }[]
- >();
+function createSlotRegistry(): SlotFillRegistry {
+ const slots = observableMap< SlotKey, SlotRecord >();
+ const fills = observableMap< SlotKey, FillRecord[] >();
- function registerSlot( name: SlotKey, instance: BaseSlotInstance ) {
- slots.set( name, instance );
+ function registerSlot( name: SlotKey, slot: SlotRecord ) {
+ slots.set( name, slot );
}
- function unregisterSlot( name: SlotKey, instance: BaseSlotInstance ) {
+ function unregisterSlot( name: SlotKey, instance: SlotFillInstance ) {
// 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 ( slots.get( name ) !== instance ) {
+ const currentSlot = slots.get( name );
+ if ( ! currentSlot || currentSlot.instance !== instance ) {
return;
}
slots.delete( name );
}
- function registerFill(
- name: SlotKey,
- instance: FillInstance,
- children: FillChildren
- ) {
- fills.set( name, [
- ...( fills.get( name ) || [] ),
- { instance, children },
- ] );
+ function updateSlot( name: SlotKey, slot: SlotRecord ) {
+ if ( slot.type !== 'portal' ) {
+ return;
+ }
+
+ const slotForName = slots.get( name );
+ if ( ! slotForName ) {
+ return;
+ }
+
+ if ( slotForName.type !== 'portal' ) {
+ return;
+ }
+
+ if ( slotForName.instance !== slot.instance ) {
+ return;
+ }
+
+ if ( isShallowEqual( slotForName.fillProps, slot.fillProps ) ) {
+ return;
+ }
+
+ slots.set( name, slot );
+ }
+
+ function registerFill( name: SlotKey, fill: FillRecord ) {
+ fills.set( name, [ ...( fills.get( name ) || [] ), fill ] );
}
- function unregisterFill( name: SlotKey, instance: FillInstance ) {
+ function unregisterFill( name: SlotKey, instance: SlotFillInstance ) {
const fillsForName = fills.get( name );
if ( ! fillsForName ) {
return;
@@ -62,33 +79,29 @@ function createSlotRegistry(): BaseSlotFillContext {
);
}
- function updateFill(
- name: SlotKey,
- instance: FillInstance,
- children: FillChildren
- ) {
+ function updateFill( name: SlotKey, fill: FillRecord ) {
const fillsForName = fills.get( name );
if ( ! fillsForName ) {
return;
}
const fillForInstance = fillsForName.find(
- ( f ) => f.instance === instance
+ ( f ) => f.instance === fill.instance
);
if ( ! fillForInstance ) {
return;
}
- if ( fillForInstance.children === children ) {
+ if ( fillForInstance.children === fill.children ) {
return;
}
fills.set(
name,
fillsForName.map( ( f ) => {
- if ( f.instance === instance ) {
- // Replace with new record with updated `children`.
- return { instance, children };
+ if ( f.instance === fill.instance ) {
+ // Replace with the new fill record with updated `children`.
+ return fill;
}
return f;
@@ -101,6 +114,7 @@ function createSlotRegistry(): BaseSlotFillContext {
fills,
registerSlot,
unregisterSlot,
+ updateSlot,
registerFill,
unregisterFill,
updateFill,
diff --git a/packages/components/src/slot-fill/slot.tsx b/packages/components/src/slot-fill/slot.tsx
index 82feaa04199f51..7320d258b0d8b3 100644
--- a/packages/components/src/slot-fill/slot.tsx
+++ b/packages/components/src/slot-fill/slot.tsx
@@ -49,14 +49,14 @@ function addKeysToChildren( children: ReactNode ) {
}
function Slot( props: Omit< SlotComponentProps, 'bubblesVirtually' > ) {
+ const { name, children, fillProps = {} } = props;
+
const registry = useContext( SlotFillContext );
const instanceRef = useRef( {} );
- const { name, children, fillProps = {} } = props;
-
useEffect( () => {
const instance = instanceRef.current;
- registry.registerSlot( name, instance );
+ registry.registerSlot( name, { type: 'children', instance } );
return () => registry.unregisterSlot( name, instance );
}, [ registry, name ] );
@@ -64,7 +64,7 @@ function Slot( props: Omit< SlotComponentProps, 'bubblesVirtually' > ) {
const currentSlot = useObservableValue( registry.slots, name );
// Fills should only be rendered in the currently registered instance of the slot.
- if ( currentSlot !== instanceRef.current ) {
+ if ( ! currentSlot || currentSlot.instance !== instanceRef.current ) {
fills = [];
}
diff --git a/packages/components/src/slot-fill/types.ts b/packages/components/src/slot-fill/types.ts
index 758f1c8257d548..fb76fcbf53af47 100644
--- a/packages/components/src/slot-fill/types.ts
+++ b/packages/components/src/slot-fill/types.ts
@@ -112,42 +112,26 @@ export type SlotFillProviderProps = {
passthrough?: boolean;
};
+export type SlotFillInstance = {};
export type SlotRef = RefObject< HTMLElement >;
-export type FillInstance = {};
-export type BaseSlotInstance = {};
-
-export type SlotFillBubblesVirtuallyContext = {
- slots: ObservableMap< SlotKey, { ref: SlotRef; fillProps: FillProps } >;
- fills: ObservableMap< SlotKey, FillInstance[] >;
- registerSlot: ( name: SlotKey, ref: SlotRef, fillProps: FillProps ) => void;
- unregisterSlot: ( name: SlotKey, ref: SlotRef ) => void;
- updateSlot: ( name: SlotKey, ref: SlotRef, fillProps: FillProps ) => void;
- registerFill: ( name: SlotKey, instance: FillInstance ) => void;
- unregisterFill: ( name: SlotKey, instance: FillInstance ) => void;
+export type SlotRecord = { instance: SlotFillInstance } & (
+ | { type: 'children' }
+ | { type: 'portal'; ref: SlotRef; fillProps: FillProps }
+);
+export type FillRecord = { instance: SlotFillInstance; children: FillChildren };
+
+export type SlotFillRegistry = {
+ slots: ObservableMap< SlotKey, SlotRecord >;
+ fills: ObservableMap< SlotKey, FillRecord[] >;
+ registerSlot: ( name: SlotKey, slot: SlotRecord ) => void;
+ unregisterSlot: ( name: SlotKey, instance: SlotFillInstance ) => void;
+ updateSlot: ( name: SlotKey, slot: SlotRecord ) => void;
+ registerFill: ( name: SlotKey, fill: FillRecord ) => void;
+ unregisterFill: ( name: SlotKey, instance: SlotFillInstance ) => void;
+ updateFill: ( name: SlotKey, fill: FillRecord ) => void;
/**
* This helps the provider know if it's using the default context value or not.
*/
isDefault?: boolean;
};
-
-export type BaseSlotFillContext = {
- slots: ObservableMap< SlotKey, BaseSlotInstance >;
- fills: ObservableMap<
- SlotKey,
- { instance: FillInstance; children: FillChildren }[]
- >;
- registerSlot: ( name: SlotKey, slot: BaseSlotInstance ) => void;
- unregisterSlot: ( name: SlotKey, slot: BaseSlotInstance ) => void;
- registerFill: (
- name: SlotKey,
- instance: FillInstance,
- children: FillChildren
- ) => void;
- unregisterFill: ( name: SlotKey, instance: FillInstance ) => void;
- updateFill: (
- name: SlotKey,
- instance: FillInstance,
- children: FillChildren
- ) => void;
-};