UNPKG

2.94 kBPlain TextView Raw
1/*
2 * Copyright 2020 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13// Portions of the code in this file are based on code from react.
14// Original licensing for the following can be found in the
15// NOTICE file in the root directory of this source tree.
16// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
17
18import {FocusEvent, HTMLAttributes, useRef} from 'react';
19import {useSyntheticBlurEvent} from './utils';
20
21interface FocusWithinProps {
22 /** Whether the focus within events should be disabled. */
23 isDisabled?: boolean,
24 /** Handler that is called when the target element or a descendant receives focus. */
25 onFocusWithin?: (e: FocusEvent) => void,
26 /** Handler that is called when the target element and all descendants lose focus. */
27 onBlurWithin?: (e: FocusEvent) => void,
28 /** Handler that is called when the the focus within state changes. */
29 onFocusWithinChange?: (isFocusWithin: boolean) => void
30}
31
32interface FocusWithinResult {
33 /** Props to spread onto the target element. */
34 focusWithinProps: HTMLAttributes<HTMLElement>
35}
36
37/**
38 * Handles focus events for the target and its descendants.
39 */
40export function useFocusWithin(props: FocusWithinProps): FocusWithinResult {
41 let state = useRef({
42 isFocusWithin: false
43 }).current;
44
45 let onBlur = props.isDisabled ? null : (e: FocusEvent) => {
46 // We don't want to trigger onBlurWithin and then immediately onFocusWithin again
47 // when moving focus inside the element. Only trigger if the currentTarget doesn't
48 // include the relatedTarget (where focus is moving).
49 if (state.isFocusWithin && !(e.currentTarget as Element).contains(e.relatedTarget as Element)) {
50 state.isFocusWithin = false;
51
52 if (props.onBlurWithin) {
53 props.onBlurWithin(e);
54 }
55
56 if (props.onFocusWithinChange) {
57 props.onFocusWithinChange(false);
58 }
59 }
60 };
61
62 let onSyntheticFocus = useSyntheticBlurEvent(onBlur);
63 let onFocus = props.isDisabled ? null : (e: FocusEvent) => {
64 if (!state.isFocusWithin) {
65 if (props.onFocusWithin) {
66 props.onFocusWithin(e);
67 }
68
69 if (props.onFocusWithinChange) {
70 props.onFocusWithinChange(true);
71 }
72
73 state.isFocusWithin = true;
74 onSyntheticFocus(e);
75 }
76 };
77
78 return {
79 focusWithinProps: {
80 onFocus,
81 onBlur
82 }
83 };
84}