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/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
### Internal

- Update Ariakit to 0.4.41 ([#84539](https://github.com/WordPress/gutenberg/pull/84539)).
- Refactor to TypeScript ([#84611](https://github.com/WordPress/gutenberg/pull/84611)).

## 42.0.0 (2026-10-07)

Expand Down
1 change: 1 addition & 0 deletions packages/components/src/context/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ConnectedProps< TProps > = TProps & { className: string };
Original file line number Diff line number Diff line change
@@ -1,52 +1,45 @@
import type { ReactNode } from 'react';
import warn from '@wordpress/warning';
import { useComponentsContext } from './context-system-provider';
import { getNamespace, getConnectedNamespace } from './utils';
import { getStyledClassNameFromKey } from './get-styled-class-name-from-key';
import { useCx } from '../utils/hooks/use-cx';

/**
* @template TProps
* @typedef {TProps & { className: string }} ConnectedProps
*/
import type { ConnectedProps } from './types';

/**
* Custom hook that derives registered props from the Context system.
* These derived props are then consolidated with incoming component props.
*
* @template {{ className?: string }} P
* @param {P} props Incoming props from the component.
* @param {string} namespace The namespace to register and to derive context props from.
* @return {ConnectedProps<P>} The connected props.
* @param props Incoming props from the component.
* @param namespace The namespace to register and to derive context props from.
* @return The connected props.
*/
export function useContextSystem( props, namespace ) {
export function useContextSystem< P extends { className?: string } >(
props: P,
namespace: string
) {
const contextSystemProps = useComponentsContext();
if ( typeof namespace === 'undefined' ) {
warn( 'useContextSystem: Please provide a namespace' );
}

const contextProps = contextSystemProps?.[ namespace ] || {};

/** @type {ConnectedProps<P>} */
// @ts-expect-error The initial object cannot satisfy the generic `P`, whose props are copied in below.
const finalComponentProps = {
...getConnectedNamespace(),
...getNamespace( namespace ),
};
} as ConnectedProps< P >;

const { _overrides, ...otherContextProps } = contextProps;
/** @type {Record<string, unknown>} */
const overrideProps = /** @type {Record<string, unknown>} */ ( _overrides );
const overrideProps = _overrides as Record< string, unknown >;

const initialMergedProps = Object.entries( otherContextProps ).length
? Object.assign( {}, otherContextProps, props )
: props;
/**
* @type {P & {
* children?: React.ReactNode,
* renderChildren?: (props: P) => React.ReactNode
* }}
*/
const propsWithChildren = initialMergedProps;
const propsWithChildren: P & {
children?: ReactNode;
renderChildren?: ( props: P ) => ReactNode;
} = initialMergedProps;

const cx = useCx();

Expand All @@ -62,20 +55,20 @@ export function useContextSystem( props, namespace ) {
: propsWithChildren.children;

for ( const key in initialMergedProps ) {
// @ts-expect-error A string key cannot index the merged props type.
finalComponentProps[ key ] = initialMergedProps[ key ];
( finalComponentProps as Record< string, unknown > )[ key ] =
initialMergedProps[ key ];
}

for ( const key in overrideProps ) {
// @ts-expect-error A string key cannot index the merged props type.
finalComponentProps[ key ] = overrideProps[ key ];
( finalComponentProps as Record< string, unknown > )[ key ] =
overrideProps[ key ];
}

// Setting an `undefined` explicitly can cause unintended overwrites
// when a `cloneElement()` is involved.
if ( rendered !== undefined ) {
// @ts-expect-error `children` does not exist on `ConnectedProps<P>`.
finalComponentProps.children = rendered;
( finalComponentProps as Record< string, unknown > ).children =
rendered;
}

finalComponentProps.className = classes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { CONNECTED_NAMESPACE, COMPONENT_NAMESPACE } from './constants';
* <div {...ns('Container')} />
* ```
*
* @param {string} componentName The name for the component.
* @return {Record<string, any>} A props object with the namespaced HTML attribute.
* @param componentName The name for the component.
* @return A props object with the namespaced HTML attribute.
*/
export function getNamespace( componentName ) {
export function getNamespace(
componentName: string
): Record< string, unknown > {
return { [ COMPONENT_NAMESPACE ]: componentName };
}

Expand All @@ -25,8 +27,8 @@ export function getNamespace( componentName ) {
* <div {...cns()} />
* ```
*
* @return {Record<string, any>} A props object with the namespaced HTML attribute.
* @return A props object with the namespaced HTML attribute.
*/
export function getConnectedNamespace() {
export function getConnectedNamespace(): Record< string, unknown > {
return { [ CONNECTED_NAMESPACE ]: true };
}
8 changes: 0 additions & 8 deletions packages/components/src/utils/breakpoint.js

This file was deleted.

8 changes: 8 additions & 0 deletions packages/components/src/utils/breakpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import breakpoints from './breakpoint-values';

/**
* @param point
* @return Media query declaration.
*/
export const breakpoint = ( point: keyof typeof breakpoints ) =>
`@media (min-width: ${ breakpoints[ point ] })`;
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ import memoize from 'memize';
import { colord, extend } from 'colord';
import namesPlugin from 'colord/plugins/names';

/** @type {HTMLDivElement} */
let colorComputationNode;
let colorComputationNode: HTMLDivElement;

extend( [ namesPlugin ] );

/**
* Generating a CSS compliant rgba() color value.
*
* @param {string} hexValue The hex value to convert to rgba().
* @param {number} alpha The alpha value for opacity.
* @return {string} The converted rgba() color value.
* @param hexValue The hex value to convert to rgba().
* @param alpha The alpha value for opacity.
* @return The converted rgba() color value.
*
* @example
* rgba( '#000000', 0.5 )
Expand All @@ -23,7 +22,7 @@ export function rgba( hexValue = '', alpha = 1 ) {
}

/**
* @return {HTMLDivElement | undefined} The HTML element for color computation.
* @return The HTML element for color computation.
*/
function getColorComputationNode() {
if ( typeof document === 'undefined' ) {
Expand All @@ -43,11 +42,11 @@ function getColorComputationNode() {
}

/**
* @param {string | unknown} value
* @param value
*
* @return {boolean} Whether the value is a valid color.
* @return Whether the value is a valid color.
*/
function isColor( value ) {
function isColor( value: string | unknown ) {
if ( typeof value !== 'string' ) {
return false;
}
Expand All @@ -60,11 +59,11 @@ function isColor( value ) {
* Retrieves the computed background color. This is useful for getting the
* value of a CSS variable color.
*
* @param {string | unknown} backgroundColor The background color to compute.
* @param backgroundColor The background color to compute.
*
* @return {string} The computed background color.
* @return The computed background color.
*/
function _getComputedBackgroundColor( backgroundColor ) {
function _getComputedBackgroundColor( backgroundColor: string | unknown ) {
if ( typeof backgroundColor !== 'string' ) {
return '';
}
Expand Down Expand Up @@ -100,11 +99,13 @@ const getComputedBackgroundColor = memoize( _getComputedBackgroundColor );
/**
* Get the text shade optimized for readability, based on a background color.
*
* @param {string | unknown} backgroundColor The background color.
* @param backgroundColor The background color.
*
* @return {string} The optimized text color (black or white).
* @return The optimized text color (black or white).
*/
export function getOptimalTextColor( backgroundColor ) {
export function getOptimalTextColor(
backgroundColor: string | unknown
): string {
const background = getComputedBackgroundColor( backgroundColor );

return colord( background ).isLight() ? '#000000' : '#ffffff';
Expand All @@ -113,11 +114,13 @@ export function getOptimalTextColor( backgroundColor ) {
/**
* Get the text shade optimized for readability, based on a background color.
*
* @param {string | unknown} backgroundColor The background color.
* @param backgroundColor The background color.
*
* @return {string} The optimized text shade (dark or light).
* @return The optimized text shade (dark or light).
*/
export function getOptimalTextShade( backgroundColor ) {
export function getOptimalTextShade(
backgroundColor: string | unknown
): string {
const result = getOptimalTextColor( backgroundColor );

return result === '#000000' ? 'dark' : 'light';
Expand Down
10 changes: 0 additions & 10 deletions packages/components/src/utils/font.js

This file was deleted.

10 changes: 10 additions & 0 deletions packages/components/src/utils/font.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import FONT from './font-values';

/**
*
* @param value Path of value from `FONT`
* @return Font rule value
*/
export function font( value: keyof typeof FONT ) {
return FONT[ value ] ?? '';
}
10 changes: 10 additions & 0 deletions packages/components/src/utils/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type ControlledStateOptions< T > = {
/**
* Initial value
*/
initial?: T;
/**
* Fallback value
*/
fallback: T | '';
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { useEffect, useState, useCallback } from '@wordpress/element';
import { isValueDefined, getDefinedValue } from '../values';
import type { ControlledStateOptions } from './types';

/**
* @template T
* @typedef Options
* @property {T} [initial] Initial value
* @property {T | ""} fallback Fallback value
*/

/** @type {Readonly<{ initial: undefined, fallback: '' }>} */
const defaultOptions = {
const defaultOptions: Readonly< { initial: undefined; fallback: '' } > = {
initial: undefined,
/**
* Defaults to empty string, as that is preferred for usage with
Expand All @@ -34,14 +27,15 @@ const defaultOptions = {
* Unlike the basic useState hook, useControlledState's state can
* be updated if a new incoming prop value is changed.
*
* @template T
*
* @param {T | undefined} currentState The current value.
* @param {Options<T>} [options=defaultOptions] Additional options for the hook.
* @param currentState The current value.
* @param [options=defaultOptions] Additional options for the hook.
*
* @return {[T | "", (nextState: T) => void]} The controlled value and the value setter.
* @return The controlled value and the value setter.
*/
function useControlledState( currentState, options = defaultOptions ) {
function useControlledState< T >(
currentState: T | undefined,
options: ControlledStateOptions< T > = defaultOptions
): [ T | '', ( nextState: T ) => void ] {
const { initial, fallback } = { ...defaultOptions, ...options };

const [ internalState, setInternalState ] = useState( currentState );
Expand All @@ -61,9 +55,8 @@ function useControlledState( currentState, options = defaultOptions ) {
fallback
);

/** @type {(nextState: T) => void} */
const setState = useCallback(
( nextState ) => {
( nextState: T ) => {
if ( ! hasCurrentState ) {
setInternalState( nextState );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { DependencyList, EffectCallback } from 'react';
import { useRef, useEffect } from '@wordpress/element';

/**
* A `React.useEffect` that will not run on the first render.
* Source:
* https://github.com/ariakit/ariakit/blob/main/packages/ariakit-react-core/src/utils/hooks.ts
*
* @param {React.EffectCallback} effect
* @param {React.DependencyList} deps
* @param effect
* @param deps
*/
function useUpdateEffect( effect, deps ) {
function useUpdateEffect( effect: EffectCallback, deps: DependencyList ) {
const mountedRef = useRef( false );
useEffect( () => {
if ( mountedRef.current ) {
Expand Down
Loading
Loading