diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 97fe6332529434..114012fe507d97 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -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)
diff --git a/packages/components/src/context/constants.js b/packages/components/src/context/constants.ts
similarity index 100%
rename from packages/components/src/context/constants.js
rename to packages/components/src/context/constants.ts
diff --git a/packages/components/src/context/types.ts b/packages/components/src/context/types.ts
new file mode 100644
index 00000000000000..c101e4aae08cb7
--- /dev/null
+++ b/packages/components/src/context/types.ts
@@ -0,0 +1 @@
+export type ConnectedProps< TProps > = TProps & { className: string };
diff --git a/packages/components/src/context/use-context-system.js b/packages/components/src/context/use-context-system.ts
similarity index 57%
rename from packages/components/src/context/use-context-system.js
rename to packages/components/src/context/use-context-system.ts
index ae5705c69c9542..a44d640b8d843b 100644
--- a/packages/components/src/context/use-context-system.js
+++ b/packages/components/src/context/use-context-system.ts
@@ -1,24 +1,23 @@
+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
} 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' );
@@ -26,27 +25,21 @@ export function useContextSystem( props, namespace ) {
const contextProps = contextSystemProps?.[ namespace ] || {};
- /** @type {ConnectedProps
} */
- // @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} */
- const overrideProps = /** @type {Record} */ ( _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();
@@ -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`.
- finalComponentProps.children = rendered;
+ ( finalComponentProps as Record< string, unknown > ).children =
+ rendered;
}
finalComponentProps.className = classes;
diff --git a/packages/components/src/context/utils.js b/packages/components/src/context/utils.ts
similarity index 61%
rename from packages/components/src/context/utils.js
rename to packages/components/src/context/utils.ts
index dec56166e1c78e..7bd09c372835f4 100644
--- a/packages/components/src/context/utils.js
+++ b/packages/components/src/context/utils.ts
@@ -9,10 +9,12 @@ import { CONNECTED_NAMESPACE, COMPONENT_NAMESPACE } from './constants';
*
* ```
*
- * @param {string} componentName The name for the component.
- * @return {Record} 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 };
}
@@ -25,8 +27,8 @@ export function getNamespace( componentName ) {
*
* ```
*
- * @return {Record} 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 };
}
diff --git a/packages/components/src/lock-unlock.js b/packages/components/src/lock-unlock.ts
similarity index 100%
rename from packages/components/src/lock-unlock.js
rename to packages/components/src/lock-unlock.ts
diff --git a/packages/components/src/toolbar/index.js b/packages/components/src/toolbar/index.ts
similarity index 100%
rename from packages/components/src/toolbar/index.js
rename to packages/components/src/toolbar/index.ts
diff --git a/packages/components/src/utils/breakpoint-values.js b/packages/components/src/utils/breakpoint-values.ts
similarity index 100%
rename from packages/components/src/utils/breakpoint-values.js
rename to packages/components/src/utils/breakpoint-values.ts
diff --git a/packages/components/src/utils/breakpoint.js b/packages/components/src/utils/breakpoint.js
deleted file mode 100644
index 8cb496d2fe4ddb..00000000000000
--- a/packages/components/src/utils/breakpoint.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import breakpoints from './breakpoint-values';
-
-/**
- * @param {keyof typeof breakpoints} point
- * @return {string} Media query declaration.
- */
-export const breakpoint = ( point ) =>
- `@media (min-width: ${ breakpoints[ point ] })`;
diff --git a/packages/components/src/utils/breakpoint.ts b/packages/components/src/utils/breakpoint.ts
new file mode 100644
index 00000000000000..ff337fce1e207c
--- /dev/null
+++ b/packages/components/src/utils/breakpoint.ts
@@ -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 ] })`;
diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.ts
similarity index 100%
rename from packages/components/src/utils/colors-values.js
rename to packages/components/src/utils/colors-values.ts
diff --git a/packages/components/src/utils/colors.js b/packages/components/src/utils/colors.ts
similarity index 68%
rename from packages/components/src/utils/colors.js
rename to packages/components/src/utils/colors.ts
index 2f7b2dc0c74103..514fe1fafca730 100644
--- a/packages/components/src/utils/colors.js
+++ b/packages/components/src/utils/colors.ts
@@ -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 )
@@ -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' ) {
@@ -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;
}
@@ -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 '';
}
@@ -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';
@@ -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';
diff --git a/packages/components/src/utils/config-values.js b/packages/components/src/utils/config-values.ts
similarity index 100%
rename from packages/components/src/utils/config-values.js
rename to packages/components/src/utils/config-values.ts
diff --git a/packages/components/src/utils/font-values.js b/packages/components/src/utils/font-values.ts
similarity index 100%
rename from packages/components/src/utils/font-values.js
rename to packages/components/src/utils/font-values.ts
diff --git a/packages/components/src/utils/font.js b/packages/components/src/utils/font.js
deleted file mode 100644
index 1f23ce85525ea2..00000000000000
--- a/packages/components/src/utils/font.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import FONT from './font-values';
-
-/**
- *
- * @param {keyof typeof FONT} value Path of value from `FONT`
- * @return {string} Font rule value
- */
-export function font( value ) {
- return FONT[ value ] ?? '';
-}
diff --git a/packages/components/src/utils/font.ts b/packages/components/src/utils/font.ts
new file mode 100644
index 00000000000000..cfd3f438c4b50c
--- /dev/null
+++ b/packages/components/src/utils/font.ts
@@ -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 ] ?? '';
+}
diff --git a/packages/components/src/utils/hooks/index.js b/packages/components/src/utils/hooks/index.ts
similarity index 100%
rename from packages/components/src/utils/hooks/index.js
rename to packages/components/src/utils/hooks/index.ts
diff --git a/packages/components/src/utils/hooks/types.ts b/packages/components/src/utils/hooks/types.ts
new file mode 100644
index 00000000000000..243a44c01e8e18
--- /dev/null
+++ b/packages/components/src/utils/hooks/types.ts
@@ -0,0 +1,10 @@
+export type ControlledStateOptions< T > = {
+ /**
+ * Initial value
+ */
+ initial?: T;
+ /**
+ * Fallback value
+ */
+ fallback: T | '';
+};
diff --git a/packages/components/src/utils/hooks/use-controlled-state.js b/packages/components/src/utils/hooks/use-controlled-state.ts
similarity index 73%
rename from packages/components/src/utils/hooks/use-controlled-state.js
rename to packages/components/src/utils/hooks/use-controlled-state.ts
index b7b1b1516c61ef..7fdb34f646d1b7 100644
--- a/packages/components/src/utils/hooks/use-controlled-state.js
+++ b/packages/components/src/utils/hooks/use-controlled-state.ts
@@ -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
@@ -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} [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 );
@@ -61,9 +55,8 @@ function useControlledState( currentState, options = defaultOptions ) {
fallback
);
- /** @type {(nextState: T) => void} */
const setState = useCallback(
- ( nextState ) => {
+ ( nextState: T ) => {
if ( ! hasCurrentState ) {
setInternalState( nextState );
}
diff --git a/packages/components/src/utils/hooks/use-update-effect.js b/packages/components/src/utils/hooks/use-update-effect.ts
similarity index 81%
rename from packages/components/src/utils/hooks/use-update-effect.js
rename to packages/components/src/utils/hooks/use-update-effect.ts
index c0f3b73b14a956..26da4fabab4763 100644
--- a/packages/components/src/utils/hooks/use-update-effect.js
+++ b/packages/components/src/utils/hooks/use-update-effect.ts
@@ -1,3 +1,4 @@
+import type { DependencyList, EffectCallback } from 'react';
import { useRef, useEffect } from '@wordpress/element';
/**
@@ -5,10 +6,10 @@ import { useRef, useEffect } from '@wordpress/element';
* 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 ) {
diff --git a/packages/components/src/utils/index.js b/packages/components/src/utils/index.ts
similarity index 100%
rename from packages/components/src/utils/index.js
rename to packages/components/src/utils/index.ts
diff --git a/packages/components/src/utils/math.js b/packages/components/src/utils/math.js
deleted file mode 100644
index 1c7321c85a3d1f..00000000000000
--- a/packages/components/src/utils/math.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Parses and retrieves a number value.
- *
- * @param {unknown} value The incoming value.
- *
- * @return {number} The parsed number value.
- */
-export function getNumber( value ) {
- const number = Number( value );
-
- return isNaN( number ) ? 0 : number;
-}
-
-/**
- * Safely adds 2 values.
- *
- * @param {Array} args Values to add together.
- *
- * @return {number} The sum of values.
- */
-export function add( ...args ) {
- return args.reduce(
- /** @type {(sum:number, arg: number|string) => number} */
- ( sum, arg ) => sum + getNumber( arg ),
- 0
- );
-}
-
-/**
- * Safely subtracts 2 values.
- *
- * @param {Array} args Values to subtract together.
- *
- * @return {number} The difference of the values.
- */
-export function subtract( ...args ) {
- return args.reduce(
- /** @type {(diff:number, arg: number|string, index:number) => number} */
- ( diff, arg, index ) => {
- const value = getNumber( arg );
- return index === 0 ? value : diff - value;
- },
- 0
- );
-}
-
-/**
- * Determines the decimal position of a number value.
- *
- * @param {number} value The number to evaluate.
- *
- * @return {number} The number of decimal places.
- */
-function getPrecision( value ) {
- const split = ( value + '' ).split( '.' );
- return split[ 1 ] !== undefined ? split[ 1 ].length : 0;
-}
-
-/**
- * Clamps a value based on a min/max range.
- *
- * @param {number|string} value The value.
- * @param {number} min The minimum range.
- * @param {number} max The maximum range.
- *
- * @return {number} The clamped value.
- */
-export function clamp( value, min, max ) {
- const baseValue = getNumber( value );
- return Math.max( min, Math.min( baseValue, max ) );
-}
-
-/**
- * Rounds a value to the nearest step offset by a minimum.
- *
- * @param {number|string} value The value.
- * @param {number} min The minimum range.
- * @param {number} step The increment for the value.
- *
- * @return {number} The value as a valid step.
- */
-export function ensureValidStep( value, min, step ) {
- const baseValue = getNumber( value );
- const minValue = getNumber( min );
- const stepValue = getNumber( step );
- const precision = Math.max( getPrecision( step ), getPrecision( min ) );
- // If the step is not a factor of the minimum then the step must be
- // offset by the minimum.
- const tare = minValue % stepValue ? minValue : 0;
- const rounded = Math.round( ( baseValue - tare ) / stepValue ) * stepValue;
- const fromMin = rounded + tare;
- return precision ? getNumber( fromMin.toFixed( precision ) ) : fromMin;
-}
diff --git a/packages/components/src/utils/math.ts b/packages/components/src/utils/math.ts
new file mode 100644
index 00000000000000..e93fc9d8f4fb68
--- /dev/null
+++ b/packages/components/src/utils/math.ts
@@ -0,0 +1,89 @@
+/**
+ * Parses and retrieves a number value.
+ *
+ * @param value The incoming value.
+ *
+ * @return The parsed number value.
+ */
+export function getNumber( value: unknown ) {
+ const number = Number( value );
+
+ return isNaN( number ) ? 0 : number;
+}
+
+/**
+ * Safely adds 2 values.
+ *
+ * @param args Values to add together.
+ *
+ * @return The sum of values.
+ */
+export function add( ...args: Array< number | string > ) {
+ return args.reduce< number >( ( sum, arg ) => sum + getNumber( arg ), 0 );
+}
+
+/**
+ * Safely subtracts 2 values.
+ *
+ * @param args Values to subtract together.
+ *
+ * @return The difference of the values.
+ */
+export function subtract( ...args: Array< number | string > ) {
+ return args.reduce< number >( ( diff, arg, index ) => {
+ const value = getNumber( arg );
+ return index === 0 ? value : diff - value;
+ }, 0 );
+}
+
+/**
+ * Determines the decimal position of a number value.
+ *
+ * @param value The number to evaluate.
+ *
+ * @return The number of decimal places.
+ */
+function getPrecision( value: number ) {
+ const split = ( value + '' ).split( '.' );
+ return split[ 1 ] !== undefined ? split[ 1 ].length : 0;
+}
+
+/**
+ * Clamps a value based on a min/max range.
+ *
+ * @param value The value.
+ * @param min The minimum range.
+ * @param max The maximum range.
+ *
+ * @return The clamped value.
+ */
+export function clamp( value: number | string, min: number, max: number ) {
+ const baseValue = getNumber( value );
+ return Math.max( min, Math.min( baseValue, max ) );
+}
+
+/**
+ * Rounds a value to the nearest step offset by a minimum.
+ *
+ * @param value The value.
+ * @param min The minimum range.
+ * @param step The increment for the value.
+ *
+ * @return The value as a valid step.
+ */
+export function ensureValidStep(
+ value: number | string,
+ min: number,
+ step: number
+) {
+ const baseValue = getNumber( value );
+ const minValue = getNumber( min );
+ const stepValue = getNumber( step );
+ const precision = Math.max( getPrecision( step ), getPrecision( min ) );
+ // If the step is not a factor of the minimum then the step must be
+ // offset by the minimum.
+ const tare = minValue % stepValue ? minValue : 0;
+ const rounded = Math.round( ( baseValue - tare ) / stepValue ) * stepValue;
+ const fromMin = rounded + tare;
+ return precision ? getNumber( fromMin.toFixed( precision ) ) : fromMin;
+}
diff --git a/packages/components/src/utils/rtl.js b/packages/components/src/utils/rtl.ts
similarity index 59%
rename from packages/components/src/utils/rtl.js
rename to packages/components/src/utils/rtl.ts
index 95c48fa4765f5c..11d59c41083c86 100644
--- a/packages/components/src/utils/rtl.js
+++ b/packages/components/src/utils/rtl.ts
@@ -1,4 +1,5 @@
-import { css } from '@emotion/react';
+import { css, type CSSObject } from '@emotion/react';
+import type { CSSProperties } from 'react';
import { isRTL } from '@wordpress/i18n';
const LOWER_LEFT_REGEXP = new RegExp( /-left/g );
@@ -9,11 +10,11 @@ const UPPER_RIGHT_REGEXP = new RegExp( /Right/g );
/**
* Flips a CSS property from left <-> right.
*
- * @param {string} key The CSS property name.
+ * @param key The CSS property name.
*
- * @return {string} The flipped CSS property name, if applicable.
+ * @return The flipped CSS property name, if applicable.
*/
-function getConvertedKey( key ) {
+function getConvertedKey( key: string ) {
if ( key === 'left' ) {
return 'right';
}
@@ -44,11 +45,13 @@ function getConvertedKey( key ) {
/**
* An incredibly basic ltr -> rtl converter for style properties
*
- * @param {React.CSSProperties} ltrStyles
+ * @param ltrStyles
*
- * @return {React.CSSProperties} Converted ltr -> rtl styles
+ * @return Converted ltr -> rtl styles
*/
-export const convertLTRToRTL = ( ltrStyles = {} ) => {
+export const convertLTRToRTL = (
+ ltrStyles: CSSProperties = {}
+): CSSProperties => {
return Object.fromEntries(
Object.entries( ltrStyles ).map( ( [ key, value ] ) => [
getConvertedKey( key ),
@@ -60,20 +63,25 @@ export const convertLTRToRTL = ( ltrStyles = {} ) => {
/**
* A higher-order function that create an incredibly basic ltr -> rtl style converter for CSS objects.
*
- * @param {React.CSSProperties} ltrStyles Ltr styles. Converts and renders from ltr -> rtl styles, if applicable.
- * @param {React.CSSProperties} [rtlStyles] Rtl styles. Renders if provided.
+ * @param ltrStyles Ltr styles. Converts and renders from ltr -> rtl styles, if applicable.
+ * @param [rtlStyles] Rtl styles. Renders if provided.
*
- * @return {() => import('@emotion/react').SerializedStyles} A function to output CSS styles for Emotion's renderer
+ * @return A function to output CSS styles for Emotion's renderer
*/
-export function rtl( ltrStyles = {}, rtlStyles ) {
+export function rtl(
+ ltrStyles: CSSProperties = {},
+ rtlStyles?: CSSProperties
+) {
return () => {
if ( rtlStyles ) {
- // @ts-expect-error `React.CSSProperties` lacks the string index signature Emotion's `CSSObject` requires.
- return isRTL() ? css( rtlStyles ) : css( ltrStyles );
+ return isRTL()
+ ? css( rtlStyles as CSSObject )
+ : css( ltrStyles as CSSObject );
}
- // @ts-expect-error `React.CSSProperties` lacks the string index signature Emotion's `CSSObject` requires.
- return isRTL() ? css( convertLTRToRTL( ltrStyles ) ) : css( ltrStyles );
+ return isRTL()
+ ? css( convertLTRToRTL( ltrStyles ) as CSSObject )
+ : css( ltrStyles as CSSObject );
};
}
diff --git a/packages/components/src/utils/style-mixins.js b/packages/components/src/utils/style-mixins.ts
similarity index 100%
rename from packages/components/src/utils/style-mixins.js
rename to packages/components/src/utils/style-mixins.ts
diff --git a/packages/components/src/utils/test/colors.js b/packages/components/src/utils/test/colors.ts
similarity index 100%
rename from packages/components/src/utils/test/colors.js
rename to packages/components/src/utils/test/colors.ts
diff --git a/packages/components/src/utils/test/math.js b/packages/components/src/utils/test/math.ts
similarity index 87%
rename from packages/components/src/utils/test/math.js
rename to packages/components/src/utils/test/math.ts
index 34d52f2471f9aa..baa77b0ebb430a 100644
--- a/packages/components/src/utils/test/math.js
+++ b/packages/components/src/utils/test/math.ts
@@ -52,15 +52,36 @@ describe( 'clamp', () => {
it( 'should clamp number or string values', () => {
expect( clamp( '50', 1, 10 ) ).toBe( 10 );
expect( clamp( '50', -10, 10 ) ).toBe( 10 );
- expect( clamp( -50, -10, '10' ) ).toBe( -10 );
+ expect(
+ clamp(
+ -50,
+ -10,
+ // @ts-expect-error `max` is typed as a number but coerced at runtime.
+ '10'
+ )
+ ).toBe( -10 );
} );
} );
describe( 'ensureValidStep', () => {
it( 'should work with number or string values', () => {
expect( ensureValidStep( '49', 0, 10 ) ).toBe( 50 );
- expect( ensureValidStep( 49, '0', 10 ) ).toBe( 50 );
- expect( ensureValidStep( 49, 0, '10' ) ).toBe( 50 );
+ expect(
+ ensureValidStep(
+ 49,
+ // @ts-expect-error `min` is typed as a number but coerced at runtime.
+ '0',
+ 10
+ )
+ ).toBe( 50 );
+ expect(
+ ensureValidStep(
+ 49,
+ 0,
+ // @ts-expect-error `step` is typed as a number but coerced at runtime.
+ '10'
+ )
+ ).toBe( 50 );
} );
it( 'should round to step', () => {
diff --git a/packages/components/src/utils/test/rtl.js b/packages/components/src/utils/test/rtl.ts
similarity index 94%
rename from packages/components/src/utils/test/rtl.js
rename to packages/components/src/utils/test/rtl.ts
index a472242f9d93ff..698a2248875d85 100644
--- a/packages/components/src/utils/test/rtl.js
+++ b/packages/components/src/utils/test/rtl.ts
@@ -1,9 +1,10 @@
import { describe, expect, it } from 'vitest';
+import type { CSSProperties } from 'react';
import { convertLTRToRTL } from '../rtl';
describe( 'convertLTRToRTL', () => {
it( 'converts (*)Left <-> (*)Right', () => {
- const style = {
+ const style: CSSProperties = {
// Left values.
borderLeft: '10px solid red',
borderLeftColor: 'red',
@@ -86,7 +87,8 @@ describe( 'convertLTRToRTL', () => {
// Edge cases.
'text-combine-upright': 'none',
};
- const nextStyle = convertLTRToRTL( style );
+ // Kebab-case keys are not in `CSSProperties`, but pass through at runtime.
+ const nextStyle = convertLTRToRTL( style ) as Record< string, unknown >;
expect( Object.keys( style ).length ).toBe(
Object.keys( nextStyle ).length
diff --git a/packages/components/src/utils/test/space.js b/packages/components/src/utils/test/space.ts
similarity index 100%
rename from packages/components/src/utils/test/space.js
rename to packages/components/src/utils/test/space.ts
diff --git a/packages/components/src/utils/test/strings.js b/packages/components/src/utils/test/strings.ts
similarity index 100%
rename from packages/components/src/utils/test/strings.js
rename to packages/components/src/utils/test/strings.ts
diff --git a/packages/components/src/utils/test/unit-values.js b/packages/components/src/utils/test/unit-values.ts
similarity index 100%
rename from packages/components/src/utils/test/unit-values.js
rename to packages/components/src/utils/test/unit-values.ts
diff --git a/packages/components/src/utils/values.js b/packages/components/src/utils/values.js
deleted file mode 100644
index c55a3ce973e5c0..00000000000000
--- a/packages/components/src/utils/values.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Determines if a value is null or undefined.
- *
- * @template T
- *
- * @param {T} value The value to check.
- * @return {value is Exclude} Whether value is not null or undefined.
- */
-export function isValueDefined( value ) {
- return value !== undefined && value !== null;
-}
-
-/**
- * Determines if a value is empty, null, or undefined.
- *
- * @param {string | number | null | undefined} value The value to check.
- * @return {value is ("" | null | undefined)} Whether value is empty.
- */
-export function isValueEmpty( value ) {
- const isEmptyString = value === '';
-
- return ! isValueDefined( value ) || isEmptyString;
-}
-
-/**
- * Get the first defined/non-null value from an array.
- *
- * @template T
- *
- * @param {Array} values Values to derive from.
- * @param {T} fallbackValue Fallback value if there are no defined values.
- * @return {T} A defined value or the fallback value.
- */
-export function getDefinedValue( values = [], fallbackValue ) {
- return values.find( isValueDefined ) ?? fallbackValue;
-}
-
-/**
- * Converts a string to a number.
- *
- * @param {string} value
- * @return {number} String as a number.
- */
-export const stringToNumber = ( value ) => {
- return parseFloat( value );
-};
-
-/**
- * Regardless of the input being a string or a number, returns a number.
- *
- * Returns `undefined` in case the string is `undefined` or not a valid numeric value.
- *
- * @param {string | number} value
- * @return {number} The parsed number.
- */
-export const ensureNumber = ( value ) => {
- return typeof value === 'string' ? stringToNumber( value ) : value;
-};
diff --git a/packages/components/src/utils/values.ts b/packages/components/src/utils/values.ts
new file mode 100644
index 00000000000000..5010173a46c07b
--- /dev/null
+++ b/packages/components/src/utils/values.ts
@@ -0,0 +1,61 @@
+/**
+ * Determines if a value is null or undefined.
+ *
+ * @param value The value to check.
+ * @return Whether value is not null or undefined.
+ */
+export function isValueDefined< T >(
+ value: T
+): value is Exclude< T, null | undefined > {
+ return value !== undefined && value !== null;
+}
+
+/**
+ * Determines if a value is empty, null, or undefined.
+ *
+ * @param value The value to check.
+ * @return Whether value is empty.
+ */
+export function isValueEmpty(
+ value: string | number | null | undefined
+): value is '' | null | undefined {
+ const isEmptyString = value === '';
+
+ return ! isValueDefined( value ) || isEmptyString;
+}
+
+/**
+ * Get the first defined/non-null value from an array.
+ *
+ * @param values Values to derive from.
+ * @param fallbackValue Fallback value if there are no defined values.
+ * @return A defined value or the fallback value.
+ */
+export function getDefinedValue< T >(
+ values: Array< T | null | undefined > = [],
+ fallbackValue: T
+) {
+ return values.find( isValueDefined ) ?? fallbackValue;
+}
+
+/**
+ * Converts a string to a number.
+ *
+ * @param value
+ * @return String as a number.
+ */
+export const stringToNumber = ( value: string ) => {
+ return parseFloat( value );
+};
+
+/**
+ * Regardless of the input being a string or a number, returns a number.
+ *
+ * Returns `undefined` in case the string is `undefined` or not a valid numeric value.
+ *
+ * @param value
+ * @return The parsed number.
+ */
+export const ensureNumber = ( value: string | number ) => {
+ return typeof value === 'string' ? stringToNumber( value ) : value;
+};
diff --git a/tools/eslint/config.mjs b/tools/eslint/config.mjs
index 9613142b56b311..5eaaca97e12b3c 100644
--- a/tools/eslint/config.mjs
+++ b/tools/eslint/config.mjs
@@ -851,7 +851,7 @@ export default dedupePlugins( [
// Override: Components src — restrict admin theme and components color vars.
{
files: [ 'packages/components/src/**' ],
- ignores: [ 'packages/components/src/utils/colors-values.js' ],
+ ignores: [ 'packages/components/src/utils/colors-values.ts' ],
rules: {
'no-restricted-syntax': [
'error',
@@ -860,14 +860,14 @@ export default dedupePlugins( [
selector:
':matches(Literal[value=/--wp-admin-theme-/],TemplateElement[value.cooked=/--wp-admin-theme-/])',
message:
- '--wp-admin-theme-* variables do not support component theming. Use variables from the COLORS object in packages/components/src/utils/colors-values.js instead.',
+ '--wp-admin-theme-* variables do not support component theming. Use variables from the COLORS object in packages/components/src/utils/colors-values.ts instead.',
},
{
selector:
// Allow overriding definitions, but not access with var()
':matches(Literal[value=/var\\(\\s*--wp-components-color-/],TemplateElement[value.cooked=/var\\(\\s*--wp-components-color-/])',
message:
- 'To ensure proper fallbacks, --wp-components-color-* variables should not be used directly. Use variables from the COLORS object in packages/components/src/utils/colors-values.js instead.',
+ 'To ensure proper fallbacks, --wp-components-color-* variables should not be used directly. Use variables from the COLORS object in packages/components/src/utils/colors-values.ts instead.',
},
],
},
diff --git a/tools/eslint/suppressions.json b/tools/eslint/suppressions.json
index d482a47121e065..ca5c424a05508f 100644
--- a/tools/eslint/suppressions.json
+++ b/tools/eslint/suppressions.json
@@ -4859,7 +4859,7 @@
"count": 4
}
},
- "packages/components/src/utils/rtl.js": {
+ "packages/components/src/utils/rtl.ts": {
"no-restricted-imports": {
"count": 1
}