Last
Some checks are pending
Deploy Volleyball CMS / deploy (push) Waiting to run

This commit is contained in:
2025-06-02 18:56:22 +02:00
parent 8f62885a45
commit 33181acf83
1443 changed files with 286102 additions and 12 deletions

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {FocusableElement} from '@react-types/shared';
import {mergeProps, useObjectRef, useSyncRef} from '@react-aria/utils';
import {PressProps} from './usePress';
import {PressResponderContext} from './context';
import React, {ForwardedRef, JSX, ReactNode, useContext, useEffect, useMemo, useRef} from 'react';
interface PressResponderProps extends PressProps {
children: ReactNode
}
export const PressResponder = React.forwardRef(({children, ...props}: PressResponderProps, ref: ForwardedRef<FocusableElement>) => {
let isRegistered = useRef(false);
let prevContext = useContext(PressResponderContext);
ref = useObjectRef(ref || prevContext?.ref);
let context = mergeProps(prevContext || {}, {
...props,
ref,
register() {
isRegistered.current = true;
if (prevContext) {
prevContext.register();
}
}
});
useSyncRef(prevContext, ref);
useEffect(() => {
if (!isRegistered.current) {
if (process.env.NODE_ENV !== 'production') {
console.warn(
'A PressResponder was rendered without a pressable child. ' +
'Either call the usePress hook, or wrap your DOM node with <Pressable> component.'
);
}
isRegistered.current = true; // only warn once in strict mode.
}
}, []);
return (
<PressResponderContext.Provider value={context}>
{children}
</PressResponderContext.Provider>
);
});
export function ClearPressResponder({children}: {children: ReactNode}): JSX.Element {
let context = useMemo(() => ({register: () => {}}), []);
return (
<PressResponderContext.Provider value={context}>
{children}
</PressResponderContext.Provider>
);
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {DOMAttributes, FocusableElement} from '@react-types/shared';
import {getOwnerWindow, isFocusable, mergeProps, mergeRefs, useObjectRef} from '@react-aria/utils';
import {PressProps, usePress} from './usePress';
import React, {ForwardedRef, ReactElement, useEffect} from 'react';
import {useFocusable} from './useFocusable';
interface PressableProps extends PressProps {
children: ReactElement<DOMAttributes, string>
}
export const Pressable = React.forwardRef(({children, ...props}: PressableProps, ref: ForwardedRef<FocusableElement>) => {
ref = useObjectRef(ref);
let {pressProps} = usePress({...props, ref});
let {focusableProps} = useFocusable(props, ref);
let child = React.Children.only(children);
useEffect(() => {
if (process.env.NODE_ENV === 'production') {
return;
}
let el = ref.current;
if (!el || !(el instanceof getOwnerWindow(el).Element)) {
console.error('<Pressable> child must forward its ref to a DOM element.');
return;
}
if (!isFocusable(el)) {
console.warn('<Pressable> child must be focusable. Please ensure the tabIndex prop is passed through.');
return;
}
if (
el.localName !== 'button' &&
el.localName !== 'input' &&
el.localName !== 'select' &&
el.localName !== 'textarea' &&
el.localName !== 'a' &&
el.localName !== 'area' &&
el.localName !== 'summary'
) {
let role = el.getAttribute('role');
if (!role) {
console.warn('<Pressable> child must have an interactive ARIA role.');
} else if (
// https://w3c.github.io/aria/#widget_roles
role !== 'application' &&
role !== 'button' &&
role !== 'checkbox' &&
role !== 'combobox' &&
role !== 'gridcell' &&
role !== 'link' &&
role !== 'menuitem' &&
role !== 'menuitemcheckbox' &&
role !== 'menuitemradio' &&
role !== 'option' &&
role !== 'radio' &&
role !== 'searchbox' &&
role !== 'separator' &&
role !== 'slider' &&
role !== 'spinbutton' &&
role !== 'switch' &&
role !== 'tab' &&
role !== 'textbox' &&
role !== 'treeitem'
) {
console.warn(`<Pressable> child must have an interactive ARIA role. Got "${role}".`);
}
}
}, [ref]);
// @ts-ignore
let childRef = parseInt(React.version, 10) < 19 ? child.ref : child.props.ref;
return React.cloneElement(
child,
{
...mergeProps(pressProps, focusableProps, child.props),
// @ts-ignore
ref: mergeRefs(childRef, ref)
}
);
});

23
node_modules/@react-aria/interactions/src/context.ts generated vendored Normal file
View File

@@ -0,0 +1,23 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {FocusableElement} from '@react-types/shared';
import {PressProps} from './usePress';
import React, {MutableRefObject} from 'react';
interface IPressResponderContext extends PressProps {
register(): void,
ref?: MutableRefObject<FocusableElement>
}
export const PressResponderContext = React.createContext<IPressResponderContext>({register: () => {}});
PressResponderContext.displayName = 'PressResponderContext';

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {BaseEvent} from '@react-types/shared';
import {SyntheticEvent} from 'react';
/**
* This function wraps a React event handler to make stopPropagation the default, and support continuePropagation instead.
*/
export function createEventHandler<T extends SyntheticEvent>(handler?: (e: BaseEvent<T>) => void): ((e: T) => void) | undefined {
if (!handler) {
return undefined;
}
let shouldStopPropagation = true;
return (e: T) => {
let event: BaseEvent<T> = {
...e,
preventDefault() {
e.preventDefault();
},
isDefaultPrevented() {
return e.isDefaultPrevented();
},
stopPropagation() {
if (shouldStopPropagation && process.env.NODE_ENV !== 'production') {
console.error('stopPropagation is now the default behavior for events in React Spectrum. You can use continuePropagation() to revert this behavior.');
} else {
shouldStopPropagation = true;
}
},
continuePropagation() {
shouldStopPropagation = false;
},
isPropagationStopped() {
return shouldStopPropagation;
}
};
handler(event);
if (shouldStopPropagation) {
e.stopPropagation();
}
};
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {FocusableElement} from '@react-types/shared';
import {
focusWithoutScrolling,
getActiveElement,
getOwnerDocument,
runAfterTransition
} from '@react-aria/utils';
import {getInteractionModality} from './useFocusVisible';
/**
* A utility function that focuses an element while avoiding undesired side effects such
* as page scrolling and screen reader issues with CSS transitions.
*/
export function focusSafely(element: FocusableElement): void {
// If the user is interacting with a virtual cursor, e.g. screen reader, then
// wait until after any animated transitions that are currently occurring on
// the page before shifting focus. This avoids issues with VoiceOver on iOS
// causing the page to scroll when moving focus if the element is transitioning
// from off the screen.
const ownerDocument = getOwnerDocument(element);
const activeElement = getActiveElement(ownerDocument);
if (getInteractionModality() === 'virtual') {
let lastFocusedElement = activeElement;
runAfterTransition(() => {
// If focus did not move and the element is still in the document, focus it.
if (getActiveElement(ownerDocument) === lastFocusedElement && element.isConnected) {
focusWithoutScrolling(element);
}
});
} else {
focusWithoutScrolling(element);
}
}

47
node_modules/@react-aria/interactions/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,47 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
export {Pressable} from './Pressable';
export {PressResponder, ClearPressResponder} from './PressResponder';
export {useFocus} from './useFocus';
export {
isFocusVisible,
getInteractionModality,
setInteractionModality,
addWindowFocusTracking,
useInteractionModality,
useFocusVisible,
useFocusVisibleListener
} from './useFocusVisible';
export {useFocusWithin} from './useFocusWithin';
export {useHover} from './useHover';
export {useInteractOutside} from './useInteractOutside';
export {useKeyboard} from './useKeyboard';
export {useMove} from './useMove';
export {usePress} from './usePress';
export {useScrollWheel} from './useScrollWheel';
export {useLongPress} from './useLongPress';
export {useFocusable, FocusableProvider, Focusable, FocusableContext} from './useFocusable';
export {focusSafely} from './focusSafely';
export type {FocusProps, FocusResult} from './useFocus';
export type {FocusVisibleHandler, FocusVisibleProps, FocusVisibleResult, Modality} from './useFocusVisible';
export type {FocusWithinProps, FocusWithinResult} from './useFocusWithin';
export type {HoverProps, HoverResult} from './useHover';
export type {InteractOutsideProps} from './useInteractOutside';
export type {KeyboardProps, KeyboardResult} from './useKeyboard';
export type {PressProps, PressHookProps, PressResult} from './usePress';
export type {PressEvent, PressEvents, MoveStartEvent, MoveMoveEvent, MoveEndEvent, MoveEvents, HoverEvent, HoverEvents, FocusEvents, KeyboardEvents} from '@react-types/shared';
export type {MoveResult} from './useMove';
export type {LongPressProps, LongPressResult} from './useLongPress';
export type {ScrollWheelProps} from './useScrollWheel';
export type {FocusableAria, FocusableOptions, FocusableProviderProps} from './useFocusable';

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {getOwnerDocument, isIOS, runAfterTransition} from '@react-aria/utils';
// Safari on iOS starts selecting text on long press. The only way to avoid this, it seems,
// is to add user-select: none to the entire page. Adding it to the pressable element prevents
// that element from being selected, but nearby elements may still receive selection. We add
// user-select: none on touch start, and remove it again on touch end to prevent this.
// This must be implemented using global state to avoid race conditions between multiple elements.
// There are three possible states due to the delay before removing user-select: none after
// pointer up. The 'default' state always transitions to the 'disabled' state, which transitions
// to 'restoring'. The 'restoring' state can either transition back to 'disabled' or 'default'.
// For non-iOS devices, we apply user-select: none to the pressed element instead to avoid possible
// performance issues that arise from applying and removing user-select: none to the entire page
// (see https://github.com/adobe/react-spectrum/issues/1609).
type State = 'default' | 'disabled' | 'restoring';
// Note that state only matters here for iOS. Non-iOS gets user-select: none applied to the target element
// rather than at the document level so we just need to apply/remove user-select: none for each pressed element individually
let state: State = 'default';
let savedUserSelect = '';
let modifiedElementMap = new WeakMap<Element, string>();
export function disableTextSelection(target?: Element): void {
if (isIOS()) {
if (state === 'default') {
const documentObject = getOwnerDocument(target);
savedUserSelect = documentObject.documentElement.style.webkitUserSelect;
documentObject.documentElement.style.webkitUserSelect = 'none';
}
state = 'disabled';
} else if (target instanceof HTMLElement || target instanceof SVGElement) {
// If not iOS, store the target's original user-select and change to user-select: none
// Ignore state since it doesn't apply for non iOS
let property = 'userSelect' in target.style ? 'userSelect' : 'webkitUserSelect';
modifiedElementMap.set(target, target.style[property]);
target.style[property] = 'none';
}
}
export function restoreTextSelection(target?: Element): void {
if (isIOS()) {
// If the state is already default, there's nothing to do.
// If it is restoring, then there's no need to queue a second restore.
if (state !== 'disabled') {
return;
}
state = 'restoring';
// There appears to be a delay on iOS where selection still might occur
// after pointer up, so wait a bit before removing user-select.
setTimeout(() => {
// Wait for any CSS transitions to complete so we don't recompute style
// for the whole page in the middle of the animation and cause jank.
runAfterTransition(() => {
// Avoid race conditions
if (state === 'restoring') {
const documentObject = getOwnerDocument(target);
if (documentObject.documentElement.style.webkitUserSelect === 'none') {
documentObject.documentElement.style.webkitUserSelect = savedUserSelect || '';
}
savedUserSelect = '';
state = 'default';
}
});
}, 300);
} else if (target instanceof HTMLElement || target instanceof SVGElement) {
// If not iOS, restore the target's original user-select if any
// Ignore state since it doesn't apply for non iOS
if (target && modifiedElementMap.has(target)) {
let targetOldUserSelect = modifiedElementMap.get(target) as string;
let property = 'userSelect' in target.style ? 'userSelect' : 'webkitUserSelect';
if (target.style[property] === 'none') {
target.style[property] = targetOldUserSelect;
}
if (target.getAttribute('style') === '') {
target.removeAttribute('style');
}
modifiedElementMap.delete(target);
}
}
}

87
node_modules/@react-aria/interactions/src/useFocus.ts generated vendored Normal file
View File

@@ -0,0 +1,87 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
// Portions of the code in this file are based on code from react.
// Original licensing for the following can be found in the
// NOTICE file in the root directory of this source tree.
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
import {DOMAttributes, FocusableElement, FocusEvents} from '@react-types/shared';
import {FocusEvent, useCallback} from 'react';
import {getActiveElement, getEventTarget, getOwnerDocument} from '@react-aria/utils';
import {useSyntheticBlurEvent} from './utils';
export interface FocusProps<Target = FocusableElement> extends FocusEvents<Target> {
/** Whether the focus events should be disabled. */
isDisabled?: boolean
}
export interface FocusResult<Target = FocusableElement> {
/** Props to spread onto the target element. */
focusProps: DOMAttributes<Target>
}
/**
* Handles focus events for the immediate target.
* Focus events on child elements will be ignored.
*/
export function useFocus<Target extends FocusableElement = FocusableElement>(props: FocusProps<Target>): FocusResult<Target> {
let {
isDisabled,
onFocus: onFocusProp,
onBlur: onBlurProp,
onFocusChange
} = props;
const onBlur: FocusProps<Target>['onBlur'] = useCallback((e: FocusEvent<Target>) => {
if (e.target === e.currentTarget) {
if (onBlurProp) {
onBlurProp(e);
}
if (onFocusChange) {
onFocusChange(false);
}
return true;
}
}, [onBlurProp, onFocusChange]);
const onSyntheticFocus = useSyntheticBlurEvent<Target>(onBlur);
const onFocus: FocusProps<Target>['onFocus'] = useCallback((e: FocusEvent<Target>) => {
// Double check that document.activeElement actually matches e.target in case a previously chained
// focus handler already moved focus somewhere else.
const ownerDocument = getOwnerDocument(e.target);
const activeElement = ownerDocument ? getActiveElement(ownerDocument) : getActiveElement();
if (e.target === e.currentTarget && activeElement === getEventTarget(e.nativeEvent)) {
if (onFocusProp) {
onFocusProp(e);
}
if (onFocusChange) {
onFocusChange(true);
}
onSyntheticFocus(e);
}
}, [onFocusChange, onFocusProp, onSyntheticFocus]);
return {
focusProps: {
onFocus: (!isDisabled && (onFocusProp || onFocusChange || onBlurProp)) ? onFocus : undefined,
onBlur: (!isDisabled && (onBlurProp || onFocusChange)) ? onBlur : undefined
}
};
}

View File

@@ -0,0 +1,341 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
// Portions of the code in this file are based on code from react.
// Original licensing for the following can be found in the
// NOTICE file in the root directory of this source tree.
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
import {getOwnerDocument, getOwnerWindow, isMac, isVirtualClick} from '@react-aria/utils';
import {ignoreFocusEvent} from './utils';
import {useEffect, useState} from 'react';
import {useIsSSR} from '@react-aria/ssr';
export type Modality = 'keyboard' | 'pointer' | 'virtual';
type HandlerEvent = PointerEvent | MouseEvent | KeyboardEvent | FocusEvent | null;
type Handler = (modality: Modality, e: HandlerEvent) => void;
export type FocusVisibleHandler = (isFocusVisible: boolean) => void;
export interface FocusVisibleProps {
/** Whether the element is a text input. */
isTextInput?: boolean,
/** Whether the element will be auto focused. */
autoFocus?: boolean
}
export interface FocusVisibleResult {
/** Whether keyboard focus is visible globally. */
isFocusVisible: boolean
}
let currentModality: null | Modality = null;
let changeHandlers = new Set<Handler>();
interface GlobalListenerData {
focus: () => void
}
export let hasSetupGlobalListeners = new Map<Window, GlobalListenerData>(); // We use a map here to support setting event listeners across multiple document objects.
let hasEventBeforeFocus = false;
let hasBlurredWindowRecently = false;
// Only Tab or Esc keys will make focus visible on text input elements
const FOCUS_VISIBLE_INPUT_KEYS = {
Tab: true,
Escape: true
};
function triggerChangeHandlers(modality: Modality, e: HandlerEvent) {
for (let handler of changeHandlers) {
handler(modality, e);
}
}
/**
* Helper function to determine if a KeyboardEvent is unmodified and could make keyboard focus styles visible.
*/
function isValidKey(e: KeyboardEvent) {
// Control and Shift keys trigger when navigating back to the tab with keyboard.
return !(e.metaKey || (!isMac() && e.altKey) || e.ctrlKey || e.key === 'Control' || e.key === 'Shift' || e.key === 'Meta');
}
function handleKeyboardEvent(e: KeyboardEvent) {
hasEventBeforeFocus = true;
if (isValidKey(e)) {
currentModality = 'keyboard';
triggerChangeHandlers('keyboard', e);
}
}
function handlePointerEvent(e: PointerEvent | MouseEvent) {
currentModality = 'pointer';
if (e.type === 'mousedown' || e.type === 'pointerdown') {
hasEventBeforeFocus = true;
triggerChangeHandlers('pointer', e);
}
}
function handleClickEvent(e: MouseEvent) {
if (isVirtualClick(e)) {
hasEventBeforeFocus = true;
currentModality = 'virtual';
}
}
function handleFocusEvent(e: FocusEvent) {
// Firefox fires two extra focus events when the user first clicks into an iframe:
// first on the window, then on the document. We ignore these events so they don't
// cause keyboard focus rings to appear.
if (e.target === window || e.target === document || ignoreFocusEvent || !e.isTrusted) {
return;
}
// If a focus event occurs without a preceding keyboard or pointer event, switch to virtual modality.
// This occurs, for example, when navigating a form with the next/previous buttons on iOS.
if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {
currentModality = 'virtual';
triggerChangeHandlers('virtual', e);
}
hasEventBeforeFocus = false;
hasBlurredWindowRecently = false;
}
function handleWindowBlur() {
if (ignoreFocusEvent) {
return;
}
// When the window is blurred, reset state. This is necessary when tabbing out of the window,
// for example, since a subsequent focus event won't be fired.
hasEventBeforeFocus = false;
hasBlurredWindowRecently = true;
}
/**
* Setup global event listeners to control when keyboard focus style should be visible.
*/
function setupGlobalFocusEvents(element?: HTMLElement | null) {
if (typeof window === 'undefined' || typeof document === 'undefined' || hasSetupGlobalListeners.get(getOwnerWindow(element))) {
return;
}
const windowObject = getOwnerWindow(element);
const documentObject = getOwnerDocument(element);
// Programmatic focus() calls shouldn't affect the current input modality.
// However, we need to detect other cases when a focus event occurs without
// a preceding user event (e.g. screen reader focus). Overriding the focus
// method on HTMLElement.prototype is a bit hacky, but works.
let focus = windowObject.HTMLElement.prototype.focus;
windowObject.HTMLElement.prototype.focus = function () {
hasEventBeforeFocus = true;
focus.apply(this, arguments as unknown as [options?: FocusOptions | undefined]);
};
documentObject.addEventListener('keydown', handleKeyboardEvent, true);
documentObject.addEventListener('keyup', handleKeyboardEvent, true);
documentObject.addEventListener('click', handleClickEvent, true);
// Register focus events on the window so they are sure to happen
// before React's event listeners (registered on the document).
windowObject.addEventListener('focus', handleFocusEvent, true);
windowObject.addEventListener('blur', handleWindowBlur, false);
if (typeof PointerEvent !== 'undefined') {
documentObject.addEventListener('pointerdown', handlePointerEvent, true);
documentObject.addEventListener('pointermove', handlePointerEvent, true);
documentObject.addEventListener('pointerup', handlePointerEvent, true);
} else if (process.env.NODE_ENV === 'test') {
documentObject.addEventListener('mousedown', handlePointerEvent, true);
documentObject.addEventListener('mousemove', handlePointerEvent, true);
documentObject.addEventListener('mouseup', handlePointerEvent, true);
}
// Add unmount handler
windowObject.addEventListener('beforeunload', () => {
tearDownWindowFocusTracking(element);
}, {once: true});
hasSetupGlobalListeners.set(windowObject, {focus});
}
const tearDownWindowFocusTracking = (element, loadListener?: () => void) => {
const windowObject = getOwnerWindow(element);
const documentObject = getOwnerDocument(element);
if (loadListener) {
documentObject.removeEventListener('DOMContentLoaded', loadListener);
}
if (!hasSetupGlobalListeners.has(windowObject)) {
return;
}
windowObject.HTMLElement.prototype.focus = hasSetupGlobalListeners.get(windowObject)!.focus;
documentObject.removeEventListener('keydown', handleKeyboardEvent, true);
documentObject.removeEventListener('keyup', handleKeyboardEvent, true);
documentObject.removeEventListener('click', handleClickEvent, true);
windowObject.removeEventListener('focus', handleFocusEvent, true);
windowObject.removeEventListener('blur', handleWindowBlur, false);
if (typeof PointerEvent !== 'undefined') {
documentObject.removeEventListener('pointerdown', handlePointerEvent, true);
documentObject.removeEventListener('pointermove', handlePointerEvent, true);
documentObject.removeEventListener('pointerup', handlePointerEvent, true);
} else if (process.env.NODE_ENV === 'test') {
documentObject.removeEventListener('mousedown', handlePointerEvent, true);
documentObject.removeEventListener('mousemove', handlePointerEvent, true);
documentObject.removeEventListener('mouseup', handlePointerEvent, true);
}
hasSetupGlobalListeners.delete(windowObject);
};
/**
* EXPERIMENTAL
* Adds a window (i.e. iframe) to the list of windows that are being tracked for focus visible.
*
* Sometimes apps render portions of their tree into an iframe. In this case, we cannot accurately track if the focus
* is visible because we cannot see interactions inside the iframe. If you have this in your application's architecture,
* then this function will attach event listeners inside the iframe. You should call `addWindowFocusTracking` with an
* element from inside the window you wish to add. We'll retrieve the relevant elements based on that.
* Note, you do not need to call this for the default window, as we call it for you.
*
* When you are ready to stop listening, but you do not wish to unmount the iframe, you may call the cleanup function
* returned by `addWindowFocusTracking`. Otherwise, when you unmount the iframe, all listeners and state will be cleaned
* up automatically for you.
*
* @param element @default document.body - The element provided will be used to get the window to add.
* @returns A function to remove the event listeners and cleanup the state.
*/
export function addWindowFocusTracking(element?: HTMLElement | null): () => void {
const documentObject = getOwnerDocument(element);
let loadListener;
if (documentObject.readyState !== 'loading') {
setupGlobalFocusEvents(element);
} else {
loadListener = () => {
setupGlobalFocusEvents(element);
};
documentObject.addEventListener('DOMContentLoaded', loadListener);
}
return () => tearDownWindowFocusTracking(element, loadListener);
}
// Server-side rendering does not have the document object defined
// eslint-disable-next-line no-restricted-globals
if (typeof document !== 'undefined') {
addWindowFocusTracking();
}
/**
* If true, keyboard focus is visible.
*/
export function isFocusVisible(): boolean {
return currentModality !== 'pointer';
}
export function getInteractionModality(): Modality | null {
return currentModality;
}
export function setInteractionModality(modality: Modality): void {
currentModality = modality;
triggerChangeHandlers(modality, null);
}
/**
* Keeps state of the current modality.
*/
export function useInteractionModality(): Modality | null {
setupGlobalFocusEvents();
let [modality, setModality] = useState(currentModality);
useEffect(() => {
let handler = () => {
setModality(currentModality);
};
changeHandlers.add(handler);
return () => {
changeHandlers.delete(handler);
};
}, []);
return useIsSSR() ? null : modality;
}
const nonTextInputTypes = new Set([
'checkbox',
'radio',
'range',
'color',
'file',
'image',
'button',
'submit',
'reset'
]);
/**
* If this is attached to text input component, return if the event is a focus event (Tab/Escape keys pressed) so that
* focus visible style can be properly set.
*/
function isKeyboardFocusEvent(isTextInput: boolean, modality: Modality, e: HandlerEvent) {
let document = getOwnerDocument(e?.target as Element);
const IHTMLInputElement = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).HTMLInputElement : HTMLInputElement;
const IHTMLTextAreaElement = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).HTMLTextAreaElement : HTMLTextAreaElement;
const IHTMLElement = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).HTMLElement : HTMLElement;
const IKeyboardEvent = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).KeyboardEvent : KeyboardEvent;
// For keyboard events that occur on a non-input element that will move focus into input element (aka ArrowLeft going from Datepicker button to the main input group)
// we need to rely on the user passing isTextInput into here. This way we can skip toggling focus visiblity for said input element
isTextInput = isTextInput ||
(document.activeElement instanceof IHTMLInputElement && !nonTextInputTypes.has(document.activeElement.type)) ||
document.activeElement instanceof IHTMLTextAreaElement ||
(document.activeElement instanceof IHTMLElement && document.activeElement.isContentEditable);
return !(isTextInput && modality === 'keyboard' && e instanceof IKeyboardEvent && !FOCUS_VISIBLE_INPUT_KEYS[e.key]);
}
/**
* Manages focus visible state for the page, and subscribes individual components for updates.
*/
export function useFocusVisible(props: FocusVisibleProps = {}): FocusVisibleResult {
let {isTextInput, autoFocus} = props;
let [isFocusVisibleState, setFocusVisible] = useState(autoFocus || isFocusVisible());
useFocusVisibleListener((isFocusVisible) => {
setFocusVisible(isFocusVisible);
}, [isTextInput], {isTextInput});
return {isFocusVisible: isFocusVisibleState};
}
/**
* Listens for trigger change and reports if focus is visible (i.e., modality is not pointer).
*/
export function useFocusVisibleListener(fn: FocusVisibleHandler, deps: ReadonlyArray<any>, opts?: {isTextInput?: boolean}): void {
setupGlobalFocusEvents();
useEffect(() => {
let handler = (modality: Modality, e: HandlerEvent) => {
// We want to early return for any keyboard events that occur inside text inputs EXCEPT for Tab and Escape
if (!isKeyboardFocusEvent(!!(opts?.isTextInput), modality, e)) {
return;
}
fn(isFocusVisible());
};
changeHandlers.add(handler);
return () => {
changeHandlers.delete(handler);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
// Portions of the code in this file are based on code from react.
// Original licensing for the following can be found in the
// NOTICE file in the root directory of this source tree.
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
import {createSyntheticEvent, setEventTarget, useSyntheticBlurEvent} from './utils';
import {DOMAttributes} from '@react-types/shared';
import {FocusEvent, useCallback, useRef} from 'react';
import {getActiveElement, getEventTarget, getOwnerDocument, nodeContains, useGlobalListeners} from '@react-aria/utils';
export interface FocusWithinProps {
/** Whether the focus within events should be disabled. */
isDisabled?: boolean,
/** Handler that is called when the target element or a descendant receives focus. */
onFocusWithin?: (e: FocusEvent) => void,
/** Handler that is called when the target element and all descendants lose focus. */
onBlurWithin?: (e: FocusEvent) => void,
/** Handler that is called when the the focus within state changes. */
onFocusWithinChange?: (isFocusWithin: boolean) => void
}
export interface FocusWithinResult {
/** Props to spread onto the target element. */
focusWithinProps: DOMAttributes
}
/**
* Handles focus events for the target and its descendants.
*/
export function useFocusWithin(props: FocusWithinProps): FocusWithinResult {
let {
isDisabled,
onBlurWithin,
onFocusWithin,
onFocusWithinChange
} = props;
let state = useRef({
isFocusWithin: false
});
let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();
let onBlur = useCallback((e: FocusEvent) => {
// Ignore events bubbling through portals.
if (!e.currentTarget.contains(e.target)) {
return;
}
// We don't want to trigger onBlurWithin and then immediately onFocusWithin again
// when moving focus inside the element. Only trigger if the currentTarget doesn't
// include the relatedTarget (where focus is moving).
if (state.current.isFocusWithin && !(e.currentTarget as Element).contains(e.relatedTarget as Element)) {
state.current.isFocusWithin = false;
removeAllGlobalListeners();
if (onBlurWithin) {
onBlurWithin(e);
}
if (onFocusWithinChange) {
onFocusWithinChange(false);
}
}
}, [onBlurWithin, onFocusWithinChange, state, removeAllGlobalListeners]);
let onSyntheticFocus = useSyntheticBlurEvent(onBlur);
let onFocus = useCallback((e: FocusEvent) => {
// Ignore events bubbling through portals.
if (!e.currentTarget.contains(e.target)) {
return;
}
// Double check that document.activeElement actually matches e.target in case a previously chained
// focus handler already moved focus somewhere else.
const ownerDocument = getOwnerDocument(e.target);
const activeElement = getActiveElement(ownerDocument);
if (!state.current.isFocusWithin && activeElement === getEventTarget(e.nativeEvent)) {
if (onFocusWithin) {
onFocusWithin(e);
}
if (onFocusWithinChange) {
onFocusWithinChange(true);
}
state.current.isFocusWithin = true;
onSyntheticFocus(e);
// Browsers don't fire blur events when elements are removed from the DOM.
// However, if a focus event occurs outside the element we're tracking, we
// can manually fire onBlur.
let currentTarget = e.currentTarget;
addGlobalListener(ownerDocument, 'focus', e => {
if (state.current.isFocusWithin && !nodeContains(currentTarget, e.target as Element)) {
let nativeEvent = new ownerDocument.defaultView!.FocusEvent('blur', {relatedTarget: e.target});
setEventTarget(nativeEvent, currentTarget);
let event = createSyntheticEvent<FocusEvent>(nativeEvent);
onBlur(event);
}
}, {capture: true});
}
}, [onFocusWithin, onFocusWithinChange, onSyntheticFocus, addGlobalListener, onBlur]);
if (isDisabled) {
return {
focusWithinProps: {
// These cannot be null, that would conflict in mergeProps
onFocus: undefined,
onBlur: undefined
}
};
}
return {
focusWithinProps: {
onFocus,
onBlur
}
};
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {DOMAttributes, FocusableDOMProps, FocusableElement, FocusableProps, RefObject} from '@react-types/shared';
import {focusSafely} from './';
import {getOwnerWindow, isFocusable, mergeProps, mergeRefs, useObjectRef, useSyncRef} from '@react-aria/utils';
import React, {ForwardedRef, forwardRef, MutableRefObject, ReactElement, ReactNode, useContext, useEffect, useRef} from 'react';
import {useFocus} from './useFocus';
import {useKeyboard} from './useKeyboard';
export interface FocusableOptions<T = FocusableElement> extends FocusableProps<T>, FocusableDOMProps {
/** Whether focus should be disabled. */
isDisabled?: boolean
}
export interface FocusableProviderProps extends DOMAttributes {
/** The child element to provide DOM props to. */
children?: ReactNode
}
interface FocusableContextValue extends FocusableProviderProps {
ref?: MutableRefObject<FocusableElement | null>
}
// Exported for @react-aria/collections, which forwards this context.
/** @private */
export let FocusableContext = React.createContext<FocusableContextValue | null>(null);
function useFocusableContext(ref: RefObject<FocusableElement | null>): FocusableContextValue {
let context = useContext(FocusableContext) || {};
useSyncRef(context, ref);
// eslint-disable-next-line
let {ref: _, ...otherProps} = context;
return otherProps;
}
/**
* Provides DOM props to the nearest focusable child.
*/
export const FocusableProvider = React.forwardRef(function FocusableProvider(props: FocusableProviderProps, ref: ForwardedRef<FocusableElement>) {
let {children, ...otherProps} = props;
let objRef = useObjectRef(ref);
let context = {
...otherProps,
ref: objRef
};
return (
<FocusableContext.Provider value={context}>
{children}
</FocusableContext.Provider>
);
});
export interface FocusableAria {
/** Props for the focusable element. */
focusableProps: DOMAttributes
}
/**
* Used to make an element focusable and capable of auto focus.
*/
export function useFocusable<T extends FocusableElement = FocusableElement>(props: FocusableOptions<T>, domRef: RefObject<FocusableElement | null>): FocusableAria {
let {focusProps} = useFocus(props);
let {keyboardProps} = useKeyboard(props);
let interactions = mergeProps(focusProps, keyboardProps);
let domProps = useFocusableContext(domRef);
let interactionProps = props.isDisabled ? {} : domProps;
let autoFocusRef = useRef(props.autoFocus);
useEffect(() => {
if (autoFocusRef.current && domRef.current) {
focusSafely(domRef.current);
}
autoFocusRef.current = false;
}, [domRef]);
// Always set a tabIndex so that Safari allows focusing native buttons and inputs.
let tabIndex: number | undefined = props.excludeFromTabOrder ? -1 : 0;
if (props.isDisabled) {
tabIndex = undefined;
}
return {
focusableProps: mergeProps(
{
...interactions,
tabIndex
},
interactionProps
)
};
}
export interface FocusableComponentProps extends FocusableOptions {
children: ReactElement<DOMAttributes, string>
}
export const Focusable = forwardRef(({children, ...props}: FocusableComponentProps, ref: ForwardedRef<FocusableElement>) => {
ref = useObjectRef(ref);
let {focusableProps} = useFocusable(props, ref);
let child = React.Children.only(children);
useEffect(() => {
if (process.env.NODE_ENV === 'production') {
return;
}
let el = ref.current;
if (!el || !(el instanceof getOwnerWindow(el).Element)) {
console.error('<Focusable> child must forward its ref to a DOM element.');
return;
}
if (!props.isDisabled && !isFocusable(el)) {
console.warn('<Focusable> child must be focusable. Please ensure the tabIndex prop is passed through.');
return;
}
if (
el.localName !== 'button' &&
el.localName !== 'input' &&
el.localName !== 'select' &&
el.localName !== 'textarea' &&
el.localName !== 'a' &&
el.localName !== 'area' &&
el.localName !== 'summary' &&
el.localName !== 'img' &&
el.localName !== 'svg'
) {
let role = el.getAttribute('role');
if (!role) {
console.warn('<Focusable> child must have an interactive ARIA role.');
} else if (
// https://w3c.github.io/aria/#widget_roles
role !== 'application' &&
role !== 'button' &&
role !== 'checkbox' &&
role !== 'combobox' &&
role !== 'gridcell' &&
role !== 'link' &&
role !== 'menuitem' &&
role !== 'menuitemcheckbox' &&
role !== 'menuitemradio' &&
role !== 'option' &&
role !== 'radio' &&
role !== 'searchbox' &&
role !== 'separator' &&
role !== 'slider' &&
role !== 'spinbutton' &&
role !== 'switch' &&
role !== 'tab' &&
role !== 'tabpanel' &&
role !== 'textbox' &&
role !== 'treeitem' &&
// aria-describedby is also announced on these roles
role !== 'img' &&
role !== 'meter' &&
role !== 'progressbar'
) {
console.warn(`<Focusable> child must have an interactive ARIA role. Got "${role}".`);
}
}
}, [ref, props.isDisabled]);
// @ts-ignore
let childRef = parseInt(React.version, 10) < 19 ? child.ref : child.props.ref;
return React.cloneElement(
child,
{
...mergeProps(focusableProps, child.props),
// @ts-ignore
ref: mergeRefs(childRef, ref)
}
);
});

220
node_modules/@react-aria/interactions/src/useHover.ts generated vendored Normal file
View File

@@ -0,0 +1,220 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
// Portions of the code in this file are based on code from react.
// Original licensing for the following can be found in the
// NOTICE file in the root directory of this source tree.
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
import {DOMAttributes, HoverEvents} from '@react-types/shared';
import {getOwnerDocument, nodeContains, useGlobalListeners} from '@react-aria/utils';
import {useEffect, useMemo, useRef, useState} from 'react';
export interface HoverProps extends HoverEvents {
/** Whether the hover events should be disabled. */
isDisabled?: boolean
}
export interface HoverResult {
/** Props to spread on the target element. */
hoverProps: DOMAttributes,
isHovered: boolean
}
// iOS fires onPointerEnter twice: once with pointerType="touch" and again with pointerType="mouse".
// We want to ignore these emulated events so they do not trigger hover behavior.
// See https://bugs.webkit.org/show_bug.cgi?id=214609.
let globalIgnoreEmulatedMouseEvents = false;
let hoverCount = 0;
function setGlobalIgnoreEmulatedMouseEvents() {
globalIgnoreEmulatedMouseEvents = true;
// Clear globalIgnoreEmulatedMouseEvents after a short timeout. iOS fires onPointerEnter
// with pointerType="mouse" immediately after onPointerUp and before onFocus. On other
// devices that don't have this quirk, we don't want to ignore a mouse hover sometime in
// the distant future because a user previously touched the element.
setTimeout(() => {
globalIgnoreEmulatedMouseEvents = false;
}, 50);
}
function handleGlobalPointerEvent(e) {
if (e.pointerType === 'touch') {
setGlobalIgnoreEmulatedMouseEvents();
}
}
function setupGlobalTouchEvents() {
if (typeof document === 'undefined') {
return;
}
if (typeof PointerEvent !== 'undefined') {
document.addEventListener('pointerup', handleGlobalPointerEvent);
} else if (process.env.NODE_ENV === 'test') {
document.addEventListener('touchend', setGlobalIgnoreEmulatedMouseEvents);
}
hoverCount++;
return () => {
hoverCount--;
if (hoverCount > 0) {
return;
}
if (typeof PointerEvent !== 'undefined') {
document.removeEventListener('pointerup', handleGlobalPointerEvent);
} else if (process.env.NODE_ENV === 'test') {
document.removeEventListener('touchend', setGlobalIgnoreEmulatedMouseEvents);
}
};
}
/**
* Handles pointer hover interactions for an element. Normalizes behavior
* across browsers and platforms, and ignores emulated mouse events on touch devices.
*/
export function useHover(props: HoverProps): HoverResult {
let {
onHoverStart,
onHoverChange,
onHoverEnd,
isDisabled
} = props;
let [isHovered, setHovered] = useState(false);
let state = useRef({
isHovered: false,
ignoreEmulatedMouseEvents: false,
pointerType: '',
target: null
}).current;
useEffect(setupGlobalTouchEvents, []);
let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();
let {hoverProps, triggerHoverEnd} = useMemo(() => {
let triggerHoverStart = (event, pointerType) => {
state.pointerType = pointerType;
if (isDisabled || pointerType === 'touch' || state.isHovered || !event.currentTarget.contains(event.target)) {
return;
}
state.isHovered = true;
let target = event.currentTarget;
state.target = target;
// When an element that is hovered over is removed, no pointerleave event is fired by the browser,
// even though the originally hovered target may have shrunk in size so it is no longer hovered.
// However, a pointerover event will be fired on the new target the mouse is over.
// In Chrome this happens immediately. In Safari and Firefox, it happens upon moving the mouse one pixel.
addGlobalListener(getOwnerDocument(event.target), 'pointerover', e => {
if (state.isHovered && state.target && !nodeContains(state.target, e.target as Element)) {
triggerHoverEnd(e, e.pointerType);
}
}, {capture: true});
if (onHoverStart) {
onHoverStart({
type: 'hoverstart',
target,
pointerType
});
}
if (onHoverChange) {
onHoverChange(true);
}
setHovered(true);
};
let triggerHoverEnd = (event, pointerType) => {
let target = state.target;
state.pointerType = '';
state.target = null;
if (pointerType === 'touch' || !state.isHovered || !target) {
return;
}
state.isHovered = false;
removeAllGlobalListeners();
if (onHoverEnd) {
onHoverEnd({
type: 'hoverend',
target,
pointerType
});
}
if (onHoverChange) {
onHoverChange(false);
}
setHovered(false);
};
let hoverProps: DOMAttributes = {};
if (typeof PointerEvent !== 'undefined') {
hoverProps.onPointerEnter = (e) => {
if (globalIgnoreEmulatedMouseEvents && e.pointerType === 'mouse') {
return;
}
triggerHoverStart(e, e.pointerType);
};
hoverProps.onPointerLeave = (e) => {
if (!isDisabled && e.currentTarget.contains(e.target as Element)) {
triggerHoverEnd(e, e.pointerType);
}
};
} else if (process.env.NODE_ENV === 'test') {
hoverProps.onTouchStart = () => {
state.ignoreEmulatedMouseEvents = true;
};
hoverProps.onMouseEnter = (e) => {
if (!state.ignoreEmulatedMouseEvents && !globalIgnoreEmulatedMouseEvents) {
triggerHoverStart(e, 'mouse');
}
state.ignoreEmulatedMouseEvents = false;
};
hoverProps.onMouseLeave = (e) => {
if (!isDisabled && e.currentTarget.contains(e.target as Element)) {
triggerHoverEnd(e, 'mouse');
}
};
}
return {hoverProps, triggerHoverEnd};
}, [onHoverStart, onHoverChange, onHoverEnd, isDisabled, state, addGlobalListener, removeAllGlobalListeners]);
useEffect(() => {
// Call the triggerHoverEnd as soon as isDisabled changes to true
// Safe to call triggerHoverEnd, it will early return if we aren't currently hovering
if (isDisabled) {
triggerHoverEnd({currentTarget: state.target}, state.pointerType);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDisabled]);
return {
hoverProps,
isHovered
};
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
// Portions of the code in this file are based on code from react.
// Original licensing for the following can be found in the
// NOTICE file in the root directory of this source tree.
// See https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions
import {getOwnerDocument, useEffectEvent} from '@react-aria/utils';
import {RefObject} from '@react-types/shared';
import {useEffect, useRef} from 'react';
export interface InteractOutsideProps {
ref: RefObject<Element | null>,
onInteractOutside?: (e: PointerEvent) => void,
onInteractOutsideStart?: (e: PointerEvent) => void,
/** Whether the interact outside events should be disabled. */
isDisabled?: boolean
}
/**
* Example, used in components like Dialogs and Popovers so they can close
* when a user clicks outside them.
*/
export function useInteractOutside(props: InteractOutsideProps): void {
let {ref, onInteractOutside, isDisabled, onInteractOutsideStart} = props;
let stateRef = useRef({
isPointerDown: false,
ignoreEmulatedMouseEvents: false
});
let onPointerDown = useEffectEvent((e) => {
if (onInteractOutside && isValidEvent(e, ref)) {
if (onInteractOutsideStart) {
onInteractOutsideStart(e);
}
stateRef.current.isPointerDown = true;
}
});
let triggerInteractOutside = useEffectEvent((e: PointerEvent) => {
if (onInteractOutside) {
onInteractOutside(e);
}
});
useEffect(() => {
let state = stateRef.current;
if (isDisabled) {
return;
}
const element = ref.current;
const documentObject = getOwnerDocument(element);
// Use pointer events if available. Otherwise, fall back to mouse and touch events.
if (typeof PointerEvent !== 'undefined') {
let onPointerUp = (e) => {
if (state.isPointerDown && isValidEvent(e, ref)) {
triggerInteractOutside(e);
}
state.isPointerDown = false;
};
// changing these to capture phase fixed combobox
documentObject.addEventListener('pointerdown', onPointerDown, true);
documentObject.addEventListener('pointerup', onPointerUp, true);
return () => {
documentObject.removeEventListener('pointerdown', onPointerDown, true);
documentObject.removeEventListener('pointerup', onPointerUp, true);
};
} else if (process.env.NODE_ENV === 'test') {
let onMouseUp = (e) => {
if (state.ignoreEmulatedMouseEvents) {
state.ignoreEmulatedMouseEvents = false;
} else if (state.isPointerDown && isValidEvent(e, ref)) {
triggerInteractOutside(e);
}
state.isPointerDown = false;
};
let onTouchEnd = (e) => {
state.ignoreEmulatedMouseEvents = true;
if (state.isPointerDown && isValidEvent(e, ref)) {
triggerInteractOutside(e);
}
state.isPointerDown = false;
};
documentObject.addEventListener('mousedown', onPointerDown, true);
documentObject.addEventListener('mouseup', onMouseUp, true);
documentObject.addEventListener('touchstart', onPointerDown, true);
documentObject.addEventListener('touchend', onTouchEnd, true);
return () => {
documentObject.removeEventListener('mousedown', onPointerDown, true);
documentObject.removeEventListener('mouseup', onMouseUp, true);
documentObject.removeEventListener('touchstart', onPointerDown, true);
documentObject.removeEventListener('touchend', onTouchEnd, true);
};
}
}, [ref, isDisabled, onPointerDown, triggerInteractOutside]);
}
function isValidEvent(event, ref) {
if (event.button > 0) {
return false;
}
if (event.target) {
// if the event target is no longer in the document, ignore
const ownerDocument = event.target.ownerDocument;
if (!ownerDocument || !ownerDocument.documentElement.contains(event.target)) {
return false;
}
// If the target is within a top layer element (e.g. toasts), ignore.
if (event.target.closest('[data-react-aria-top-layer]')) {
return false;
}
}
if (!ref.current) {
return false;
}
// When the event source is inside a Shadow DOM, event.target is just the shadow root.
// Using event.composedPath instead means we can get the actual element inside the shadow root.
// This only works if the shadow root is open, there is no way to detect if it is closed.
// If the event composed path contains the ref, interaction is inside.
return !event.composedPath().includes(ref.current);
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {createEventHandler} from './createEventHandler';
import {DOMAttributes, KeyboardEvents} from '@react-types/shared';
export interface KeyboardProps extends KeyboardEvents {
/** Whether the keyboard events should be disabled. */
isDisabled?: boolean
}
export interface KeyboardResult {
/** Props to spread onto the target element. */
keyboardProps: DOMAttributes
}
/**
* Handles keyboard interactions for a focusable element.
*/
export function useKeyboard(props: KeyboardProps): KeyboardResult {
return {
keyboardProps: props.isDisabled ? {} : {
onKeyDown: createEventHandler(props.onKeyDown),
onKeyUp: createEventHandler(props.onKeyUp)
}
};
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {DOMAttributes, FocusableElement, LongPressEvent} from '@react-types/shared';
import {focusWithoutScrolling, getOwnerDocument, mergeProps, useDescription, useGlobalListeners} from '@react-aria/utils';
import {usePress} from './usePress';
import {useRef} from 'react';
export interface LongPressProps {
/** Whether long press events should be disabled. */
isDisabled?: boolean,
/** Handler that is called when a long press interaction starts. */
onLongPressStart?: (e: LongPressEvent) => void,
/**
* Handler that is called when a long press interaction ends, either
* over the target or when the pointer leaves the target.
*/
onLongPressEnd?: (e: LongPressEvent) => void,
/**
* Handler that is called when the threshold time is met while
* the press is over the target.
*/
onLongPress?: (e: LongPressEvent) => void,
/**
* The amount of time in milliseconds to wait before triggering a long press.
* @default 500ms
*/
threshold?: number,
/**
* A description for assistive techology users indicating that a long press
* action is available, e.g. "Long press to open menu".
*/
accessibilityDescription?: string
}
export interface LongPressResult {
/** Props to spread on the target element. */
longPressProps: DOMAttributes
}
const DEFAULT_THRESHOLD = 500;
/**
* Handles long press interactions across mouse and touch devices. Supports a customizable time threshold,
* accessibility description, and normalizes behavior across browsers and devices.
*/
export function useLongPress(props: LongPressProps): LongPressResult {
let {
isDisabled,
onLongPressStart,
onLongPressEnd,
onLongPress,
threshold = DEFAULT_THRESHOLD,
accessibilityDescription
} = props;
const timeRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
let {addGlobalListener, removeGlobalListener} = useGlobalListeners();
let {pressProps} = usePress({
isDisabled,
onPressStart(e) {
e.continuePropagation();
if (e.pointerType === 'mouse' || e.pointerType === 'touch') {
if (onLongPressStart) {
onLongPressStart({
...e,
type: 'longpressstart'
});
}
timeRef.current = setTimeout(() => {
// Prevent other usePress handlers from also handling this event.
e.target.dispatchEvent(new PointerEvent('pointercancel', {bubbles: true}));
// Ensure target is focused. On touch devices, browsers typically focus on pointer up.
if (getOwnerDocument(e.target).activeElement !== e.target) {
focusWithoutScrolling(e.target as FocusableElement);
}
if (onLongPress) {
onLongPress({
...e,
type: 'longpress'
});
}
timeRef.current = undefined;
}, threshold);
// Prevent context menu, which may be opened on long press on touch devices
if (e.pointerType === 'touch') {
let onContextMenu = e => {
e.preventDefault();
};
addGlobalListener(e.target, 'contextmenu', onContextMenu, {once: true});
addGlobalListener(window, 'pointerup', () => {
// If no contextmenu event is fired quickly after pointerup, remove the handler
// so future context menu events outside a long press are not prevented.
setTimeout(() => {
removeGlobalListener(e.target, 'contextmenu', onContextMenu);
}, 30);
}, {once: true});
}
}
},
onPressEnd(e) {
if (timeRef.current) {
clearTimeout(timeRef.current);
}
if (onLongPressEnd && (e.pointerType === 'mouse' || e.pointerType === 'touch')) {
onLongPressEnd({
...e,
type: 'longpressend'
});
}
}
});
let descriptionProps = useDescription(onLongPress && !isDisabled ? accessibilityDescription : undefined);
return {
longPressProps: mergeProps(pressProps, descriptionProps)
};
}

231
node_modules/@react-aria/interactions/src/useMove.ts generated vendored Normal file
View File

@@ -0,0 +1,231 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {disableTextSelection, restoreTextSelection} from './textSelection';
import {DOMAttributes, MoveEvents, PointerType} from '@react-types/shared';
import React, {useMemo, useRef} from 'react';
import {useEffectEvent, useGlobalListeners} from '@react-aria/utils';
export interface MoveResult {
/** Props to spread on the target element. */
moveProps: DOMAttributes
}
interface EventBase {
shiftKey: boolean,
ctrlKey: boolean,
metaKey: boolean,
altKey: boolean
}
/**
* Handles move interactions across mouse, touch, and keyboard, including dragging with
* the mouse or touch, and using the arrow keys. Normalizes behavior across browsers and
* platforms, and ignores emulated mouse events on touch devices.
*/
export function useMove(props: MoveEvents): MoveResult {
let {onMoveStart, onMove, onMoveEnd} = props;
let state = useRef<{
didMove: boolean,
lastPosition: {pageX: number, pageY: number} | null,
id: number | null
}>({didMove: false, lastPosition: null, id: null});
let {addGlobalListener, removeGlobalListener} = useGlobalListeners();
let move = useEffectEvent((originalEvent: EventBase, pointerType: PointerType, deltaX: number, deltaY: number) => {
if (deltaX === 0 && deltaY === 0) {
return;
}
if (!state.current.didMove) {
state.current.didMove = true;
onMoveStart?.({
type: 'movestart',
pointerType,
shiftKey: originalEvent.shiftKey,
metaKey: originalEvent.metaKey,
ctrlKey: originalEvent.ctrlKey,
altKey: originalEvent.altKey
});
}
onMove?.({
type: 'move',
pointerType,
deltaX: deltaX,
deltaY: deltaY,
shiftKey: originalEvent.shiftKey,
metaKey: originalEvent.metaKey,
ctrlKey: originalEvent.ctrlKey,
altKey: originalEvent.altKey
});
});
let end = useEffectEvent((originalEvent: EventBase, pointerType: PointerType) => {
restoreTextSelection();
if (state.current.didMove) {
onMoveEnd?.({
type: 'moveend',
pointerType,
shiftKey: originalEvent.shiftKey,
metaKey: originalEvent.metaKey,
ctrlKey: originalEvent.ctrlKey,
altKey: originalEvent.altKey
});
}
});
let moveProps = useMemo(() => {
let moveProps: DOMAttributes = {};
let start = () => {
disableTextSelection();
state.current.didMove = false;
};
if (typeof PointerEvent === 'undefined' && process.env.NODE_ENV === 'test') {
let onMouseMove = (e: MouseEvent) => {
if (e.button === 0) {
move(e, 'mouse', e.pageX - (state.current.lastPosition?.pageX ?? 0), e.pageY - (state.current.lastPosition?.pageY ?? 0));
state.current.lastPosition = {pageX: e.pageX, pageY: e.pageY};
}
};
let onMouseUp = (e: MouseEvent) => {
if (e.button === 0) {
end(e, 'mouse');
removeGlobalListener(window, 'mousemove', onMouseMove, false);
removeGlobalListener(window, 'mouseup', onMouseUp, false);
}
};
moveProps.onMouseDown = (e: React.MouseEvent) => {
if (e.button === 0) {
start();
e.stopPropagation();
e.preventDefault();
state.current.lastPosition = {pageX: e.pageX, pageY: e.pageY};
addGlobalListener(window, 'mousemove', onMouseMove, false);
addGlobalListener(window, 'mouseup', onMouseUp, false);
}
};
let onTouchMove = (e: TouchEvent) => {
let touch = [...e.changedTouches].findIndex(({identifier}) => identifier === state.current.id);
if (touch >= 0) {
let {pageX, pageY} = e.changedTouches[touch];
move(e, 'touch', pageX - (state.current.lastPosition?.pageX ?? 0), pageY - (state.current.lastPosition?.pageY ?? 0));
state.current.lastPosition = {pageX, pageY};
}
};
let onTouchEnd = (e: TouchEvent) => {
let touch = [...e.changedTouches].findIndex(({identifier}) => identifier === state.current.id);
if (touch >= 0) {
end(e, 'touch');
state.current.id = null;
removeGlobalListener(window, 'touchmove', onTouchMove);
removeGlobalListener(window, 'touchend', onTouchEnd);
removeGlobalListener(window, 'touchcancel', onTouchEnd);
}
};
moveProps.onTouchStart = (e: React.TouchEvent) => {
if (e.changedTouches.length === 0 || state.current.id != null) {
return;
}
let {pageX, pageY, identifier} = e.changedTouches[0];
start();
e.stopPropagation();
e.preventDefault();
state.current.lastPosition = {pageX, pageY};
state.current.id = identifier;
addGlobalListener(window, 'touchmove', onTouchMove, false);
addGlobalListener(window, 'touchend', onTouchEnd, false);
addGlobalListener(window, 'touchcancel', onTouchEnd, false);
};
} else {
let onPointerMove = (e: PointerEvent) => {
if (e.pointerId === state.current.id) {
let pointerType = (e.pointerType || 'mouse') as PointerType;
// Problems with PointerEvent#movementX/movementY:
// 1. it is always 0 on macOS Safari.
// 2. On Chrome Android, it's scaled by devicePixelRatio, but not on Chrome macOS
move(e, pointerType, e.pageX - (state.current.lastPosition?.pageX ?? 0), e.pageY - (state.current.lastPosition?.pageY ?? 0));
state.current.lastPosition = {pageX: e.pageX, pageY: e.pageY};
}
};
let onPointerUp = (e: PointerEvent) => {
if (e.pointerId === state.current.id) {
let pointerType = (e.pointerType || 'mouse') as PointerType;
end(e, pointerType);
state.current.id = null;
removeGlobalListener(window, 'pointermove', onPointerMove, false);
removeGlobalListener(window, 'pointerup', onPointerUp, false);
removeGlobalListener(window, 'pointercancel', onPointerUp, false);
}
};
moveProps.onPointerDown = (e: React.PointerEvent) => {
if (e.button === 0 && state.current.id == null) {
start();
e.stopPropagation();
e.preventDefault();
state.current.lastPosition = {pageX: e.pageX, pageY: e.pageY};
state.current.id = e.pointerId;
addGlobalListener(window, 'pointermove', onPointerMove, false);
addGlobalListener(window, 'pointerup', onPointerUp, false);
addGlobalListener(window, 'pointercancel', onPointerUp, false);
}
};
}
let triggerKeyboardMove = (e: EventBase, deltaX: number, deltaY: number) => {
start();
move(e, 'keyboard', deltaX, deltaY);
end(e, 'keyboard');
};
moveProps.onKeyDown = (e) => {
switch (e.key) {
case 'Left':
case 'ArrowLeft':
e.preventDefault();
e.stopPropagation();
triggerKeyboardMove(e, -1, 0);
break;
case 'Right':
case 'ArrowRight':
e.preventDefault();
e.stopPropagation();
triggerKeyboardMove(e, 1, 0);
break;
case 'Up':
case 'ArrowUp':
e.preventDefault();
e.stopPropagation();
triggerKeyboardMove(e, 0, -1);
break;
case 'Down':
case 'ArrowDown':
e.preventDefault();
e.stopPropagation();
triggerKeyboardMove(e, 0, 1);
break;
}
};
return moveProps;
}, [state, addGlobalListener, removeGlobalListener, move, end]);
return {moveProps};
}

1035
node_modules/@react-aria/interactions/src/usePress.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2021 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {RefObject, ScrollEvents} from '@react-types/shared';
import {useCallback} from 'react';
import {useEvent} from '@react-aria/utils';
export interface ScrollWheelProps extends ScrollEvents {
/** Whether the scroll listener should be disabled. */
isDisabled?: boolean
}
// scroll wheel needs to be added not passively so it's cancelable, small helper hook to remember that
export function useScrollWheel(props: ScrollWheelProps, ref: RefObject<HTMLElement | null>): void {
let {onScroll, isDisabled} = props;
let onScrollHandler = useCallback((e) => {
// If the ctrlKey is pressed, this is a zoom event, do nothing.
if (e.ctrlKey) {
return;
}
// stop scrolling the page
e.preventDefault();
e.stopPropagation();
if (onScroll) {
onScroll({deltaX: e.deltaX, deltaY: e.deltaY});
}
}, [onScroll]);
useEvent(ref, 'wheel', isDisabled ? undefined : onScrollHandler);
}

178
node_modules/@react-aria/interactions/src/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,178 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {FocusableElement} from '@react-types/shared';
import {focusWithoutScrolling, getOwnerWindow, isFocusable, useEffectEvent, useLayoutEffect} from '@react-aria/utils';
import {FocusEvent as ReactFocusEvent, SyntheticEvent, useCallback, useRef} from 'react';
// Turn a native event into a React synthetic event.
export function createSyntheticEvent<E extends SyntheticEvent>(nativeEvent: Event): E {
let event = nativeEvent as any as E;
event.nativeEvent = nativeEvent;
event.isDefaultPrevented = () => event.defaultPrevented;
// cancelBubble is technically deprecated in the spec, but still supported in all browsers.
event.isPropagationStopped = () => (event as any).cancelBubble;
event.persist = () => {};
return event;
}
export function setEventTarget(event: Event, target: Element): void {
Object.defineProperty(event, 'target', {value: target});
Object.defineProperty(event, 'currentTarget', {value: target});
}
export function useSyntheticBlurEvent<Target extends Element = Element>(onBlur: (e: ReactFocusEvent<Target>) => void): (e: ReactFocusEvent<Target>) => void {
let stateRef = useRef({
isFocused: false,
observer: null as MutationObserver | null
});
// Clean up MutationObserver on unmount. See below.
useLayoutEffect(() => {
const state = stateRef.current;
return () => {
if (state.observer) {
state.observer.disconnect();
state.observer = null;
}
};
}, []);
let dispatchBlur = useEffectEvent((e: ReactFocusEvent<Target>) => {
onBlur?.(e);
});
// This function is called during a React onFocus event.
return useCallback((e: ReactFocusEvent<Target>) => {
// React does not fire onBlur when an element is disabled. https://github.com/facebook/react/issues/9142
// Most browsers fire a native focusout event in this case, except for Firefox. In that case, we use a
// MutationObserver to watch for the disabled attribute, and dispatch these events ourselves.
// For browsers that do, focusout fires before the MutationObserver, so onBlur should not fire twice.
if (
e.target instanceof HTMLButtonElement ||
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement ||
e.target instanceof HTMLSelectElement
) {
stateRef.current.isFocused = true;
let target = e.target;
let onBlurHandler: EventListenerOrEventListenerObject | null = (e) => {
stateRef.current.isFocused = false;
if (target.disabled) {
// For backward compatibility, dispatch a (fake) React synthetic event.
let event = createSyntheticEvent<ReactFocusEvent<Target>>(e);
dispatchBlur(event);
}
// We no longer need the MutationObserver once the target is blurred.
if (stateRef.current.observer) {
stateRef.current.observer.disconnect();
stateRef.current.observer = null;
}
};
target.addEventListener('focusout', onBlurHandler, {once: true});
stateRef.current.observer = new MutationObserver(() => {
if (stateRef.current.isFocused && target.disabled) {
stateRef.current.observer?.disconnect();
let relatedTargetEl = target === document.activeElement ? null : document.activeElement;
target.dispatchEvent(new FocusEvent('blur', {relatedTarget: relatedTargetEl}));
target.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget: relatedTargetEl}));
}
});
stateRef.current.observer.observe(target, {attributes: true, attributeFilter: ['disabled']});
}
}, [dispatchBlur]);
}
export let ignoreFocusEvent = false;
/**
* This function prevents the next focus event fired on `target`, without using `event.preventDefault()`.
* It works by waiting for the series of focus events to occur, and reverts focus back to where it was before.
* It also makes these events mostly non-observable by using a capturing listener on the window and stopping propagation.
*/
export function preventFocus(target: FocusableElement | null): (() => void) | undefined {
// The browser will focus the nearest focusable ancestor of our target.
while (target && !isFocusable(target)) {
target = target.parentElement;
}
let window = getOwnerWindow(target);
let activeElement = window.document.activeElement as FocusableElement | null;
if (!activeElement || activeElement === target) {
return;
}
ignoreFocusEvent = true;
let isRefocusing = false;
let onBlur = (e: FocusEvent) => {
if (e.target === activeElement || isRefocusing) {
e.stopImmediatePropagation();
}
};
let onFocusOut = (e: FocusEvent) => {
if (e.target === activeElement || isRefocusing) {
e.stopImmediatePropagation();
// If there was no focusable ancestor, we don't expect a focus event.
// Re-focus the original active element here.
if (!target && !isRefocusing) {
isRefocusing = true;
focusWithoutScrolling(activeElement);
cleanup();
}
}
};
let onFocus = (e: FocusEvent) => {
if (e.target === target || isRefocusing) {
e.stopImmediatePropagation();
}
};
let onFocusIn = (e: FocusEvent) => {
if (e.target === target || isRefocusing) {
e.stopImmediatePropagation();
if (!isRefocusing) {
isRefocusing = true;
focusWithoutScrolling(activeElement);
cleanup();
}
}
};
window.addEventListener('blur', onBlur, true);
window.addEventListener('focusout', onFocusOut, true);
window.addEventListener('focusin', onFocusIn, true);
window.addEventListener('focus', onFocus, true);
let cleanup = () => {
cancelAnimationFrame(raf);
window.removeEventListener('blur', onBlur, true);
window.removeEventListener('focusout', onFocusOut, true);
window.removeEventListener('focusin', onFocusIn, true);
window.removeEventListener('focus', onFocus, true);
ignoreFocusEvent = false;
isRefocusing = false;
};
let raf = requestAnimationFrame(cleanup);
return cleanup;
}