Skip to content

Commit

Permalink
SlotFill: Migrate to Typescript. (#51350)
Browse files Browse the repository at this point in the history
* convert typescript slot-fill-context.ts and slot-fill-provider.tsx

* fix portalContainer type.

* convert hooks to ts.

* fix types

* fix fillProps

* convert slot.tsx

* update Fill

* fix typename.

* fix dropdown v2 portal container type.

* fix ref type

* refactor

* refactor SlotFillProvider

* migrate SlotComponent to TS

* convert useSlot

* add update

* fix ProviderProps

* refactor type

* refactor type

* allow symbol to name prop.

* refactor SlotComponentProps

* refactor stroies and README

* fix typo

* remove comments. remove children from Slot.

* refactor SlotComponentProps

* Apply suggestions from code review

Co-authored-by: Lena Morita <lena@jaguchi.com>

* Apply suggestions from code review

Co-authored-by: Lena Morita <lena@jaguchi.com>

* refactor: newChildren to inline.

* Remove unnecessary type variables.

* fix type comments

* Remove unnecessary Type Guards. Remove unnecessary Type Guards. And use `React.Key`.

* remove type from jsdoc

* refactor types

* refactor types

* Simplify the type of `slot`.

* Remove the type specification and leave it to type inference.

* fix types

* remove story.js

* update changelog

* remove ts-nocheck

* fix story filename. remove override bubblesVirtually attribute

* replace useState to useMemo

* switch to ts-expect-error in story.

* fix mssing changelog https://github.com/WordPress/gutenberg/pull/53272/files/362b6d4405edd476df1f6479bb264dc9f51d6789#r1319926144

* add `children?: never` #51350 (comment)

* use Record

* use Record / Enable className only when bubblesVirtually: true.

* Update packages/components/src/slot-fill/types.ts

Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>

* update changelog

* use optional chain

* fix WordPressComponentProps import

---------

Co-authored-by: Lena Morita <lena@jaguchi.com>
Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>
  • Loading branch information
3 people authored and mikachan committed Dec 23, 2023
1 parent 82fc70d commit 3cca31c
Show file tree
Hide file tree
Showing 21 changed files with 682 additions and 377 deletions.
3 changes: 3 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
- `Tooltip`, `Shortcut`: Remove unused `ui/` components from the codebase ([#54573](https://github.com/WordPress/gutenberg/pull/54573))
- Update `uuid` package to 9.0.1 ([#54725](https://github.com/WordPress/gutenberg/pull/54725)).
- `ContextSystemProvider`: Move out of `ui/` ([#54847](https://github.com/WordPress/gutenberg/pull/54847)).
- `SlotFill`: Migrate to TypeScript and Convert to Functional Component `<Slot bubblesVirtually />`. ([#51350](https://github.com/WordPress/gutenberg/pull/51350)).


## 25.8.0 (2023-09-20)

Expand Down Expand Up @@ -130,6 +132,7 @@

- `ColorPalette`, `BorderControl`: Don't hyphenate hex value in `aria-label` ([#52932](https://github.com/WordPress/gutenberg/pull/52932)).
- `MenuItemsChoice`, `MenuItem`: Support a `disabled` prop on a menu item ([#52737](https://github.com/WordPress/gutenberg/pull/52737)).
- `TabPanel`: Introduce a new version of `TabPanel` with updated internals and improved adherence to ARIA guidance on `tabpanel` focus behavior while maintaining the same functionality and API surface.([#52133](https://github.com/WordPress/gutenberg/pull/52133)).

### Bug Fix

Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/dropdown-menu-v2/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,5 +261,5 @@ export type DropdownMenuPrivateContext = Pick<
DropdownMenuInternalContext,
'variant'
> & {
portalContainer: HTMLElement | null;
portalContainer?: HTMLElement | null;
};
1 change: 0 additions & 1 deletion packages/components/src/popover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,6 @@ function PopoverSlot(
) {
return (
<Slot
// @ts-expect-error Need to type `SlotFill`
bubblesVirtually
name={ name }
className="popover-slot"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
/**
* WordPress dependencies
*/
Expand All @@ -9,6 +8,7 @@ import { useRef, useState, useEffect, createPortal } from '@wordpress/element';
*/
import useSlot from './use-slot';
import StyleProvider from '../../style-provider';
import type { FillComponentProps } from '../types';

function useForceUpdate() {
const [ , setState ] = useState( {} );
Expand All @@ -28,7 +28,8 @@ function useForceUpdate() {
};
}

export default function Fill( { name, children } ) {
export default function Fill( props: FillComponentProps ) {
const { name, children } = props;
const { registerFill, unregisterFill, ...slot } = useSlot( name );
const rerender = useForceUpdate();
const ref = useRef( { rerender } );
Expand All @@ -47,17 +48,15 @@ export default function Fill( { name, children } ) {
return null;
}

if ( typeof children === 'function' ) {
children = children( slot.fillProps );
}

// 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 = (
<StyleProvider document={ slot.ref.current.ownerDocument }>
{ children }
{ typeof children === 'function'
? children( slot.fillProps ?? {} )
: children }
</StyleProvider>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
/**
* External dependencies
*/
Expand All @@ -8,8 +7,12 @@ import { proxyMap } from 'valtio/utils';
*/
import { createContext } from '@wordpress/element';
import warning from '@wordpress/warning';
/**
* Internal dependencies
*/
import type { SlotFillBubblesVirtuallyContext } from '../types';

const SlotFillContext = createContext( {
const initialContextValue: SlotFillBubblesVirtuallyContext = {
slots: proxyMap(),
fills: proxyMap(),
registerSlot: () => {
Expand All @@ -25,6 +28,8 @@ const SlotFillContext = createContext( {

// This helps the provider know if it's using the default context value or not.
isDefault: true,
} );
};

const SlotFillContext = createContext( initialContextValue );

export default SlotFillContext;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* External dependencies
*/
import { ref as valRef } from 'valtio';
import { proxyMap } from 'valtio/utils';

/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';
import isShallowEqual from '@wordpress/is-shallow-equal';

/**
* Internal dependencies
*/
import SlotFillContext from './slot-fill-context';
import type {
SlotFillProviderProps,
SlotFillBubblesVirtuallyContext,
} from '../types';

function createSlotRegistry(): SlotFillBubblesVirtuallyContext {
const slots: SlotFillBubblesVirtuallyContext[ 'slots' ] = proxyMap();
const fills: SlotFillBubblesVirtuallyContext[ 'fills' ] = proxyMap();

const registerSlot: SlotFillBubblesVirtuallyContext[ 'registerSlot' ] = (
name,
ref,
fillProps
) => {
const slot = slots.get( name );

slots.set(
name,
valRef( {
...slot,
ref: ref || slot?.ref,
fillProps: fillProps || slot?.fillProps || {},
} )
);
};

const unregisterSlot: SlotFillBubblesVirtuallyContext[ 'unregisterSlot' ] =
( name, ref ) => {
// Make sure we're not unregistering a slot registered by another element
// See https://github.com/WordPress/gutenberg/pull/19242#issuecomment-590295412
if ( slots.get( name )?.ref === ref ) {
slots.delete( name );
}
};

const updateSlot: SlotFillBubblesVirtuallyContext[ 'updateSlot' ] = (
name,
fillProps
) => {
const slot = slots.get( name );
if ( ! slot ) {
return;
}

if ( isShallowEqual( slot.fillProps, fillProps ) ) {
return;
}

slot.fillProps = fillProps;
const slotFills = fills.get( name );
if ( slotFills ) {
// Force update fills.
slotFills.map( ( fill ) => fill.current.rerender() );
}
};

const registerFill: SlotFillBubblesVirtuallyContext[ 'registerFill' ] = (
name,
ref
) => {
fills.set( name, valRef( [ ...( fills.get( name ) || [] ), ref ] ) );
};

const unregisterFill: SlotFillBubblesVirtuallyContext[ 'registerFill' ] = (
name,
ref
) => {
const fillsForName = fills.get( name );
if ( ! fillsForName ) {
return;
}

fills.set(
name,
valRef( fillsForName.filter( ( fillRef ) => fillRef !== ref ) )
);
};

return {
slots,
fills,
registerSlot,
updateSlot,
unregisterSlot,
registerFill,
unregisterFill,
};
}

export default function SlotFillProvider( {
children,
}: SlotFillProviderProps ) {
const registry = useMemo( createSlotRegistry, [] );
return (
<SlotFillContext.Provider value={ registry }>
{ children }
</SlotFillContext.Provider>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
// @ts-nocheck
/**
* External dependencies
*/
import type { ForwardedRef } from 'react';

/**
* WordPress dependencies
*/
Expand All @@ -15,21 +19,30 @@ import { useMergeRefs } from '@wordpress/compose';
*/
import { View } from '../../view';
import SlotFillContext from './slot-fill-context';
import type { WordPressComponentProps } from '../../context';
import type { SlotComponentProps } from '../types';

function Slot( props, forwardedRef ) {
function Slot(
props: WordPressComponentProps<
Omit< SlotComponentProps, 'bubblesVirtually' >,
'div'
>,
forwardedRef: ForwardedRef< any >
) {
const {
name,
fillProps = {},
as,
// `children` is not allowed. However, if it is passed,
// it will be displayed as is, so remove `children`.
// @ts-ignore
children,
...restProps
} = props;

const { registerSlot, unregisterSlot, ...registry } =
useContext( SlotFillContext );
const ref = useRef();
const ref = useRef< HTMLElement >( null );

useLayoutEffect( () => {
registerSlot( name, ref, fillProps );
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
/**
* External dependencies
*/
Expand All @@ -13,8 +12,9 @@ import { useContext } from '@wordpress/element';
* Internal dependencies
*/
import SlotFillContext from './slot-fill-context';
import type { SlotKey } from '../types';

export default function useSlotFills( name ) {
export default function useSlotFills( name: SlotKey ) {
const registry = useContext( SlotFillContext );
const fills = useSnapshot( registry.fills, { sync: true } );
// The important bit here is that this call ensures that the hook
Expand Down
Loading

0 comments on commit 3cca31c

Please sign in to comment.