Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
SlotFill: Migrate to Typescript. (#51350)
* 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
commit 3cca31c5866b3ffc397200c3d385fba7a2028642
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