Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components: remove createPrivateSlotFill function #67238

Merged
merged 4 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions packages/block-editor/src/components/block-controls/slot.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ export default function BlockControlsSlot( { group = 'default', ...props } ) {
[ toolbarState, contextState ]
);

const Slot = groups[ group ]?.Slot;
const fills = useSlotFills( Slot?.__unstableName );
if ( ! Slot ) {
const slotFill = groups[ group ];
const fills = useSlotFills( slotFill.name );

if ( ! slotFill ) {
warning( `Unknown BlockControls group "${ group }" provided.` );
return null;
}
Expand All @@ -42,6 +43,7 @@ export default function BlockControlsSlot( { group = 'default', ...props } ) {
return null;
}

const { Slot } = slotFill;
const slot = <Slot { ...props } bubblesVirtually fillProps={ fillProps } />;

if ( group === 'default' ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
/**
* WordPress dependencies
*/
import { privateApis as componentsPrivateApis } from '@wordpress/components';
import { createSlotFill } from '@wordpress/components';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';
import {
useBlockEditContext,
mayDisplayControlsKey,
} from '../block-edit/context';

const { createPrivateSlotFill } = unlock( componentsPrivateApis );
const { Fill, Slot } = createPrivateSlotFill( 'BlockInformation' );
const { Fill, Slot } = createSlotFill( Symbol( 'BlockInformation' ) );

const BlockInfo = ( props ) => {
const context = useBlockEditContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ const PositionControlsPanel = () => {
};

const PositionControls = () => {
const fills = useSlotFills(
InspectorControlsGroups.position.Slot.__unstableName
);
const fills = useSlotFills( InspectorControlsGroups.position.name );
const hasFills = Boolean( fills && fills.length );

if ( ! hasFills ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,18 @@ export default function useInspectorControlsTabs( blockName ) {

// List View Tab: If there are any fills for the list group add that tab.
const listViewDisabled = useIsListViewTabDisabled( blockName );
const listFills = useSlotFills( listGroup.Slot.__unstableName );
const listFills = useSlotFills( listGroup.name );
const hasListFills = ! listViewDisabled && !! listFills && listFills.length;

// Styles Tab: Add this tab if there are any fills for block supports
// e.g. border, color, spacing, typography, etc.
const styleFills = [
...( useSlotFills( borderGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( colorGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( dimensionsGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( stylesGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( typographyGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( effectsGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( borderGroup.name ) || [] ),
...( useSlotFills( colorGroup.name ) || [] ),
...( useSlotFills( dimensionsGroup.name ) || [] ),
...( useSlotFills( stylesGroup.name ) || [] ),
...( useSlotFills( typographyGroup.name ) || [] ),
...( useSlotFills( effectsGroup.name ) || [] ),
];
const hasStyleFills = styleFills.length;

Expand All @@ -67,12 +67,12 @@ export default function useInspectorControlsTabs( blockName ) {
// the advanced controls slot as well to ensure they are rendered.
const advancedFills = [
...( useSlotFills( InspectorAdvancedControls.slotName ) || [] ),
...( useSlotFills( bindingsGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( bindingsGroup.name ) || [] ),
];

const settingsFills = [
...( useSlotFills( defaultGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( positionGroup.Slot.__unstableName ) || [] ),
...( useSlotFills( defaultGroup.name ) || [] ),
...( useSlotFills( positionGroup.name ) || [] ),
...( hasListFills && hasStyleFills > 1 ? advancedFills : [] ),
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ export default function InspectorControlsSlot( {
);
group = __experimentalGroup;
}
const Slot = groups[ group ]?.Slot;
const fills = useSlotFills( Slot?.__unstableName );
const slotFill = groups[ group ];
const fills = useSlotFills( slotFill?.name );

const motionContextValue = useContext( MotionContext );

const computedFillProps = useMemo(
() => ( {
...( fillProps ?? {} ),
...fillProps,
forwardedContext: [
...( fillProps?.forwardedContext ?? [] ),
[ MotionContext.Provider, { value: motionContextValue } ],
Expand All @@ -50,7 +50,7 @@ export default function InspectorControlsSlot( {
[ motionContextValue, fillProps ]
);

if ( ! Slot ) {
if ( ! slotFill ) {
warning( `Unknown InspectorControls group "${ group }" provided.` );
return null;
}
Expand All @@ -59,6 +59,8 @@ export default function InspectorControlsSlot( {
return null;
}

const { Slot } = slotFill;

if ( label ) {
return (
<BlockSupportToolsPanel group={ group } label={ label }>
Expand Down
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
### Experimental

- `SlotFill`: Remove registration API methods from return value of `__experimentalUseSlot` ([#67070](https://github.com/WordPress/gutenberg/pull/67070)).
- `SlotFill`: Remove the `createPrivateSlotFill` private API ([#67238](https://github.com/WordPress/gutenberg/pull/67238)).

### Internal

Expand Down
2 changes: 0 additions & 2 deletions packages/components/src/private-apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Internal dependencies
*/
import { positionToPlacement as __experimentalPopoverLegacyPositionToPlacement } from './popover/utils';
import { createPrivateSlotFill } from './slot-fill';
import { Menu } from './menu';
import { ComponentsContext } from './context/context-system-provider';
import Theme from './theme';
Expand All @@ -13,7 +12,6 @@ import { lock } from './lock-unlock';
export const privateApis = {};
lock( privateApis, {
__experimentalPopoverLegacyPositionToPlacement,
createPrivateSlotFill,
ComponentsContext,
Tabs,
Theme,
Expand Down
41 changes: 26 additions & 15 deletions packages/components/src/slot-fill/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Slot Fill
# Slot/Fill

Slot and Fill are a pair of components which enable developers to render elsewhere in a React element tree, a pattern often referred to as "portal" rendering. It is a pattern for component extensibility, where a single Slot may be occupied by an indeterminate number of Fills elsewhere in the application.
`Slot` and `Fill` are a pair of components which enable developers to render React UI elsewhere in a React element tree, a pattern often referred to as "portal" rendering. It is a pattern for component extensibility, where a single `Slot` may be occupied by multiple `Fill`s rendered in different parts of the application.

Slot Fill is heavily inspired by the [`react-slot-fill` library](https://github.com/camwest/react-slot-fill), but uses React's own portal rendering API.
Slot/Fill was originally inspired by the [`react-slot-fill` library](https://github.com/camwest/react-slot-fill).

## Usage

At the root of your application, you must render a `SlotFillProvider` which coordinates Slot and Fill rendering.
At the root of your application, you must render a `SlotFillProvider` which coordinates `Slot` and `Fill` rendering.

Then, render a Slot component anywhere in your application, giving it a name.
Then, render a `Slot` component anywhere in your application, giving it a `name`. The `name` is either a `string` or a symbol. Symbol names are useful for slots that are supposed to be private, accessible only to clients that have access to the symbol value.

Any Fill will automatically occupy this Slot space, even if rendered elsewhere in the application.
Any `Fill` will render its UI in this `Slot` space, even if rendered elsewhere in the application.

You can either use the Fill component directly, or a wrapper component type as in the below example to abstract the slot name from consumer awareness.
You can either use the `Fill` component directly, or create a wrapper component (as in the following example) to hide the slot name from the consumer.

```jsx
import {
Expand Down Expand Up @@ -43,7 +43,7 @@ const MySlotFillProvider = () => {
};
```

There is also `createSlotFill` helper method which was created to simplify the process of matching the corresponding `Slot` and `Fill` components:
There is also the `createSlotFill` helper method which was created to simplify the process of matching the corresponding `Slot` and `Fill` components:

```jsx
const { Fill, Slot } = createSlotFill( 'Toolbar' );
Expand All @@ -59,18 +59,27 @@ const Toolbar = () => (

## Props

The `SlotFillProvider` component does not accept any props.
The `SlotFillProvider` component does not accept any props (except `children`).

Both `Slot` and `Fill` accept a `name` string prop, where a `Slot` with a given `name` will render the `children` of any associated `Fill`s.

`Slot` accepts a `bubblesVirtually` prop which changes the event bubbling behaviour:
`Slot` accepts a `bubblesVirtually` prop which changes the method how the `Fill` children are rendered. With `bubblesVirtually`, the `Fill` is rendered using a React portal. That affects the event bubbling and React context propagation behaviour:

- By default, events will bubble to their parents on the DOM hierarchy (native event bubbling)
- If `bubblesVirtually` is set to true, events will bubble to their virtual parent in the React elements hierarchy instead.
### `bubblesVirtually=false`

`Slot` with `bubblesVirtually` set to true also accept optional `className` and `style` props to add to the slot container.
- events will bubble to their parents on the DOM hierarchy (native event bubbling)
- the React elements inside the `Fill` will be rendered with React context of the `Slot`
- renders the `Fill` elements directly, inside a `Fragment`, with no wrapper DOM element

`Slot` **without** `bubblesVirtually` accepts an optional `children` function prop, which takes `fills` as a param. It allows you to perform additional processing and wrap `fills` conditionally.
### `bubblesVirtually=true`

- events will bubble to their virtual (React) parent in the React elements hierarchy
- the React elements inside the `Fill` will keep the React context of the `Fill` and its parents
- renders a wrapper DOM element inside which the `Fill` elements are rendered (used as an argument for React `createPortal`)

`Slot` with `bubblesVirtually=true` renders a wrapper DOM element (a `div` by default) and accepts additional props that customize this element, like `className` or `style`. You can also replace the `div` with another element by passing an `as` prop.

`Slot` **without** `bubblesVirtually` accepts an optional `children` prop, which is a function that receives `fills` array as a param. It allows you to perform additional processing: render a placeholder when there are no fills, or render a wrapper only when there are fills.

_Example_:

Expand All @@ -90,7 +99,9 @@ const Toolbar = ( { isMobile } ) => (
);
```

Props can also be passed from a `Slot` to a `Fill` by using the prop `fillProps` on the `Slot`:
Additional information (props) can also be passed from a `Slot` to a `Fill` by a combination of:
1. Adding a `fillProps` prop to the `Slot`.
2. Passing a function as `children` to the `Fill`. This function will receive the `fillProps` as an argument.

```jsx
const { Fill, Slot } = createSlotFill( 'Toolbar' );
Expand Down
12 changes: 5 additions & 7 deletions packages/components/src/slot-fill/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,15 @@ export function createSlotFill( key: SlotKey ) {
props: DistributiveOmit< SlotComponentProps, 'name' >
) => <Slot name={ key } { ...props } />;
SlotComponent.displayName = `${ baseName }Slot`;
/**
* @deprecated 6.8.0
* Please use `slotFill.name` instead of `slotFill.Slot.__unstableName`.
*/
SlotComponent.__unstableName = key;

return {
name: key,
Fill: FillComponent,
Slot: SlotComponent,
};
}

export const createPrivateSlotFill = ( name: string ) => {
const privateKey = Symbol( name );
const privateSlotFill = createSlotFill( privateKey );

return { privateKey, ...privateSlotFill };
};
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function EditorCanvasContainer( {
}

function useHasEditorCanvasContainer() {
const fills = useSlotFills( EditorContentSlotFill.privateKey );
const fills = useSlotFills( EditorContentSlotFill.name );
return !! fills?.length;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
/**
* WordPress dependencies
*/
import { privateApis as componentsPrivateApis } from '@wordpress/components';
import { createSlotFill } from '@wordpress/components';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';

const { createPrivateSlotFill } = unlock( componentsPrivateApis );
const SLOT_FILL_NAME = 'EditCanvasContainerSlot';
const EditorContentSlotFill = createPrivateSlotFill( SLOT_FILL_NAME );
const EditorContentSlotFill = createSlotFill(
Symbol( 'EditCanvasContainerSlot' )
);

export default EditorContentSlotFill;
Loading