Skip to content
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
1 change: 1 addition & 0 deletions packages/block-editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
- Convert the block support helpers to TypeScript ([#84721](https://github.com/WordPress/gutenberg/pull/84721)).
- Convert the layout helpers and block gap utils to TypeScript ([#84722](https://github.com/WordPress/gutenberg/pull/84722)).
- Convert the block attribute filter hooks to TypeScript ([#84723](https://github.com/WordPress/gutenberg/pull/84723)).
- Convert `InspectorControls` to TypeScript ([#84725](https://github.com/WordPress/gutenberg/pull/84725)).

## 18.1.0 (2026-10-07)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { __experimentalToolsPanelContext as ToolsPanelContext } from '@wordpress/components';
import { useContext, useMemo } from '@wordpress/element';
import type { BlockSupportSlotContainerProps } from './types';

export default function BlockSupportSlotContainer( {
Slot,
fillProps,
...props
} ) {
}: BlockSupportSlotContainerProps ) {
// Add the toolspanel context provider and value to existing fill props
const toolsPanelContext = useContext( ToolsPanelContext );
const computedFillProps = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@ import { useCallback } from '@wordpress/element';
import { store as blockEditorStore } from '../../store';
import { cleanEmptyObject } from '../../hooks/utils';
import { useToolsPanelDropdownMenuProps } from '../global-styles/utils';
import type {
BlockSupportResetAllFilter,
BlockSupportToolsPanelProps,
} from './types';

export default function BlockSupportToolsPanel( { children, group, label } ) {
export default function BlockSupportToolsPanel( {
children,
group,
label,
}: BlockSupportToolsPanelProps ) {
const { updateBlockAttributes } = useDispatch( blockEditorStore );
const {
getBlockAttributes,
Expand All @@ -17,17 +25,21 @@ export default function BlockSupportToolsPanel( { children, group, label } ) {
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
const panelId = getSelectedBlockClientId();
const resetAll = useCallback(
( resetFilters = [] ) => {
const newAttributes = {};
( resetFilters: BlockSupportResetAllFilter[] = [] ) => {
const newAttributes: Record<
string,
Record< string, unknown >
> = {};

// The panel only renders while a block is selected.
const clientIds = hasMultiSelection()
? getMultiSelectedBlockClientIds()
: [ panelId ];
: [ panelId! ];

clientIds.forEach( ( clientId ) => {
const blockAttributes = getBlockAttributes( clientId ) || {};
const { style } = blockAttributes;
let newBlockAttributes = { style };
let newBlockAttributes: Record< string, unknown > = { style };
const resetContext = {
attributes: blockAttributes,
clientId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ import {
useBlockStyleState,
} from '../../hooks/block-style-state';
import { ListViewContentFill } from './list-view-content-popover';
import type {
InspectorControlsFillProps,
RegisterResetAllProps,
ToolsPanelInspectorControlProps,
} from './types';

const PATTERN_EDITING_GROUPS = [ 'content', 'list' ];

export default function InspectorControlsFill( {
children,
group = 'default',
resetAllFilter,
} ) {
}: InspectorControlsFillProps ) {
const context = useBlockEditContext();
const isSelectedBlock = context[ mayDisplayControlsKey ];
const isPatternEditing = context[ mayDisplayPatternEditingControlsKey ];
Expand Down Expand Up @@ -87,15 +92,18 @@ export default function InspectorControlsFill( {
);
}

function RegisterResetAll( { resetAllFilter, children } ) {
function RegisterResetAll( {
resetAllFilter,
children,
}: RegisterResetAllProps ) {
const { registerResetAllFilter, deregisterResetAllFilter } =
useContext( ToolsPanelContext );
const selectedState = useBlockStyleState();
const scopedResetAllFilter = useMemo(
() => scopeResetAllFilterToState( selectedState, resetAllFilter ),
[ resetAllFilter, selectedState ]
);
useEffect( () => {
useEffect( (): void | ( () => void ) => {
if (
scopedResetAllFilter &&
registerResetAllFilter &&
Expand All @@ -114,7 +122,11 @@ function RegisterResetAll( { resetAllFilter, children } ) {
return children;
}

function ToolsPanelInspectorControl( { children, resetAllFilter, fillProps } ) {
function ToolsPanelInspectorControl( {
children,
resetAllFilter,
fillProps,
}: ToolsPanelInspectorControlProps ) {
// `fillProps.forwardedContext` is an array of context provider entries, provided by slot,
// that should wrap the fill markup.
const { forwardedContext = [] } = fillProps;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createSlotFill } from '@wordpress/components';
import type { SlotFill } from './types';

const InspectorControlsDefault = createSlotFill( 'InspectorControls' );
const InspectorControlsAdvanced = createSlotFill( 'InspectorAdvancedControls' );
Expand Down Expand Up @@ -26,7 +27,7 @@ const InspectorControlsStyles = createSlotFill( 'InspectorControlsStyles' );
const InspectorControlsEffects = createSlotFill( 'InspectorControlsEffects' );
const InspectorControlsContent = createSlotFill( 'InspectorControlsContent' );

const groups = {
const groups: Record< string, SlotFill > = {
default: InspectorControlsDefault,
advanced: InspectorControlsAdvanced,
background: InspectorControlsBackground,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import type { ComponentProps } from 'react';
import InspectorControlsFill from './fill';
import InspectorControlsSlot from './slot';
import type { InspectorControlsFillProps } from './types';

const InspectorControls = InspectorControlsFill;
// TypeScript only types a property set on a function declared in this file.
const InspectorControls =
InspectorControlsFill as typeof InspectorControlsFill & {
Slot: typeof InspectorControlsSlot;
};

InspectorControls.Slot = InspectorControlsSlot;

// This is just here for backward compatibility.
export const InspectorAdvancedControls = ( props ) => {
export const InspectorAdvancedControls = (
props: InspectorControlsFillProps
) => {
return <InspectorControlsFill { ...props } group="advanced" />;
};
InspectorAdvancedControls.Slot = function Slot( props ) {
InspectorAdvancedControls.Slot = function Slot(
props: ComponentProps< typeof InspectorControlsSlot >
) {
return <InspectorControlsSlot { ...props } group="advanced" />;
};
InspectorAdvancedControls.slotName = 'InspectorAdvancedControls';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { createSlotFill } from '@wordpress/components';
import type { ComponentProps } from 'react';
import {
useBlockEditContext,
mayDisplayControlsKey,
} from '../block-edit/context';

const { Fill, Slot } = createSlotFill( Symbol( 'InspectorControlsLastItem' ) );

const InspectorControlsLastItem = ( props ) => {
const InspectorControlsLastItem = ( props: ComponentProps< typeof Fill > ) => {
const context = useBlockEditContext();
if ( ! context[ mayDisplayControlsKey ] ) {
return null;
}
return <Fill { ...props } />;
};
InspectorControlsLastItem.Slot = function InspectorControlsLastItemSlot(
props
props: ComponentProps< typeof Slot >
) {
return <Slot { ...props } />;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { useViewportMatch } from '@wordpress/compose';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';

/** @typedef {import('@wordpress/element').RefObject} RefObject */
import type { ListViewContentPopoverProps } from './types';

// Create private slot-fill for ListViewContentPanel
const LIST_VIEW_CONTENT_PANEL_SLOT = Symbol( 'ListViewContentPopover' );
Expand All @@ -23,7 +22,7 @@ function useInspectorPopoverPlacement() {
return ! isMobile
? {
popoverProps: {
placement: 'left-start',
placement: 'left-start' as const,
offset: 35,
resize: false,
},
Expand All @@ -38,10 +37,12 @@ function useInspectorPopoverPlacement() {
* List View block support. The routing is handled automatically by
* `InspectorControlsFill`.
*
* @param {Object} props
* @param {RefObject<HTMLElement>} props.listViewRef Ref to the List View slot for the block inspector.
* @param props
* @param props.listViewRef Ref to the List View slot for the block inspector.
*/
export function ListViewContentPopover( { listViewRef } ) {
export function ListViewContentPopover( {
listViewRef,
}: ListViewContentPopoverProps ) {
const { popoverProps } = useInspectorPopoverPlacement();
const fills = useSlotFills( LIST_VIEW_CONTENT_PANEL_SLOT );
const hasFills = Boolean( fills && fills.length );
Expand All @@ -58,7 +59,9 @@ export function ListViewContentPopover( { listViewRef } ) {
}, [] );

// Query DOM for the selected block row element in List View.
const [ anchorElement, setAnchorElement ] = useState( null );
const [ anchorElement, setAnchorElement ] = useState< Element | null >(
null
);

useLayoutEffect( () => {
if ( ! selectedClientId || ! listViewRef?.current ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components';
import { forwardRef } from '@wordpress/element';
import warning from '@wordpress/warning';
import type {
ForwardedRef,
ForwardRefExoticComponent,
RefAttributes,
} from 'react';
import BlockSupportToolsPanel from './block-support-tools-panel';
import BlockSupportSlotContainer from './block-support-slot-container';
import groups from './groups';
import type { InspectorControlsSlotProps } from './types';

function InspectorControlsSlot(
{ group = 'default', label, fillProps, ...props },
ref
{
group = 'default',
label,
fillProps,
...props
}: InspectorControlsSlotProps,
ref: ForwardedRef< unknown >
) {
const slotFill = groups[ group ];
const fills = useSlotFills( slotFill?.name );
Expand Down Expand Up @@ -45,4 +56,7 @@ function InspectorControlsSlot(
);
}

export default forwardRef( InspectorControlsSlot );
// `forwardRef` drops the named props of a type with an index signature.
export default forwardRef( InspectorControlsSlot ) as ForwardRefExoticComponent<
InspectorControlsSlotProps & RefAttributes< unknown >
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { ElementType, ReactNode, RefObject } from 'react';
import type { createSlotFill } from '@wordpress/components';
import type { ResetAllContext, ResetAllFilter } from '../../hooks/types';

/**
* A slot and fill pair.
*/
export type SlotFill = ReturnType< typeof createSlotFill >;

/**
* Props of `InspectorControls`.
*/
export interface InspectorControlsFillProps {
children?: ReactNode;
group?: string;
resetAllFilter?: ResetAllFilter;
Comment thread
manzoorwanijk marked this conversation as resolved.
}

/**
* Props of `InspectorControls.Slot`.
*/
export interface InspectorControlsSlotProps {
group?: string;
label?: string;
fillProps?: Record< string, unknown >;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InspectorControls.Slot currently accepts group={42} and label={{ bad: true }} because forwardRef applies Omit to this string index signature and loses the named prop types. Could we type the forwarded slot props explicitly so group, label, and fillProps keep their declared types?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. Fixed in 11d0eaf

[ prop: string ]: unknown;
}

/**
* Context providers, with their props, that a slot forwards to its fills.
*/
export type ForwardedContext = Array<
[ ElementType, Record< string, unknown > ]
>;

/**
* Props of the slot that forwards the tools panel context to its fills.
*/
export interface BlockSupportSlotContainerProps {
Slot: SlotFill[ 'Slot' ];
fillProps?: {
forwardedContext?: ForwardedContext;
[ prop: string ]: unknown;
};
[ prop: string ]: unknown;
}

/**
* A reset-all filter the block support tools panel runs for each block.
*/
export type BlockSupportResetAllFilter = (
attributes: Record< string, unknown >,
context: ResetAllContext
) => Record< string, unknown > | undefined;

/**
* Props of the component that registers a fill's reset-all filter.
*/
export interface RegisterResetAllProps {
resetAllFilter?: ResetAllFilter;
children?: ReactNode;
}

/**
* Props of the fill content rendered inside a block support tools panel.
*/
export interface ToolsPanelInspectorControlProps {
children?: ReactNode;
resetAllFilter?: ResetAllFilter;
fillProps: { forwardedContext?: ForwardedContext };
}

/**
* Props of the tools panel that wraps a labelled InspectorControls slot.
*/
export interface BlockSupportToolsPanelProps {
children?: ReactNode;
group: string;
label: string;
}

/**
* Props of the List View content popover.
*/
export interface ListViewContentPopoverProps {
listViewRef: RefObject< HTMLElement >;
}
21 changes: 17 additions & 4 deletions packages/block-editor/src/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,24 @@ export interface BlockStyleState {
export type BlockStyle = Record< string, unknown >;

/**
* A Tools Panel reset-all filter, returning the attributes to set.
* The block a reset-all filter resets.
*/
export type ResetAllFilter = ( attributes: {
style?: BlockStyle;
} ) => ( Record< string, unknown > & { style?: BlockStyle } ) | undefined;
export interface ResetAllContext {
attributes: Record< string, unknown >;
clientId: string;
name: string | null;
}

/**
* A Tools Panel reset-all filter, returning the attributes to set. The block
* support tools panel passes the block context; state-scoped filters do not.
*/
export type ResetAllFilter = (
attributes: {
style?: BlockStyle;
},
context?: ResetAllContext
) => ( Record< string, unknown > & { style?: BlockStyle } ) | undefined;

/**
* Props a block support's controls receive: the block's identity and the
Expand Down
Loading
Loading