Skip to content

Commit

Permalink
with-focus-outside: Convert to TypeScript (#53980)
Browse files Browse the repository at this point in the history
* Rename and nocheck removal

* Pass on typing with-focus-outside.

* Removes ts-ignore in ComboboxControl

* Moves components back to inside test and makes TestComponent more similar to previous implementation.

* Updates changelog

* Replaces FocusEvent with React.FocusEvent

* FocusEvent to React.FocusEvent in combobox-control

* Imported Event to React.XXX namespaced types in use-focus-outside

---------

Co-authored-by: Marco Ciampini <[email protected]>
  • Loading branch information
margolisj and ciampo authored Sep 11, 2023
1 parent 76b0165 commit 65bf4f7
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 53 deletions.
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions packages/components/src/combobox-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//@ts-nocheck

/**
* WordPress dependencies
*/
Expand All @@ -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 )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -36,15 +40,13 @@ describe( 'withFocusOutside', () => {
}
);

class TestComponent extends Component {
render() {
return <EnhancedComponent { ...this.props } />;
}
}
const TestComponent: React.FC< TestComponentProps > = ( props ) => {
return <EnhancedComponent { ...props } />;
};

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;

Expand Down
52 changes: 20 additions & 32 deletions packages/compose/src/hooks/use-focus-outside/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
/**
* External dependencies
*/
import type {
FocusEventHandler,
EventHandler,
MouseEventHandler,
TouchEventHandler,
FocusEvent,
MouseEvent,
TouchEvent,
} from 'react';

/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -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;
};

/**
Expand All @@ -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( () => {
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand Down

1 comment on commit 65bf4f7

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 65bf4f7.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6144129592
📝 Reported issues:

Please sign in to comment.