diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 5df10f16b91942..b50e7dce23c905 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -22,6 +22,7 @@ - `BorderControl`: Refactor unit tests to use `userEvent` ([#54155](https://github.com/WordPress/gutenberg/pull/54155)) - `FocusableIframe`: Convert to TypeScript ([#53979](https://github.com/WordPress/gutenberg/pull/53979)). - `Popover`: Remove unused `overlay` type from `positionToPlacement` utility function ([#54101](https://github.com/WordPress/gutenberg/pull/54101)). +- `Higher Order` -- `with-focus-outside`: Convert to TypeScript ([#53980](https://github.com/WordPress/gutenberg/pull/53980)). - `IsolatedEventContainer`: Convert unit test to TypeScript ([#54316](https://github.com/WordPress/gutenberg/pull/54316)). ### Experimental diff --git a/packages/components/src/combobox-control/index.tsx b/packages/components/src/combobox-control/index.tsx index eb8ae89c0971e5..f4292d9980ee41 100644 --- a/packages/components/src/combobox-control/index.tsx +++ b/packages/components/src/combobox-control/index.tsx @@ -36,16 +36,18 @@ import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props' const noop = () => {}; +interface DetectOutsideComponentProps { + onFocusOutside: ( event: React.FocusEvent ) => void; + children?: React.ReactNode; +} + const DetectOutside = withFocusOutside( - class extends Component { - // @ts-expect-error - TODO: Should be resolved when `withFocusOutside` is refactored to TypeScript - handleFocusOutside( event ) { - // @ts-expect-error - TODO: Should be resolved when `withFocusOutside` is refactored to TypeScript + class extends Component< DetectOutsideComponentProps > { + handleFocusOutside( event: React.FocusEvent ) { this.props.onFocusOutside( event ); } render() { - // @ts-expect-error - TODO: Should be resolved when `withFocusOutside` is refactored to TypeScript return this.props.children; } } diff --git a/packages/components/src/higher-order/with-focus-outside/index.js b/packages/components/src/higher-order/with-focus-outside/index.tsx similarity index 79% rename from packages/components/src/higher-order/with-focus-outside/index.js rename to packages/components/src/higher-order/with-focus-outside/index.tsx index 41a7d9c9c3ea47..d340874b8bf136 100644 --- a/packages/components/src/higher-order/with-focus-outside/index.js +++ b/packages/components/src/higher-order/with-focus-outside/index.tsx @@ -1,5 +1,3 @@ -//@ts-nocheck - /** * WordPress dependencies */ @@ -11,9 +9,14 @@ import { export default createHigherOrderComponent( ( WrappedComponent ) => ( props ) => { - const [ handleFocusOutside, setHandleFocusOutside ] = useState(); - const bindFocusOutsideHandler = useCallback( - ( node ) => + const [ handleFocusOutside, setHandleFocusOutside ] = useState< + undefined | ( ( event: React.FocusEvent ) => void ) + >( undefined ); + + const bindFocusOutsideHandler = useCallback< + ( node: React.FocusEvent ) => void + >( + ( node: any ) => setHandleFocusOutside( () => node?.handleFocusOutside ? node.handleFocusOutside.bind( node ) diff --git a/packages/components/src/higher-order/with-focus-outside/test/index.js b/packages/components/src/higher-order/with-focus-outside/test/index.tsx similarity index 85% rename from packages/components/src/higher-order/with-focus-outside/test/index.js rename to packages/components/src/higher-order/with-focus-outside/test/index.tsx index ed0e70f2ec37a6..a53a1cf11298df 100644 --- a/packages/components/src/higher-order/with-focus-outside/test/index.js +++ b/packages/components/src/higher-order/with-focus-outside/test/index.tsx @@ -12,17 +12,21 @@ import { Component } from '@wordpress/element'; /** * Internal dependencies */ -import withFocusOutside from '../'; +import withFocusOutside from '..'; -let onFocusOutside; +interface TestComponentProps { + onFocusOutside: ( event: FocusEvent ) => void; +} + +let onFocusOutside: () => void; describe( 'withFocusOutside', () => { - let origHasFocus; + let origHasFocus: typeof document.hasFocus; const EnhancedComponent = withFocusOutside( - class extends Component { + class extends Component< TestComponentProps > { handleFocusOutside() { - this.props.onFocusOutside(); + this.props.onFocusOutside( new FocusEvent( 'blur' ) ); } render() { @@ -36,15 +40,13 @@ describe( 'withFocusOutside', () => { } ); - class TestComponent extends Component { - render() { - return ; - } - } + const TestComponent: React.FC< TestComponentProps > = ( props ) => { + return ; + }; beforeEach( () => { // Mock document.hasFocus() to always be true for testing - // note: we overide this for some tests. + // note: we override this for some tests. origHasFocus = document.hasFocus; document.hasFocus = () => true; diff --git a/packages/compose/src/hooks/use-focus-outside/index.ts b/packages/compose/src/hooks/use-focus-outside/index.ts index 8d50a078ad70b5..3d85b588a9ab3a 100644 --- a/packages/compose/src/hooks/use-focus-outside/index.ts +++ b/packages/compose/src/hooks/use-focus-outside/index.ts @@ -1,16 +1,3 @@ -/** - * External dependencies - */ -import type { - FocusEventHandler, - EventHandler, - MouseEventHandler, - TouchEventHandler, - FocusEvent, - MouseEvent, - TouchEvent, -} from 'react'; - /** * WordPress dependencies */ @@ -63,12 +50,12 @@ function isFocusNormalizedButton( } type UseFocusOutsideReturn = { - onFocus: FocusEventHandler; - onMouseDown: MouseEventHandler; - onMouseUp: MouseEventHandler; - onTouchStart: TouchEventHandler; - onTouchEnd: TouchEventHandler; - onBlur: FocusEventHandler; + onFocus: React.FocusEventHandler; + onMouseDown: React.MouseEventHandler; + onMouseUp: React.MouseEventHandler; + onTouchStart: React.TouchEventHandler; + onTouchEnd: React.TouchEventHandler; + onBlur: React.FocusEventHandler; }; /** @@ -82,7 +69,7 @@ type UseFocusOutsideReturn = { * wrapping element element to capture when focus moves outside that element. */ export default function useFocusOutside( - onFocusOutside: ( event: FocusEvent ) => void + onFocusOutside: ( ( event: React.FocusEvent ) => void ) | undefined ): UseFocusOutsideReturn { const currentOnFocusOutside = useRef( onFocusOutside ); useEffect( () => { @@ -122,17 +109,18 @@ export default function useFocusOutside( * @param event * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus */ - const normalizeButtonFocus: EventHandler< MouseEvent | TouchEvent > = - useCallback( ( event ) => { - const { type, target } = event; - const isInteractionEnd = [ 'mouseup', 'touchend' ].includes( type ); - - if ( isInteractionEnd ) { - preventBlurCheck.current = false; - } else if ( isFocusNormalizedButton( target ) ) { - preventBlurCheck.current = true; - } - }, [] ); + const normalizeButtonFocus: React.EventHandler< + React.MouseEvent | React.TouchEvent + > = useCallback( ( event ) => { + const { type, target } = event; + const isInteractionEnd = [ 'mouseup', 'touchend' ].includes( type ); + + if ( isInteractionEnd ) { + preventBlurCheck.current = false; + } else if ( isFocusNormalizedButton( target ) ) { + preventBlurCheck.current = true; + } + }, [] ); /** * A callback triggered when a blur event occurs on the element the handler @@ -141,7 +129,7 @@ export default function useFocusOutside( * Calls the `onFocusOutside` callback in an immediate timeout if focus has * move outside the bound element and is still within the document. */ - const queueBlurCheck: FocusEventHandler = useCallback( ( event ) => { + const queueBlurCheck: React.FocusEventHandler = useCallback( ( event ) => { // React does not allow using an event reference asynchronously // due to recycling behavior, except when explicitly persisted. event.persist();