diff --git a/packages/components/src/slot-fill/context.js b/packages/components/src/slot-fill/context.ts
similarity index 54%
rename from packages/components/src/slot-fill/context.js
rename to packages/components/src/slot-fill/context.ts
index 0f7961a8ffd3b6..9df4b9898b05fe 100644
--- a/packages/components/src/slot-fill/context.js
+++ b/packages/components/src/slot-fill/context.ts
@@ -1,17 +1,21 @@
-// @ts-nocheck
/**
* WordPress dependencies
*/
import { createContext } from '@wordpress/element';
+/**
+ * Internal dependencies
+ */
+import type { BaseSlotFillContext } from './types';
-export const SlotFillContext = createContext( {
+const initialValue: BaseSlotFillContext = {
registerSlot: () => {},
unregisterSlot: () => {},
registerFill: () => {},
unregisterFill: () => {},
getSlot: () => {},
getFills: () => {},
- subscribe: () => {},
-} );
+ subscribe: () => () => {},
+};
+export const SlotFillContext = createContext( initialValue );
export default SlotFillContext;
diff --git a/packages/components/src/slot-fill/provider.js b/packages/components/src/slot-fill/provider.js
deleted file mode 100644
index 94c83c3c350111..00000000000000
--- a/packages/components/src/slot-fill/provider.js
+++ /dev/null
@@ -1,119 +0,0 @@
-// @ts-nocheck
-/**
- * WordPress dependencies
- */
-import { Component } from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import SlotFillContext from './context';
-
-export default 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.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,
- 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 ] =
- this.fills[ name ]?.filter( ( fill ) => fill !== 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 ];
- }
-
- 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 = this.listeners.filter( ( l ) => l !== listener );
- };
- }
-
- render() {
- return (
-
- { this.props.children }
-
- );
- }
-}
diff --git a/packages/components/src/slot-fill/provider.tsx b/packages/components/src/slot-fill/provider.tsx
new file mode 100644
index 00000000000000..c2299e9c0e6320
--- /dev/null
+++ b/packages/components/src/slot-fill/provider.tsx
@@ -0,0 +1,116 @@
+/**
+ * WordPress dependencies
+ */
+import type { Component } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import SlotFillContext from './context';
+import type { BaseSlotFillContext } from './types';
+import { useState } from '@wordpress/element';
+/**
+ * External dependencies
+ */
+import type { ReactNode } from 'react';
+
+export function createSlotRegistory(): BaseSlotFillContext {
+ const slots: Record< string, Component > = {};
+ const fills: Record< string, unknown[] > = {};
+ let listeners: Array< () => void > = [];
+
+ function registerSlot( name: string, slot: Component ) {
+ const previousSlot = slots[ name ];
+ slots[ name ] = slot;
+ triggerListeners();
+
+ // Sometimes the fills are registered after the initial render of slot
+ // But before the registerSlot call, we need to rerender the slot.
+ 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();
+ }
+ }
+
+ function registerFill( name: string, instance: unknown ) {
+ fills[ name ] = [ ...( fills[ name ] || [] ), instance ];
+ forceUpdateSlot( name );
+ }
+
+ function unregisterSlot( name: string, instance: Component ) {
+ // 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[ name ] !== instance ) {
+ return;
+ }
+
+ delete slots[ name ];
+ triggerListeners();
+ }
+
+ function unregisterFill( name: string, instance: unknown ) {
+ fills[ name ] =
+ fills[ name ]?.filter( ( fill ) => fill !== instance ) ?? [];
+ forceUpdateSlot( name );
+ }
+
+ function getSlot( name: string ) {
+ return slots[ name ];
+ }
+
+ function getFills( name: string, slotInstance: unknown ) {
+ // Fills should only be returned for the current instance of the slot
+ // in which they occupy.
+ if ( slots[ name ] !== slotInstance ) {
+ return [];
+ }
+ return fills[ name ];
+ }
+
+ function forceUpdateSlot( name: string ) {
+ const slot = getSlot( name );
+
+ if ( slot ) {
+ slot.forceUpdate();
+ }
+ }
+
+ function triggerListeners() {
+ listeners.forEach( ( listener ) => listener() );
+ }
+
+ function subscribe( listener: () => void ) {
+ listeners.push( listener );
+
+ return () => {
+ listeners = listeners.filter( ( l ) => l !== listener );
+ };
+ }
+
+ return {
+ registerSlot,
+ unregisterSlot,
+ registerFill,
+ unregisterFill,
+ getSlot,
+ getFills,
+ subscribe,
+ };
+}
+
+export function SlotFillProvider( { children }: { children: ReactNode } ) {
+ const [ contextValue ] = useState( createSlotRegistory );
+ return (
+
+ { children }
+
+ );
+}
+
+export default SlotFillProvider;
diff --git a/packages/components/src/slot-fill/types.ts b/packages/components/src/slot-fill/types.ts
new file mode 100644
index 00000000000000..4068cafbc9e16a
--- /dev/null
+++ b/packages/components/src/slot-fill/types.ts
@@ -0,0 +1,15 @@
+/**
+ * External dependencies
+ */
+import type { Component } from 'react';
+
+export type BaseSlotFillContext = {
+ registerSlot: ( name: string, slot: Component ) => void;
+ unregisterSlot: ( name: string, slot: Component ) => void;
+ registerFill: ( name: string, instance: any ) => void;
+ unregisterFill: ( name: string, instance: any ) => void;
+ getSlot: ( name: string ) => any;
+ // TODO: getFill?
+ getFills: ( name: string, slotInstance: any ) => any;
+ subscribe: ( listener: () => {} ) => () => void;
+};