@react-aria/interactions
Advanced tools
Comparing version 3.0.0-nightly-50c7ada5d-241223 to 3.0.0-nightly-527c98a84-250305
@@ -30,6 +30,10 @@ | ||
stopPropagation () { | ||
console.error('stopPropagation is now the default behavior for events in React Spectrum. You can use continuePropagation() to revert this behavior.'); | ||
if (shouldStopPropagation) 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; | ||
} | ||
@@ -36,0 +40,0 @@ }; |
@@ -24,6 +24,10 @@ /* | ||
stopPropagation () { | ||
console.error('stopPropagation is now the default behavior for events in React Spectrum. You can use continuePropagation() to revert this behavior.'); | ||
if (shouldStopPropagation) 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; | ||
} | ||
@@ -30,0 +34,0 @@ }; |
@@ -13,2 +13,4 @@ var $e1dbec26039c051d$exports = require("./Pressable.main.js"); | ||
var $3cd7b5d0eebf0ca9$exports = require("./useLongPress.main.js"); | ||
var $15f8fd80892557ff$exports = require("./useFocusable.main.js"); | ||
var $2833058fcd3993f5$exports = require("./focusSafely.main.js"); | ||
@@ -39,2 +41,7 @@ | ||
$parcel$export(module.exports, "useLongPress", () => $3cd7b5d0eebf0ca9$exports.useLongPress); | ||
$parcel$export(module.exports, "useFocusable", () => $15f8fd80892557ff$exports.useFocusable); | ||
$parcel$export(module.exports, "FocusableProvider", () => $15f8fd80892557ff$exports.FocusableProvider); | ||
$parcel$export(module.exports, "Focusable", () => $15f8fd80892557ff$exports.Focusable); | ||
$parcel$export(module.exports, "FocusableContext", () => $15f8fd80892557ff$exports.FocusableContext); | ||
$parcel$export(module.exports, "focusSafely", () => $2833058fcd3993f5$exports.focusSafely); | ||
/* | ||
@@ -64,2 +71,4 @@ * Copyright 2020 Adobe. All rights reserved. | ||
//# sourceMappingURL=main.js.map |
@@ -13,2 +13,4 @@ import {Pressable as $3b117e43dc0ca95d$export$27c701ed9e449e99} from "./Pressable.module.js"; | ||
import {useLongPress as $8a26561d2877236e$export$c24ed0104d07eab9} from "./useLongPress.module.js"; | ||
import {Focusable as $f645667febf57a63$export$35a3bebf7ef2d934, FocusableContext as $f645667febf57a63$export$f9762fab77588ecb, FocusableProvider as $f645667febf57a63$export$13f3202a3e5ddd5, useFocusable as $f645667febf57a63$export$4c014de7c8940b4c} from "./useFocusable.module.js"; | ||
import {focusSafely as $3ad3f6e1647bc98d$export$80f3e147d781571c} from "./focusSafely.module.js"; | ||
@@ -39,3 +41,5 @@ /* | ||
export {$3b117e43dc0ca95d$export$27c701ed9e449e99 as Pressable, $f1ab8c75478c6f73$export$3351871ee4b288b8 as PressResponder, $f1ab8c75478c6f73$export$cf75428e0b9ed1ea as ClearPressResponder, $a1ea59d68270f0dd$export$f8168d8dd8fd66e6 as useFocus, $507fabe10e71c6fb$export$b9b3dfddab17db27 as isFocusVisible, $507fabe10e71c6fb$export$630ff653c5ada6a9 as getInteractionModality, $507fabe10e71c6fb$export$8397ddfc504fdb9a as setInteractionModality, $507fabe10e71c6fb$export$2f1888112f558a7d as addWindowFocusTracking, $507fabe10e71c6fb$export$98e20ec92f614cfe as useInteractionModality, $507fabe10e71c6fb$export$ffd9e5021c1fb2d6 as useFocusVisible, $507fabe10e71c6fb$export$ec71b4b83ac08ec3 as useFocusVisibleListener, $9ab94262bd0047c7$export$420e68273165f4ec as useFocusWithin, $6179b936705e76d3$export$ae780daf29e6d456 as useHover, $e0b6e0b68ec7f50f$export$872b660ac5a1ff98 as useInteractOutside, $46d819fcbaf35654$export$8f71654801c2f7cd as useKeyboard, $e8a7022cf87cba2a$export$36da96379f79f245 as useMove, $f6c31cce2adf654f$export$45712eceda6fad21 as usePress, $7d0a636d7a4dcefd$export$2123ff2b87c81ca as useScrollWheel, $8a26561d2877236e$export$c24ed0104d07eab9 as useLongPress}; | ||
export {$3b117e43dc0ca95d$export$27c701ed9e449e99 as Pressable, $f1ab8c75478c6f73$export$3351871ee4b288b8 as PressResponder, $f1ab8c75478c6f73$export$cf75428e0b9ed1ea as ClearPressResponder, $a1ea59d68270f0dd$export$f8168d8dd8fd66e6 as useFocus, $507fabe10e71c6fb$export$b9b3dfddab17db27 as isFocusVisible, $507fabe10e71c6fb$export$630ff653c5ada6a9 as getInteractionModality, $507fabe10e71c6fb$export$8397ddfc504fdb9a as setInteractionModality, $507fabe10e71c6fb$export$2f1888112f558a7d as addWindowFocusTracking, $507fabe10e71c6fb$export$98e20ec92f614cfe as useInteractionModality, $507fabe10e71c6fb$export$ffd9e5021c1fb2d6 as useFocusVisible, $507fabe10e71c6fb$export$ec71b4b83ac08ec3 as useFocusVisibleListener, $9ab94262bd0047c7$export$420e68273165f4ec as useFocusWithin, $6179b936705e76d3$export$ae780daf29e6d456 as useHover, $e0b6e0b68ec7f50f$export$872b660ac5a1ff98 as useInteractOutside, $46d819fcbaf35654$export$8f71654801c2f7cd as useKeyboard, $e8a7022cf87cba2a$export$36da96379f79f245 as useMove, $f6c31cce2adf654f$export$45712eceda6fad21 as usePress, $7d0a636d7a4dcefd$export$2123ff2b87c81ca as useScrollWheel, $8a26561d2877236e$export$c24ed0104d07eab9 as useLongPress, $f645667febf57a63$export$4c014de7c8940b4c as useFocusable, $f645667febf57a63$export$13f3202a3e5ddd5 as FocusableProvider, $f645667febf57a63$export$35a3bebf7ef2d934 as Focusable, $f645667febf57a63$export$f9762fab77588ecb as FocusableContext, $3ad3f6e1647bc98d$export$80f3e147d781571c as focusSafely}; | ||
//# sourceMappingURL=module.js.map |
var $0294ea432cd92340$exports = require("./usePress.main.js"); | ||
var $15f8fd80892557ff$exports = require("./useFocusable.main.js"); | ||
var $ev4bP$reactariautils = require("@react-aria/utils"); | ||
@@ -28,2 +29,3 @@ var $ev4bP$react = require("react"); | ||
const $e1dbec26039c051d$export$27c701ed9e449e99 = /*#__PURE__*/ (0, ($parcel$interopDefault($ev4bP$react))).forwardRef(({ children: children, ...props }, ref)=>{ | ||
@@ -35,7 +37,29 @@ ref = (0, $ev4bP$reactariautils.useObjectRef)(ref); | ||
}); | ||
let { focusableProps: focusableProps } = (0, $15f8fd80892557ff$exports.useFocusable)(props, ref); | ||
let child = (0, ($parcel$interopDefault($ev4bP$react))).Children.only(children); | ||
return /*#__PURE__*/ (0, ($parcel$interopDefault($ev4bP$react))).cloneElement(child, // @ts-ignore | ||
{ | ||
ref: ref, | ||
...(0, $ev4bP$reactariautils.mergeProps)(child.props, pressProps) | ||
(0, $ev4bP$react.useEffect)(()=>{ | ||
let el = ref.current; | ||
if (!el || !(el instanceof (0, $ev4bP$reactariautils.getOwnerWindow)(el).Element)) { | ||
console.error('<Pressable> child must forward its ref to a DOM element.'); | ||
return; | ||
} | ||
if (!(0, $ev4bP$reactariautils.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((0, ($parcel$interopDefault($ev4bP$react))).version, 10) < 19 ? child.ref : child.props.ref; | ||
return /*#__PURE__*/ (0, ($parcel$interopDefault($ev4bP$react))).cloneElement(child, { | ||
...(0, $ev4bP$reactariautils.mergeProps)(pressProps, focusableProps, child.props), | ||
// @ts-ignore | ||
ref: (0, $ev4bP$reactariautils.mergeRefs)(childRef, ref) | ||
}); | ||
@@ -42,0 +66,0 @@ }); |
import {usePress as $f6c31cce2adf654f$export$45712eceda6fad21} from "./usePress.module.js"; | ||
import {useObjectRef as $hhDyF$useObjectRef, mergeProps as $hhDyF$mergeProps} from "@react-aria/utils"; | ||
import $hhDyF$react from "react"; | ||
import {useFocusable as $f645667febf57a63$export$4c014de7c8940b4c} from "./useFocusable.module.js"; | ||
import {useObjectRef as $hhDyF$useObjectRef, getOwnerWindow as $hhDyF$getOwnerWindow, isFocusable as $hhDyF$isFocusable, mergeProps as $hhDyF$mergeProps, mergeRefs as $hhDyF$mergeRefs} from "@react-aria/utils"; | ||
import $hhDyF$react, {useEffect as $hhDyF$useEffect} from "react"; | ||
@@ -18,2 +19,3 @@ /* | ||
const $3b117e43dc0ca95d$export$27c701ed9e449e99 = /*#__PURE__*/ (0, $hhDyF$react).forwardRef(({ children: children, ...props }, ref)=>{ | ||
@@ -25,7 +27,29 @@ ref = (0, $hhDyF$useObjectRef)(ref); | ||
}); | ||
let { focusableProps: focusableProps } = (0, $f645667febf57a63$export$4c014de7c8940b4c)(props, ref); | ||
let child = (0, $hhDyF$react).Children.only(children); | ||
return /*#__PURE__*/ (0, $hhDyF$react).cloneElement(child, // @ts-ignore | ||
{ | ||
ref: ref, | ||
...(0, $hhDyF$mergeProps)(child.props, pressProps) | ||
(0, $hhDyF$useEffect)(()=>{ | ||
let el = ref.current; | ||
if (!el || !(el instanceof (0, $hhDyF$getOwnerWindow)(el).Element)) { | ||
console.error('<Pressable> child must forward its ref to a DOM element.'); | ||
return; | ||
} | ||
if (!(0, $hhDyF$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((0, $hhDyF$react).version, 10) < 19 ? child.ref : child.props.ref; | ||
return /*#__PURE__*/ (0, $hhDyF$react).cloneElement(child, { | ||
...(0, $hhDyF$mergeProps)(pressProps, focusableProps, child.props), | ||
// @ts-ignore | ||
ref: (0, $hhDyF$mergeRefs)(childRef, ref) | ||
}); | ||
@@ -32,0 +56,0 @@ }); |
@@ -37,4 +37,5 @@ var $20aJV$reactariautils = require("@react-aria/utils"); | ||
// Ignore state since it doesn't apply for non iOS | ||
$f7e14e656343df57$var$modifiedElementMap.set(target, target.style.userSelect); | ||
target.style.userSelect = 'none'; | ||
let property = 'userSelect' in target.style ? 'userSelect' : 'webkitUserSelect'; | ||
$f7e14e656343df57$var$modifiedElementMap.set(target, target.style[property]); | ||
target.style[property] = 'none'; | ||
} | ||
@@ -68,3 +69,4 @@ } | ||
let targetOldUserSelect = $f7e14e656343df57$var$modifiedElementMap.get(target); | ||
if (target.style.userSelect === 'none') target.style.userSelect = targetOldUserSelect; | ||
let property = 'userSelect' in target.style ? 'userSelect' : 'webkitUserSelect'; | ||
if (target.style[property] === 'none') target.style[property] = targetOldUserSelect; | ||
if (target.getAttribute('style') === '') target.removeAttribute('style'); | ||
@@ -71,0 +73,0 @@ $f7e14e656343df57$var$modifiedElementMap.delete(target); |
@@ -30,4 +30,5 @@ import {isIOS as $7R18e$isIOS, getOwnerDocument as $7R18e$getOwnerDocument, runAfterTransition as $7R18e$runAfterTransition} from "@react-aria/utils"; | ||
// Ignore state since it doesn't apply for non iOS | ||
$14c0b72509d70225$var$modifiedElementMap.set(target, target.style.userSelect); | ||
target.style.userSelect = 'none'; | ||
let property = 'userSelect' in target.style ? 'userSelect' : 'webkitUserSelect'; | ||
$14c0b72509d70225$var$modifiedElementMap.set(target, target.style[property]); | ||
target.style[property] = 'none'; | ||
} | ||
@@ -61,3 +62,4 @@ } | ||
let targetOldUserSelect = $14c0b72509d70225$var$modifiedElementMap.get(target); | ||
if (target.style.userSelect === 'none') target.style.userSelect = targetOldUserSelect; | ||
let property = 'userSelect' in target.style ? 'userSelect' : 'webkitUserSelect'; | ||
if (target.style[property] === 'none') target.style[property] = targetOldUserSelect; | ||
if (target.getAttribute('style') === '') target.removeAttribute('style'); | ||
@@ -64,0 +66,0 @@ $14c0b72509d70225$var$modifiedElementMap.delete(target); |
@@ -1,3 +0,3 @@ | ||
import { DOMAttributes, PressEvents, RefObject, FocusableElement, FocusEvents, HoverEvents, KeyboardEvents, MoveEvents, ScrollEvents, LongPressEvent } from "@react-types/shared"; | ||
import React, { ReactElement, ReactNode, FocusEvent } from "react"; | ||
import { DOMAttributes, PressEvents, RefObject, FocusableElement, FocusEvents, KeyboardEvents, FocusableDOMProps, FocusableProps, HoverEvents, MoveEvents, ScrollEvents, LongPressEvent } from "@react-types/shared"; | ||
import React, { MutableRefObject, ReactElement, ReactNode, FocusEvent } from "react"; | ||
export interface PressProps extends PressEvents { | ||
@@ -36,13 +36,2 @@ /** Whether the target is in a controlled press state (e.g. an overlay it triggers is open). */ | ||
export function usePress(props: PressHookProps): PressResult; | ||
interface PressableProps extends PressProps { | ||
children: ReactElement<DOMAttributes, string>; | ||
} | ||
export const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<HTMLElement>>; | ||
interface PressResponderProps extends PressProps { | ||
children: ReactNode; | ||
} | ||
export const PressResponder: React.ForwardRefExoticComponent<PressResponderProps & React.RefAttributes<FocusableElement>>; | ||
export function ClearPressResponder({ children }: { | ||
children: ReactNode; | ||
}): React.JSX.Element; | ||
export interface FocusProps<Target = FocusableElement> extends FocusEvents<Target> { | ||
@@ -61,2 +50,54 @@ /** Whether the focus events should be disabled. */ | ||
export function useFocus<Target extends FocusableElement = FocusableElement>(props: FocusProps<Target>): FocusResult<Target>; | ||
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; | ||
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>; | ||
} | ||
/** @private */ | ||
export let FocusableContext: React.Context<FocusableContextValue | null>; | ||
/** | ||
* Provides DOM props to the nearest focusable child. | ||
*/ | ||
export const FocusableProvider: React.ForwardRefExoticComponent<FocusableProviderProps & React.RefAttributes<FocusableElement>>; | ||
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; | ||
interface FocusableComponentProps extends FocusableOptions { | ||
children: ReactElement<DOMAttributes, string>; | ||
} | ||
export const Focusable: React.ForwardRefExoticComponent<FocusableComponentProps & React.RefAttributes<FocusableElement>>; | ||
interface PressableProps extends PressProps { | ||
children: ReactElement<DOMAttributes, string>; | ||
} | ||
export const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<FocusableElement>>; | ||
interface PressResponderProps extends PressProps { | ||
children: ReactNode; | ||
} | ||
export const PressResponder: React.ForwardRefExoticComponent<PressResponderProps & React.RefAttributes<FocusableElement>>; | ||
export function ClearPressResponder({ children }: { | ||
children: ReactNode; | ||
}): React.JSX.Element; | ||
export type Modality = 'keyboard' | 'pointer' | 'virtual'; | ||
@@ -156,14 +197,2 @@ export type FocusVisibleHandler = (isFocusVisible: boolean) => void; | ||
export function useInteractOutside(props: InteractOutsideProps): void; | ||
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; | ||
export interface MoveResult { | ||
@@ -219,4 +248,9 @@ /** Props to spread on the target element. */ | ||
export function useLongPress(props: LongPressProps): LongPressResult; | ||
/** | ||
* 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; | ||
export type { PressEvent, PressEvents, MoveStartEvent, MoveMoveEvent, MoveEndEvent, MoveEvents, HoverEvent, HoverEvents, FocusEvents, KeyboardEvents } from '@react-types/shared'; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -45,3 +45,4 @@ var $625cf83917e112ad$exports = require("./utils.main.js"); | ||
const ownerDocument = (0, $hrHul$reactariautils.getOwnerDocument)(e.target); | ||
if (e.target === e.currentTarget && ownerDocument.activeElement === e.target) { | ||
const activeElement = ownerDocument ? (0, $hrHul$reactariautils.getActiveElement)(ownerDocument) : (0, $hrHul$reactariautils.getActiveElement)(); | ||
if (e.target === e.currentTarget && activeElement === (0, $hrHul$reactariautils.getEventTarget)(e.nativeEvent)) { | ||
if (onFocusProp) onFocusProp(e); | ||
@@ -48,0 +49,0 @@ if (onFocusChange) onFocusChange(true); |
import {useSyntheticBlurEvent as $8a9cb279dc87e130$export$715c682d09d639cc} from "./utils.module.js"; | ||
import {useCallback as $hf0lj$useCallback} from "react"; | ||
import {getOwnerDocument as $hf0lj$getOwnerDocument} from "@react-aria/utils"; | ||
import {getOwnerDocument as $hf0lj$getOwnerDocument, getActiveElement as $hf0lj$getActiveElement, getEventTarget as $hf0lj$getEventTarget} from "@react-aria/utils"; | ||
@@ -39,3 +39,4 @@ /* | ||
const ownerDocument = (0, $hf0lj$getOwnerDocument)(e.target); | ||
if (e.target === e.currentTarget && ownerDocument.activeElement === e.target) { | ||
const activeElement = ownerDocument ? (0, $hf0lj$getActiveElement)(ownerDocument) : (0, $hf0lj$getActiveElement)(); | ||
if (e.target === e.currentTarget && activeElement === (0, $hf0lj$getEventTarget)(e.nativeEvent)) { | ||
if (onFocusProp) onFocusProp(e); | ||
@@ -42,0 +43,0 @@ if (onFocusChange) onFocusChange(true); |
@@ -0,1 +1,2 @@ | ||
var $625cf83917e112ad$exports = require("./utils.main.js"); | ||
var $cR3F8$reactariautils = require("@react-aria/utils"); | ||
@@ -34,2 +35,3 @@ var $cR3F8$react = require("react"); | ||
let $e77252a287ef94ab$var$currentModality = null; | ||
@@ -78,3 +80,3 @@ let $e77252a287ef94ab$var$changeHandlers = new Set(); | ||
// cause keyboard focus rings to appear. | ||
if (e.target === window || e.target === document) return; | ||
if (e.target === window || e.target === document || (0, $625cf83917e112ad$exports.ignoreFocusEvent) || !e.isTrusted) return; | ||
// If a focus event occurs without a preceding keyboard or pointer event, switch to virtual modality. | ||
@@ -90,2 +92,3 @@ // This occurs, for example, when navigating a form with the next/previous buttons on iOS. | ||
function $e77252a287ef94ab$var$handleWindowBlur() { | ||
if (0, $625cf83917e112ad$exports.ignoreFocusEvent) return; | ||
// When the window is blurred, reset state. This is necessary when tabbing out of the window, | ||
@@ -213,3 +216,3 @@ // for example, since a subsequent focus event won't be fired. | ||
*/ function $e77252a287ef94ab$var$isKeyboardFocusEvent(isTextInput, modality, e) { | ||
var _e_target; | ||
let document1 = (0, $cR3F8$reactariautils.getOwnerDocument)(e === null || e === void 0 ? void 0 : e.target); | ||
const IHTMLInputElement = typeof window !== 'undefined' ? (0, $cR3F8$reactariautils.getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).HTMLInputElement : HTMLInputElement; | ||
@@ -219,3 +222,5 @@ const IHTMLTextAreaElement = typeof window !== 'undefined' ? (0, $cR3F8$reactariautils.getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).HTMLTextAreaElement : HTMLTextAreaElement; | ||
const IKeyboardEvent = typeof window !== 'undefined' ? (0, $cR3F8$reactariautils.getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).KeyboardEvent : KeyboardEvent; | ||
isTextInput = isTextInput || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLInputElement && !$e77252a287ef94ab$var$nonTextInputTypes.has(e === null || e === void 0 ? void 0 : (_e_target = e.target) === null || _e_target === void 0 ? void 0 : _e_target.type) || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLTextAreaElement || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLElement && (e === null || e === void 0 ? void 0 : e.target.isContentEditable); | ||
// 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 || document1.activeElement instanceof IHTMLInputElement && !$e77252a287ef94ab$var$nonTextInputTypes.has(document1.activeElement.type) || document1.activeElement instanceof IHTMLTextAreaElement || document1.activeElement instanceof IHTMLElement && document1.activeElement.isContentEditable; | ||
return !(isTextInput && modality === 'keyboard' && e instanceof IKeyboardEvent && !$e77252a287ef94ab$var$FOCUS_VISIBLE_INPUT_KEYS[e.key]); | ||
@@ -241,2 +246,3 @@ } | ||
let handler = (modality, e)=>{ | ||
// We want to early return for any keyboard events that occur inside text inputs EXCEPT for Tab and Escape | ||
if (!$e77252a287ef94ab$var$isKeyboardFocusEvent(!!(opts === null || opts === void 0 ? void 0 : opts.isTextInput), modality, e)) return; | ||
@@ -243,0 +249,0 @@ fn($e77252a287ef94ab$export$b9b3dfddab17db27()); |
@@ -0,1 +1,2 @@ | ||
import {ignoreFocusEvent as $8a9cb279dc87e130$export$fda7da73ab5d4c48} from "./utils.module.js"; | ||
import {isMac as $28AnR$isMac, isVirtualClick as $28AnR$isVirtualClick, getOwnerWindow as $28AnR$getOwnerWindow, getOwnerDocument as $28AnR$getOwnerDocument} from "@react-aria/utils"; | ||
@@ -22,2 +23,3 @@ import {useState as $28AnR$useState, useEffect as $28AnR$useEffect} from "react"; | ||
let $507fabe10e71c6fb$var$currentModality = null; | ||
@@ -66,3 +68,3 @@ let $507fabe10e71c6fb$var$changeHandlers = new Set(); | ||
// cause keyboard focus rings to appear. | ||
if (e.target === window || e.target === document) return; | ||
if (e.target === window || e.target === document || (0, $8a9cb279dc87e130$export$fda7da73ab5d4c48) || !e.isTrusted) return; | ||
// If a focus event occurs without a preceding keyboard or pointer event, switch to virtual modality. | ||
@@ -78,2 +80,3 @@ // This occurs, for example, when navigating a form with the next/previous buttons on iOS. | ||
function $507fabe10e71c6fb$var$handleWindowBlur() { | ||
if (0, $8a9cb279dc87e130$export$fda7da73ab5d4c48) return; | ||
// When the window is blurred, reset state. This is necessary when tabbing out of the window, | ||
@@ -201,3 +204,3 @@ // for example, since a subsequent focus event won't be fired. | ||
*/ function $507fabe10e71c6fb$var$isKeyboardFocusEvent(isTextInput, modality, e) { | ||
var _e_target; | ||
let document1 = (0, $28AnR$getOwnerDocument)(e === null || e === void 0 ? void 0 : e.target); | ||
const IHTMLInputElement = typeof window !== 'undefined' ? (0, $28AnR$getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).HTMLInputElement : HTMLInputElement; | ||
@@ -207,3 +210,5 @@ const IHTMLTextAreaElement = typeof window !== 'undefined' ? (0, $28AnR$getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).HTMLTextAreaElement : HTMLTextAreaElement; | ||
const IKeyboardEvent = typeof window !== 'undefined' ? (0, $28AnR$getOwnerWindow)(e === null || e === void 0 ? void 0 : e.target).KeyboardEvent : KeyboardEvent; | ||
isTextInput = isTextInput || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLInputElement && !$507fabe10e71c6fb$var$nonTextInputTypes.has(e === null || e === void 0 ? void 0 : (_e_target = e.target) === null || _e_target === void 0 ? void 0 : _e_target.type) || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLTextAreaElement || (e === null || e === void 0 ? void 0 : e.target) instanceof IHTMLElement && (e === null || e === void 0 ? void 0 : e.target.isContentEditable); | ||
// 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 || document1.activeElement instanceof IHTMLInputElement && !$507fabe10e71c6fb$var$nonTextInputTypes.has(document1.activeElement.type) || document1.activeElement instanceof IHTMLTextAreaElement || document1.activeElement instanceof IHTMLElement && document1.activeElement.isContentEditable; | ||
return !(isTextInput && modality === 'keyboard' && e instanceof IKeyboardEvent && !$507fabe10e71c6fb$var$FOCUS_VISIBLE_INPUT_KEYS[e.key]); | ||
@@ -229,2 +234,3 @@ } | ||
let handler = (modality, e)=>{ | ||
// We want to early return for any keyboard events that occur inside text inputs EXCEPT for Tab and Escape | ||
if (!$507fabe10e71c6fb$var$isKeyboardFocusEvent(!!(opts === null || opts === void 0 ? void 0 : opts.isTextInput), modality, e)) return; | ||
@@ -231,0 +237,0 @@ fn($507fabe10e71c6fb$export$b9b3dfddab17db27()); |
var $625cf83917e112ad$exports = require("./utils.main.js"); | ||
var $kDAhS$react = require("react"); | ||
var $kDAhS$reactariautils = require("@react-aria/utils"); | ||
@@ -26,2 +27,3 @@ | ||
function $d16842bbd0359d1b$export$420e68273165f4ec(props) { | ||
@@ -32,3 +34,6 @@ let { isDisabled: isDisabled, onBlurWithin: onBlurWithin, onFocusWithin: onFocusWithin, onFocusWithinChange: onFocusWithinChange } = props; | ||
}); | ||
let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $kDAhS$reactariautils.useGlobalListeners)(); | ||
let onBlur = (0, $kDAhS$react.useCallback)((e)=>{ | ||
// Ignore events bubbling through portals. | ||
if (!e.currentTarget.contains(e.target)) return; | ||
// We don't want to trigger onBlurWithin and then immediately onFocusWithin again | ||
@@ -39,2 +44,3 @@ // when moving focus inside the element. Only trigger if the currentTarget doesn't | ||
state.current.isFocusWithin = false; | ||
removeAllGlobalListeners(); | ||
if (onBlurWithin) onBlurWithin(e); | ||
@@ -46,9 +52,14 @@ if (onFocusWithinChange) onFocusWithinChange(false); | ||
onFocusWithinChange, | ||
state | ||
state, | ||
removeAllGlobalListeners | ||
]); | ||
let onSyntheticFocus = (0, $625cf83917e112ad$exports.useSyntheticBlurEvent)(onBlur); | ||
let onFocus = (0, $kDAhS$react.useCallback)((e)=>{ | ||
// 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. | ||
if (!state.current.isFocusWithin && document.activeElement === e.target) { | ||
const ownerDocument = (0, $kDAhS$reactariautils.getOwnerDocument)(e.target); | ||
const activeElement = (0, $kDAhS$reactariautils.getActiveElement)(ownerDocument); | ||
if (!state.current.isFocusWithin && activeElement === (0, $kDAhS$reactariautils.getEventTarget)(e.nativeEvent)) { | ||
if (onFocusWithin) onFocusWithin(e); | ||
@@ -58,2 +69,18 @@ if (onFocusWithinChange) onFocusWithinChange(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 && !(0, $kDAhS$reactariautils.nodeContains)(currentTarget, e.target)) { | ||
let event = new (0, $625cf83917e112ad$exports.SyntheticFocusEvent)('blur', new ownerDocument.defaultView.FocusEvent('blur', { | ||
relatedTarget: e.target | ||
})); | ||
event.target = currentTarget; | ||
event.currentTarget = currentTarget; | ||
onBlur(event); | ||
} | ||
}, { | ||
capture: true | ||
}); | ||
} | ||
@@ -63,7 +90,9 @@ }, [ | ||
onFocusWithinChange, | ||
onSyntheticFocus | ||
onSyntheticFocus, | ||
addGlobalListener, | ||
onBlur | ||
]); | ||
if (isDisabled) return { | ||
focusWithinProps: { | ||
// These should not have been null, that would conflict in mergeProps | ||
// These cannot be null, that would conflict in mergeProps | ||
onFocus: undefined, | ||
@@ -70,0 +99,0 @@ onBlur: undefined |
@@ -1,3 +0,4 @@ | ||
import {useSyntheticBlurEvent as $8a9cb279dc87e130$export$715c682d09d639cc} from "./utils.module.js"; | ||
import {SyntheticFocusEvent as $8a9cb279dc87e130$export$905e7fc544a71f36, useSyntheticBlurEvent as $8a9cb279dc87e130$export$715c682d09d639cc} from "./utils.module.js"; | ||
import {useRef as $3b9Q0$useRef, useCallback as $3b9Q0$useCallback} from "react"; | ||
import {useGlobalListeners as $3b9Q0$useGlobalListeners, getOwnerDocument as $3b9Q0$getOwnerDocument, getActiveElement as $3b9Q0$getActiveElement, getEventTarget as $3b9Q0$getEventTarget, nodeContains as $3b9Q0$nodeContains} from "@react-aria/utils"; | ||
@@ -20,2 +21,3 @@ /* | ||
function $9ab94262bd0047c7$export$420e68273165f4ec(props) { | ||
@@ -26,3 +28,6 @@ let { isDisabled: isDisabled, onBlurWithin: onBlurWithin, onFocusWithin: onFocusWithin, onFocusWithinChange: onFocusWithinChange } = props; | ||
}); | ||
let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $3b9Q0$useGlobalListeners)(); | ||
let onBlur = (0, $3b9Q0$useCallback)((e)=>{ | ||
// Ignore events bubbling through portals. | ||
if (!e.currentTarget.contains(e.target)) return; | ||
// We don't want to trigger onBlurWithin and then immediately onFocusWithin again | ||
@@ -33,2 +38,3 @@ // when moving focus inside the element. Only trigger if the currentTarget doesn't | ||
state.current.isFocusWithin = false; | ||
removeAllGlobalListeners(); | ||
if (onBlurWithin) onBlurWithin(e); | ||
@@ -40,9 +46,14 @@ if (onFocusWithinChange) onFocusWithinChange(false); | ||
onFocusWithinChange, | ||
state | ||
state, | ||
removeAllGlobalListeners | ||
]); | ||
let onSyntheticFocus = (0, $8a9cb279dc87e130$export$715c682d09d639cc)(onBlur); | ||
let onFocus = (0, $3b9Q0$useCallback)((e)=>{ | ||
// 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. | ||
if (!state.current.isFocusWithin && document.activeElement === e.target) { | ||
const ownerDocument = (0, $3b9Q0$getOwnerDocument)(e.target); | ||
const activeElement = (0, $3b9Q0$getActiveElement)(ownerDocument); | ||
if (!state.current.isFocusWithin && activeElement === (0, $3b9Q0$getEventTarget)(e.nativeEvent)) { | ||
if (onFocusWithin) onFocusWithin(e); | ||
@@ -52,2 +63,18 @@ if (onFocusWithinChange) onFocusWithinChange(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 && !(0, $3b9Q0$nodeContains)(currentTarget, e.target)) { | ||
let event = new (0, $8a9cb279dc87e130$export$905e7fc544a71f36)('blur', new ownerDocument.defaultView.FocusEvent('blur', { | ||
relatedTarget: e.target | ||
})); | ||
event.target = currentTarget; | ||
event.currentTarget = currentTarget; | ||
onBlur(event); | ||
} | ||
}, { | ||
capture: true | ||
}); | ||
} | ||
@@ -57,7 +84,9 @@ }, [ | ||
onFocusWithinChange, | ||
onSyntheticFocus | ||
onSyntheticFocus, | ||
addGlobalListener, | ||
onBlur | ||
]); | ||
if (isDisabled) return { | ||
focusWithinProps: { | ||
// These should not have been null, that would conflict in mergeProps | ||
// These cannot be null, that would conflict in mergeProps | ||
onFocus: undefined, | ||
@@ -64,0 +93,0 @@ onBlur: undefined |
@@ -0,1 +1,2 @@ | ||
var $82z6W$reactariautils = require("@react-aria/utils"); | ||
var $82z6W$react = require("react"); | ||
@@ -24,2 +25,3 @@ | ||
// iOS fires onPointerEnter twice: once with pointerType="touch" and again with pointerType="mouse". | ||
@@ -65,2 +67,3 @@ // We want to ignore these emulated events so they do not trigger hover behavior. | ||
(0, $82z6W$react.useEffect)($ffbc150311c75f01$var$setupGlobalTouchEvents, []); | ||
let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $82z6W$reactariautils.useGlobalListeners)(); | ||
let { hoverProps: hoverProps, triggerHoverEnd: triggerHoverEnd } = (0, $82z6W$react.useMemo)(()=>{ | ||
@@ -73,2 +76,11 @@ let triggerHoverStart = (event, pointerType)=>{ | ||
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((0, $82z6W$reactariautils.getOwnerDocument)(event.target), 'pointerover', (e)=>{ | ||
if (state.isHovered && state.target && !(0, $82z6W$reactariautils.nodeContains)(state.target, e.target)) triggerHoverEnd(e, e.pointerType); | ||
}, { | ||
capture: true | ||
}); | ||
if (onHoverStart) onHoverStart({ | ||
@@ -83,7 +95,8 @@ type: 'hoverstart', | ||
let triggerHoverEnd = (event, pointerType)=>{ | ||
let target = state.target; | ||
state.pointerType = ''; | ||
state.target = null; | ||
if (pointerType === 'touch' || !state.isHovered) return; | ||
if (pointerType === 'touch' || !state.isHovered || !target) return; | ||
state.isHovered = false; | ||
let target = event.currentTarget; | ||
removeAllGlobalListeners(); | ||
if (onHoverEnd) onHoverEnd({ | ||
@@ -127,3 +140,5 @@ type: 'hoverend', | ||
isDisabled, | ||
state | ||
state, | ||
addGlobalListener, | ||
removeAllGlobalListeners | ||
]); | ||
@@ -130,0 +145,0 @@ (0, $82z6W$react.useEffect)(()=>{ |
@@ -0,1 +1,2 @@ | ||
import {useGlobalListeners as $AWxnT$useGlobalListeners, getOwnerDocument as $AWxnT$getOwnerDocument, nodeContains as $AWxnT$nodeContains} from "@react-aria/utils"; | ||
import {useState as $AWxnT$useState, useRef as $AWxnT$useRef, useEffect as $AWxnT$useEffect, useMemo as $AWxnT$useMemo} from "react"; | ||
@@ -18,2 +19,3 @@ | ||
// iOS fires onPointerEnter twice: once with pointerType="touch" and again with pointerType="mouse". | ||
@@ -59,2 +61,3 @@ // We want to ignore these emulated events so they do not trigger hover behavior. | ||
(0, $AWxnT$useEffect)($6179b936705e76d3$var$setupGlobalTouchEvents, []); | ||
let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $AWxnT$useGlobalListeners)(); | ||
let { hoverProps: hoverProps, triggerHoverEnd: triggerHoverEnd } = (0, $AWxnT$useMemo)(()=>{ | ||
@@ -67,2 +70,11 @@ let triggerHoverStart = (event, pointerType)=>{ | ||
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((0, $AWxnT$getOwnerDocument)(event.target), 'pointerover', (e)=>{ | ||
if (state.isHovered && state.target && !(0, $AWxnT$nodeContains)(state.target, e.target)) triggerHoverEnd(e, e.pointerType); | ||
}, { | ||
capture: true | ||
}); | ||
if (onHoverStart) onHoverStart({ | ||
@@ -77,7 +89,8 @@ type: 'hoverstart', | ||
let triggerHoverEnd = (event, pointerType)=>{ | ||
let target = state.target; | ||
state.pointerType = ''; | ||
state.target = null; | ||
if (pointerType === 'touch' || !state.isHovered) return; | ||
if (pointerType === 'touch' || !state.isHovered || !target) return; | ||
state.isHovered = false; | ||
let target = event.currentTarget; | ||
removeAllGlobalListeners(); | ||
if (onHoverEnd) onHoverEnd({ | ||
@@ -121,3 +134,5 @@ type: 'hoverend', | ||
isDisabled, | ||
state | ||
state, | ||
addGlobalListener, | ||
removeAllGlobalListeners | ||
]); | ||
@@ -124,0 +139,0 @@ (0, $AWxnT$useEffect)(()=>{ |
@@ -97,3 +97,8 @@ var $9Icr4$reactariautils = require("@react-aria/utils"); | ||
} | ||
return ref.current && !ref.current.contains(event.target); | ||
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); | ||
} | ||
@@ -100,0 +105,0 @@ |
@@ -91,3 +91,8 @@ import {useEffectEvent as $ispOf$useEffectEvent, getOwnerDocument as $ispOf$getOwnerDocument} from "@react-aria/utils"; | ||
} | ||
return ref.current && !ref.current.contains(event.target); | ||
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); | ||
} | ||
@@ -94,0 +99,0 @@ |
@@ -43,2 +43,4 @@ var $0294ea432cd92340$exports = require("./usePress.main.js"); | ||
})); | ||
// Ensure target is focused. On touch devices, browsers typically focus on pointer up. | ||
if ((0, $5sxTM$reactariautils.getOwnerDocument)(e.target).activeElement !== e.target) (0, $5sxTM$reactariautils.focusWithoutScrolling)(e.target); | ||
if (onLongPress) onLongPress({ | ||
@@ -45,0 +47,0 @@ ...e, |
import {usePress as $f6c31cce2adf654f$export$45712eceda6fad21} from "./usePress.module.js"; | ||
import {useGlobalListeners as $4k2kv$useGlobalListeners, useDescription as $4k2kv$useDescription, mergeProps as $4k2kv$mergeProps} from "@react-aria/utils"; | ||
import {useGlobalListeners as $4k2kv$useGlobalListeners, getOwnerDocument as $4k2kv$getOwnerDocument, focusWithoutScrolling as $4k2kv$focusWithoutScrolling, useDescription as $4k2kv$useDescription, mergeProps as $4k2kv$mergeProps} from "@react-aria/utils"; | ||
import {useRef as $4k2kv$useRef} from "react"; | ||
@@ -37,2 +37,4 @@ | ||
})); | ||
// Ensure target is focused. On touch devices, browsers typically focus on pointer up. | ||
if ((0, $4k2kv$getOwnerDocument)(e.target).activeElement !== e.target) (0, $4k2kv$focusWithoutScrolling)(e.target); | ||
if (onLongPress) onLongPress({ | ||
@@ -39,0 +41,0 @@ ...e, |
var $f7e14e656343df57$exports = require("./textSelection.main.js"); | ||
var $01d3f539e91688c8$exports = require("./context.main.js"); | ||
var $625cf83917e112ad$exports = require("./utils.main.js"); | ||
var $bBqCQ$swchelperscjs_class_private_field_getcjs = require("@swc/helpers/cjs/_class_private_field_get.cjs"); | ||
@@ -7,2 +8,3 @@ var $bBqCQ$swchelperscjs_class_private_field_initcjs = require("@swc/helpers/cjs/_class_private_field_init.cjs"); | ||
var $bBqCQ$reactariautils = require("@react-aria/utils"); | ||
var $bBqCQ$reactdom = require("react-dom"); | ||
var $bBqCQ$react = require("react"); | ||
@@ -37,2 +39,4 @@ | ||
function $0294ea432cd92340$var$usePressResponderContext(props) { | ||
@@ -100,3 +104,2 @@ // Consume context from <PressResponder> and merge with props. | ||
ignoreEmulatedMouseEvents: false, | ||
ignoreClickAfterPress: false, | ||
didFirePressStart: false, | ||
@@ -107,3 +110,4 @@ isTriggeringEvent: false, | ||
isOverTarget: false, | ||
pointerType: null | ||
pointerType: null, | ||
disposables: [] | ||
}); | ||
@@ -130,3 +134,2 @@ let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $bBqCQ$reactariautils.useGlobalListeners)(); | ||
if (!state.didFirePressStart) return false; | ||
state.ignoreClickAfterPress = true; | ||
state.didFirePressStart = false; | ||
@@ -165,3 +168,3 @@ state.isTriggeringEvent = true; | ||
if (state.isPressed && state.target) { | ||
if (state.isOverTarget && state.pointerType != null) triggerPressEnd($0294ea432cd92340$var$createEvent(state.target, e), state.pointerType, false); | ||
if (state.didFirePressStart && state.pointerType != null) triggerPressEnd($0294ea432cd92340$var$createEvent(state.target, e), state.pointerType, false); | ||
state.isPressed = false; | ||
@@ -173,2 +176,4 @@ state.isOverTarget = false; | ||
if (!allowTextSelectionOnPress) (0, $f7e14e656343df57$exports.restoreTextSelection)(state.target); | ||
for (let dispose of state.disposables)dispose(); | ||
state.disposables = []; | ||
} | ||
@@ -183,5 +188,5 @@ }); | ||
onKeyDown (e) { | ||
if ($0294ea432cd92340$var$isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && e.currentTarget.contains(e.target)) { | ||
if ($0294ea432cd92340$var$isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && (0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) { | ||
var _state_metaKeyEvents; | ||
if ($0294ea432cd92340$var$shouldPreventDefaultKeyboard(e.target, e.key)) e.preventDefault(); | ||
if ($0294ea432cd92340$var$shouldPreventDefaultKeyboard((0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent), e.key)) e.preventDefault(); | ||
// If the event is repeating, it may have started on a different element | ||
@@ -194,2 +199,3 @@ // after which focus moved to the current element. Ignore these events and | ||
state.isPressed = true; | ||
state.pointerType = 'keyboard'; | ||
shouldStopPropagation = triggerPressStart(e, 'keyboard'); | ||
@@ -201,3 +207,3 @@ // Focus may move before the key up event, so register the event on the document | ||
let pressUp = (e)=>{ | ||
if ($0294ea432cd92340$var$isValidKeyboardEvent(e, originalTarget) && !e.repeat && originalTarget.contains(e.target) && state.target) triggerPressUp($0294ea432cd92340$var$createEvent(state.target, e), 'keyboard'); | ||
if ($0294ea432cd92340$var$isValidKeyboardEvent(e, originalTarget) && !e.repeat && (0, $bBqCQ$reactariautils.nodeContains)(originalTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e)) && state.target) triggerPressUp($0294ea432cd92340$var$createEvent(state.target, e), 'keyboard'); | ||
}; | ||
@@ -218,3 +224,3 @@ addGlobalListener((0, $bBqCQ$reactariautils.getOwnerDocument)(e.currentTarget), 'keyup', (0, $bBqCQ$reactariautils.chain)(pressUp, onKeyUp), true); | ||
onClick (e) { | ||
if (e && !e.currentTarget.contains(e.target)) return; | ||
if (e && !(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
if (e && e.button === 0 && !state.isTriggeringEvent && !(0, $bBqCQ$reactariautils.openLink).isOpening) { | ||
@@ -225,5 +231,3 @@ let shouldStopPropagation = true; | ||
// trigger as if it were a keyboard click. | ||
if (!state.ignoreClickAfterPress && !state.ignoreEmulatedMouseEvents && !state.isPressed && (state.pointerType === 'virtual' || (0, $bBqCQ$reactariautils.isVirtualClick)(e.nativeEvent))) { | ||
// Ensure the element receives focus (VoiceOver on iOS does not do this) | ||
if (!isDisabled && !preventFocusOnPress) (0, $bBqCQ$reactariautils.focusWithoutScrolling)(e.currentTarget); | ||
if (!state.ignoreEmulatedMouseEvents && !state.isPressed && (state.pointerType === 'virtual' || (0, $bBqCQ$reactariautils.isVirtualClick)(e.nativeEvent))) { | ||
let stopPressStart = triggerPressStart(e, 'virtual'); | ||
@@ -233,5 +237,9 @@ let stopPressUp = triggerPressUp(e, 'virtual'); | ||
shouldStopPropagation = stopPressStart && stopPressUp && stopPressEnd; | ||
} else if (state.isPressed && state.pointerType !== 'keyboard') { | ||
let pointerType = state.pointerType || e.nativeEvent.pointerType || 'virtual'; | ||
shouldStopPropagation = triggerPressEnd($0294ea432cd92340$var$createEvent(e.currentTarget, e), pointerType, true); | ||
state.isOverTarget = false; | ||
cancel(e); | ||
} | ||
state.ignoreEmulatedMouseEvents = false; | ||
state.ignoreClickAfterPress = false; | ||
if (shouldStopPropagation) e.stopPropagation(); | ||
@@ -245,5 +253,5 @@ } | ||
var _state_metaKeyEvents1; | ||
if ($0294ea432cd92340$var$shouldPreventDefaultKeyboard(e.target, e.key)) e.preventDefault(); | ||
let target = e.target; | ||
triggerPressEnd($0294ea432cd92340$var$createEvent(state.target, e), 'keyboard', state.target.contains(target)); | ||
if ($0294ea432cd92340$var$shouldPreventDefaultKeyboard((0, $bBqCQ$reactariautils.getEventTarget)(e), e.key)) e.preventDefault(); | ||
let target = (0, $bBqCQ$reactariautils.getEventTarget)(e); | ||
triggerPressEnd($0294ea432cd92340$var$createEvent(state.target, e), 'keyboard', (0, $bBqCQ$reactariautils.nodeContains)(state.target, (0, $bBqCQ$reactariautils.getEventTarget)(e))); | ||
removeAllGlobalListeners(); | ||
@@ -253,3 +261,3 @@ // If a link was triggered with a key other than Enter, open the URL ourselves. | ||
// only applies when using the Enter key. | ||
if (e.key !== 'Enter' && $0294ea432cd92340$var$isHTMLAnchorLink(state.target) && state.target.contains(target) && !e[$0294ea432cd92340$var$LINK_CLICKED]) { | ||
if (e.key !== 'Enter' && $0294ea432cd92340$var$isHTMLAnchorLink(state.target) && (0, $bBqCQ$reactariautils.nodeContains)(state.target, target) && !e[$0294ea432cd92340$var$LINK_CLICKED]) { | ||
// Store a hidden property on the event so we only trigger link click once, | ||
@@ -275,3 +283,3 @@ // even if there are multiple usePress instances attached to the element. | ||
// Only handle left clicks, and ignore events that bubbled through portals. | ||
if (e.button !== 0 || !e.currentTarget.contains(e.target)) return; | ||
if (e.button !== 0 || !(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
// iOS safari fires pointer events from VoiceOver with incorrect coordinates/target. | ||
@@ -285,5 +293,2 @@ // Ignore and let the onClick handler take care of it instead. | ||
} | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent | ||
// default on pointer down and handle focusing the pressable element ourselves. | ||
if ($0294ea432cd92340$var$shouldPreventDefaultDown(e.currentTarget)) e.preventDefault(); | ||
state.pointerType = e.pointerType; | ||
@@ -296,3 +301,2 @@ let shouldStopPropagation = true; | ||
state.target = e.currentTarget; | ||
if (!isDisabled && !preventFocusOnPress) (0, $bBqCQ$reactariautils.focusWithoutScrolling)(e.currentTarget); | ||
if (!allowTextSelectionOnPress) (0, $f7e14e656343df57$exports.disableTextSelection)(state.target); | ||
@@ -302,3 +306,3 @@ shouldStopPropagation = triggerPressStart(e, state.pointerType); | ||
// This enables onPointerLeave and onPointerEnter to fire. | ||
let target = e.target; | ||
let target = (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent); | ||
if ('releasePointerCapture' in target) target.releasePointerCapture(e.pointerId); | ||
@@ -311,8 +315,8 @@ addGlobalListener((0, $bBqCQ$reactariautils.getOwnerDocument)(e.currentTarget), 'pointerup', onPointerUp, false); | ||
pressProps.onMouseDown = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
if (e.button === 0) { | ||
// Chrome and Firefox on touch Windows devices require mouse down events | ||
// to be canceled in addition to pointer events, or an extra asynchronous | ||
// focus event will be fired. | ||
if ($0294ea432cd92340$var$shouldPreventDefaultDown(e.currentTarget)) e.preventDefault(); | ||
if (preventFocusOnPress) { | ||
let dispose = (0, $625cf83917e112ad$exports.preventFocus)(e.target); | ||
if (dispose) state.disposables.push(dispose); | ||
} | ||
e.stopPropagation(); | ||
@@ -323,3 +327,3 @@ } | ||
// iOS fires pointerup with zero width and height, so check the pointerType recorded during pointerdown. | ||
if (!e.currentTarget.contains(e.target) || state.pointerType === 'virtual') return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent)) || state.pointerType === 'virtual') return; | ||
// Only handle left clicks | ||
@@ -343,27 +347,32 @@ if (e.button === 0) triggerPressUp(e, state.pointerType || e.pointerType); | ||
if (e.pointerId === state.activePointerId && state.isPressed && e.button === 0 && state.target) { | ||
if (state.target.contains(e.target) && state.pointerType != null) triggerPressEnd($0294ea432cd92340$var$createEvent(state.target, e), state.pointerType); | ||
else if (state.isOverTarget && state.pointerType != null) triggerPressEnd($0294ea432cd92340$var$createEvent(state.target, e), state.pointerType, false); | ||
state.isPressed = false; | ||
if ((0, $bBqCQ$reactariautils.nodeContains)(state.target, (0, $bBqCQ$reactariautils.getEventTarget)(e)) && state.pointerType != null) { | ||
// Wait for onClick to fire onPress. This avoids browser issues when the DOM | ||
// is mutated between onPointerUp and onClick, and is more compatible with third party libraries. | ||
// https://github.com/adobe/react-spectrum/issues/1513 | ||
// https://issues.chromium.org/issues/40732224 | ||
// However, iOS and Android do not focus or fire onClick after a long press. | ||
// We work around this by triggering a click ourselves after a timeout. | ||
// This timeout is canceled during the click event in case the real one fires first. | ||
// The timeout must be at least 32ms, because Safari on iOS delays the click event on | ||
// non-form elements without certain ARIA roles (for hover emulation). | ||
// https://github.com/WebKit/WebKit/blob/dccfae42bb29bd4bdef052e469f604a9387241c0/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L875-L892 | ||
let clicked = false; | ||
let timeout = setTimeout(()=>{ | ||
if (state.isPressed && state.target instanceof HTMLElement) { | ||
if (clicked) cancel(e); | ||
else { | ||
(0, $bBqCQ$reactariautils.focusWithoutScrolling)(state.target); | ||
state.target.click(); | ||
} | ||
} | ||
}, 80); | ||
// Use a capturing listener to track if a click occurred. | ||
// If stopPropagation is called it may never reach our handler. | ||
addGlobalListener(e.currentTarget, 'click', ()=>clicked = true, true); | ||
state.disposables.push(()=>clearTimeout(timeout)); | ||
} else cancel(e); | ||
// Ignore subsequent onPointerLeave event before onClick on touch devices. | ||
state.isOverTarget = false; | ||
state.activePointerId = null; | ||
state.pointerType = null; | ||
removeAllGlobalListeners(); | ||
if (!allowTextSelectionOnPress) (0, $f7e14e656343df57$exports.restoreTextSelection)(state.target); | ||
// Prevent subsequent touchend event from triggering onClick on unrelated elements on Android. See below. | ||
// Both 'touch' and 'pen' pointerTypes trigger onTouchEnd, but 'mouse' does not. | ||
if ('ontouchend' in state.target && e.pointerType !== 'mouse') addGlobalListener(state.target, 'touchend', onTouchEnd, { | ||
once: true | ||
}); | ||
} | ||
}; | ||
// This is a workaround for an Android Chrome/Firefox issue where click events are fired on an incorrect element | ||
// if the original target is removed during onPointerUp (before onClick). | ||
// https://github.com/adobe/react-spectrum/issues/1513 | ||
// https://issues.chromium.org/issues/40732224 | ||
// Note: this event must be registered directly on the element, not via React props in order to work. | ||
// https://github.com/facebook/react/issues/9809 | ||
let onTouchEnd = (e)=>{ | ||
// Don't preventDefault if we actually want the default (e.g. submit/link click). | ||
if ($0294ea432cd92340$var$shouldPreventDefaultUp(e.currentTarget)) e.preventDefault(); | ||
}; | ||
let onPointerCancel = (e)=>{ | ||
@@ -373,3 +382,3 @@ cancel(e); | ||
pressProps.onDragStart = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
// Safari does not call onPointerCancel when a drag starts, whereas Chrome and Firefox do. | ||
@@ -379,8 +388,7 @@ cancel(e); | ||
} else { | ||
// NOTE: this fallback branch is almost entirely used by unit tests. | ||
// All browsers now support pointer events, but JSDOM still does not. | ||
pressProps.onMouseDown = (e)=>{ | ||
// Only handle left clicks | ||
if (e.button !== 0 || !e.currentTarget.contains(e.target)) return; | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent | ||
// default on mouse down and handle focusing the pressable element ourselves. | ||
if ($0294ea432cd92340$var$shouldPreventDefaultDown(e.currentTarget)) e.preventDefault(); | ||
if (e.button !== 0 || !(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
if (state.ignoreEmulatedMouseEvents) { | ||
@@ -394,9 +402,13 @@ e.stopPropagation(); | ||
state.pointerType = (0, $bBqCQ$reactariautils.isVirtualClick)(e.nativeEvent) ? 'virtual' : 'mouse'; | ||
if (!isDisabled && !preventFocusOnPress) (0, $bBqCQ$reactariautils.focusWithoutScrolling)(e.currentTarget); | ||
let shouldStopPropagation = triggerPressStart(e, state.pointerType); | ||
// Flush sync so that focus moved during react re-renders occurs before we yield back to the browser. | ||
let shouldStopPropagation = (0, $bBqCQ$reactdom.flushSync)(()=>triggerPressStart(e, state.pointerType)); | ||
if (shouldStopPropagation) e.stopPropagation(); | ||
if (preventFocusOnPress) { | ||
let dispose = (0, $625cf83917e112ad$exports.preventFocus)(e.target); | ||
if (dispose) state.disposables.push(dispose); | ||
} | ||
addGlobalListener((0, $bBqCQ$reactariautils.getOwnerDocument)(e.currentTarget), 'mouseup', onMouseUp, false); | ||
}; | ||
pressProps.onMouseEnter = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
let shouldStopPropagation = true; | ||
@@ -410,3 +422,3 @@ if (state.isPressed && !state.ignoreEmulatedMouseEvents && state.pointerType != null) { | ||
pressProps.onMouseLeave = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
let shouldStopPropagation = true; | ||
@@ -421,3 +433,3 @@ if (state.isPressed && !state.ignoreEmulatedMouseEvents && state.pointerType != null) { | ||
pressProps.onMouseUp = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
if (!state.ignoreEmulatedMouseEvents && e.button === 0) triggerPressUp(e, state.pointerType || 'mouse'); | ||
@@ -428,4 +440,2 @@ }; | ||
if (e.button !== 0) return; | ||
state.isPressed = false; | ||
removeAllGlobalListeners(); | ||
if (state.ignoreEmulatedMouseEvents) { | ||
@@ -435,8 +445,8 @@ state.ignoreEmulatedMouseEvents = false; | ||
} | ||
if (state.target && $0294ea432cd92340$var$isOverTarget(e, state.target) && state.pointerType != null) triggerPressEnd($0294ea432cd92340$var$createEvent(state.target, e), state.pointerType); | ||
else if (state.target && state.isOverTarget && state.pointerType != null) triggerPressEnd($0294ea432cd92340$var$createEvent(state.target, e), state.pointerType, false); | ||
if (state.target && state.target.contains(e.target) && state.pointerType != null) ; | ||
else cancel(e); | ||
state.isOverTarget = false; | ||
}; | ||
pressProps.onTouchStart = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
let touch = $0294ea432cd92340$var$getTouchFromEvent(e.nativeEvent); | ||
@@ -450,5 +460,2 @@ if (!touch) return; | ||
state.pointerType = 'touch'; | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent default | ||
// on the emulated mouse event and handle focusing the pressable element ourselves. | ||
if (!isDisabled && !preventFocusOnPress) (0, $bBqCQ$reactariautils.focusWithoutScrolling)(e.currentTarget); | ||
if (!allowTextSelectionOnPress) (0, $f7e14e656343df57$exports.disableTextSelection)(state.target); | ||
@@ -460,3 +467,3 @@ let shouldStopPropagation = triggerPressStart($0294ea432cd92340$var$createTouchEvent(state.target, e), state.pointerType); | ||
pressProps.onTouchMove = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
if (!state.isPressed) { | ||
@@ -481,3 +488,3 @@ e.stopPropagation(); | ||
pressProps.onTouchEnd = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
if (!state.isPressed) { | ||
@@ -502,3 +509,3 @@ e.stopPropagation(); | ||
pressProps.onTouchCancel = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
e.stopPropagation(); | ||
@@ -508,3 +515,3 @@ if (state.isPressed) cancel($0294ea432cd92340$var$createTouchEvent(state.target, e)); | ||
let onScroll = (e)=>{ | ||
if (state.isPressed && e.target.contains(state.target)) cancel({ | ||
if (state.isPressed && (0, $bBqCQ$reactariautils.nodeContains)((0, $bBqCQ$reactariautils.getEventTarget)(e), state.target)) cancel({ | ||
currentTarget: state.target, | ||
@@ -518,3 +525,3 @@ shiftKey: false, | ||
pressProps.onDragStart = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $bBqCQ$reactariautils.nodeContains)(e.currentTarget, (0, $bBqCQ$reactariautils.getEventTarget)(e.nativeEvent))) return; | ||
cancel(e); | ||
@@ -538,6 +545,8 @@ }; | ||
(0, $bBqCQ$react.useEffect)(()=>{ | ||
let state = ref.current; | ||
return ()=>{ | ||
var _ref_current_target; | ||
if (!allowTextSelectionOnPress) // eslint-disable-next-line react-hooks/exhaustive-deps | ||
(0, $f7e14e656343df57$exports.restoreTextSelection)((_ref_current_target = ref.current.target) !== null && _ref_current_target !== void 0 ? _ref_current_target : undefined); | ||
var _state_target; | ||
if (!allowTextSelectionOnPress) (0, $f7e14e656343df57$exports.restoreTextSelection)((_state_target = state.target) !== null && _state_target !== void 0 ? _state_target : undefined); | ||
for (let dispose of state.disposables)dispose(); | ||
state.disposables = []; | ||
}; | ||
@@ -633,6 +642,2 @@ }, [ | ||
} | ||
function $0294ea432cd92340$var$shouldPreventDefaultDown(target) { | ||
// We cannot prevent default if the target is a draggable element. | ||
return !(target instanceof HTMLElement) || !target.hasAttribute('draggable'); | ||
} | ||
function $0294ea432cd92340$var$shouldPreventDefaultUp(target) { | ||
@@ -639,0 +644,0 @@ if (target instanceof HTMLInputElement) return false; |
import {disableTextSelection as $14c0b72509d70225$export$16a4697467175487, restoreTextSelection as $14c0b72509d70225$export$b0d6fa1ab32e3295} from "./textSelection.module.js"; | ||
import {PressResponderContext as $ae1eeba8b9eafd08$export$5165eccb35aaadb5} from "./context.module.js"; | ||
import {preventFocus as $8a9cb279dc87e130$export$cabe61c495ee3649} from "./utils.module.js"; | ||
import {_ as $7mdmh$_} from "@swc/helpers/_/_class_private_field_get"; | ||
import {_ as $7mdmh$_1} from "@swc/helpers/_/_class_private_field_init"; | ||
import {_ as $7mdmh$_2} from "@swc/helpers/_/_class_private_field_set"; | ||
import {mergeProps as $7mdmh$mergeProps, useSyncRef as $7mdmh$useSyncRef, useGlobalListeners as $7mdmh$useGlobalListeners, useEffectEvent as $7mdmh$useEffectEvent, getOwnerDocument as $7mdmh$getOwnerDocument, chain as $7mdmh$chain, isMac as $7mdmh$isMac, openLink as $7mdmh$openLink, isVirtualClick as $7mdmh$isVirtualClick, focusWithoutScrolling as $7mdmh$focusWithoutScrolling, isVirtualPointerEvent as $7mdmh$isVirtualPointerEvent, getOwnerWindow as $7mdmh$getOwnerWindow} from "@react-aria/utils"; | ||
import {mergeProps as $7mdmh$mergeProps, useSyncRef as $7mdmh$useSyncRef, useGlobalListeners as $7mdmh$useGlobalListeners, useEffectEvent as $7mdmh$useEffectEvent, nodeContains as $7mdmh$nodeContains, getEventTarget as $7mdmh$getEventTarget, getOwnerDocument as $7mdmh$getOwnerDocument, chain as $7mdmh$chain, isMac as $7mdmh$isMac, openLink as $7mdmh$openLink, isVirtualClick as $7mdmh$isVirtualClick, isVirtualPointerEvent as $7mdmh$isVirtualPointerEvent, focusWithoutScrolling as $7mdmh$focusWithoutScrolling, getOwnerWindow as $7mdmh$getOwnerWindow} from "@react-aria/utils"; | ||
import {flushSync as $7mdmh$flushSync} from "react-dom"; | ||
import {useContext as $7mdmh$useContext, useState as $7mdmh$useState, useRef as $7mdmh$useRef, useMemo as $7mdmh$useMemo, useEffect as $7mdmh$useEffect} from "react"; | ||
@@ -30,2 +32,4 @@ | ||
function $f6c31cce2adf654f$var$usePressResponderContext(props) { | ||
@@ -93,3 +97,2 @@ // Consume context from <PressResponder> and merge with props. | ||
ignoreEmulatedMouseEvents: false, | ||
ignoreClickAfterPress: false, | ||
didFirePressStart: false, | ||
@@ -100,3 +103,4 @@ isTriggeringEvent: false, | ||
isOverTarget: false, | ||
pointerType: null | ||
pointerType: null, | ||
disposables: [] | ||
}); | ||
@@ -123,3 +127,2 @@ let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $7mdmh$useGlobalListeners)(); | ||
if (!state.didFirePressStart) return false; | ||
state.ignoreClickAfterPress = true; | ||
state.didFirePressStart = false; | ||
@@ -158,3 +161,3 @@ state.isTriggeringEvent = true; | ||
if (state.isPressed && state.target) { | ||
if (state.isOverTarget && state.pointerType != null) triggerPressEnd($f6c31cce2adf654f$var$createEvent(state.target, e), state.pointerType, false); | ||
if (state.didFirePressStart && state.pointerType != null) triggerPressEnd($f6c31cce2adf654f$var$createEvent(state.target, e), state.pointerType, false); | ||
state.isPressed = false; | ||
@@ -166,2 +169,4 @@ state.isOverTarget = false; | ||
if (!allowTextSelectionOnPress) (0, $14c0b72509d70225$export$b0d6fa1ab32e3295)(state.target); | ||
for (let dispose of state.disposables)dispose(); | ||
state.disposables = []; | ||
} | ||
@@ -176,5 +181,5 @@ }); | ||
onKeyDown (e) { | ||
if ($f6c31cce2adf654f$var$isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && e.currentTarget.contains(e.target)) { | ||
if ($f6c31cce2adf654f$var$isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && (0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) { | ||
var _state_metaKeyEvents; | ||
if ($f6c31cce2adf654f$var$shouldPreventDefaultKeyboard(e.target, e.key)) e.preventDefault(); | ||
if ($f6c31cce2adf654f$var$shouldPreventDefaultKeyboard((0, $7mdmh$getEventTarget)(e.nativeEvent), e.key)) e.preventDefault(); | ||
// If the event is repeating, it may have started on a different element | ||
@@ -187,2 +192,3 @@ // after which focus moved to the current element. Ignore these events and | ||
state.isPressed = true; | ||
state.pointerType = 'keyboard'; | ||
shouldStopPropagation = triggerPressStart(e, 'keyboard'); | ||
@@ -194,3 +200,3 @@ // Focus may move before the key up event, so register the event on the document | ||
let pressUp = (e)=>{ | ||
if ($f6c31cce2adf654f$var$isValidKeyboardEvent(e, originalTarget) && !e.repeat && originalTarget.contains(e.target) && state.target) triggerPressUp($f6c31cce2adf654f$var$createEvent(state.target, e), 'keyboard'); | ||
if ($f6c31cce2adf654f$var$isValidKeyboardEvent(e, originalTarget) && !e.repeat && (0, $7mdmh$nodeContains)(originalTarget, (0, $7mdmh$getEventTarget)(e)) && state.target) triggerPressUp($f6c31cce2adf654f$var$createEvent(state.target, e), 'keyboard'); | ||
}; | ||
@@ -211,3 +217,3 @@ addGlobalListener((0, $7mdmh$getOwnerDocument)(e.currentTarget), 'keyup', (0, $7mdmh$chain)(pressUp, onKeyUp), true); | ||
onClick (e) { | ||
if (e && !e.currentTarget.contains(e.target)) return; | ||
if (e && !(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
if (e && e.button === 0 && !state.isTriggeringEvent && !(0, $7mdmh$openLink).isOpening) { | ||
@@ -218,5 +224,3 @@ let shouldStopPropagation = true; | ||
// trigger as if it were a keyboard click. | ||
if (!state.ignoreClickAfterPress && !state.ignoreEmulatedMouseEvents && !state.isPressed && (state.pointerType === 'virtual' || (0, $7mdmh$isVirtualClick)(e.nativeEvent))) { | ||
// Ensure the element receives focus (VoiceOver on iOS does not do this) | ||
if (!isDisabled && !preventFocusOnPress) (0, $7mdmh$focusWithoutScrolling)(e.currentTarget); | ||
if (!state.ignoreEmulatedMouseEvents && !state.isPressed && (state.pointerType === 'virtual' || (0, $7mdmh$isVirtualClick)(e.nativeEvent))) { | ||
let stopPressStart = triggerPressStart(e, 'virtual'); | ||
@@ -226,5 +230,9 @@ let stopPressUp = triggerPressUp(e, 'virtual'); | ||
shouldStopPropagation = stopPressStart && stopPressUp && stopPressEnd; | ||
} else if (state.isPressed && state.pointerType !== 'keyboard') { | ||
let pointerType = state.pointerType || e.nativeEvent.pointerType || 'virtual'; | ||
shouldStopPropagation = triggerPressEnd($f6c31cce2adf654f$var$createEvent(e.currentTarget, e), pointerType, true); | ||
state.isOverTarget = false; | ||
cancel(e); | ||
} | ||
state.ignoreEmulatedMouseEvents = false; | ||
state.ignoreClickAfterPress = false; | ||
if (shouldStopPropagation) e.stopPropagation(); | ||
@@ -238,5 +246,5 @@ } | ||
var _state_metaKeyEvents1; | ||
if ($f6c31cce2adf654f$var$shouldPreventDefaultKeyboard(e.target, e.key)) e.preventDefault(); | ||
let target = e.target; | ||
triggerPressEnd($f6c31cce2adf654f$var$createEvent(state.target, e), 'keyboard', state.target.contains(target)); | ||
if ($f6c31cce2adf654f$var$shouldPreventDefaultKeyboard((0, $7mdmh$getEventTarget)(e), e.key)) e.preventDefault(); | ||
let target = (0, $7mdmh$getEventTarget)(e); | ||
triggerPressEnd($f6c31cce2adf654f$var$createEvent(state.target, e), 'keyboard', (0, $7mdmh$nodeContains)(state.target, (0, $7mdmh$getEventTarget)(e))); | ||
removeAllGlobalListeners(); | ||
@@ -246,3 +254,3 @@ // If a link was triggered with a key other than Enter, open the URL ourselves. | ||
// only applies when using the Enter key. | ||
if (e.key !== 'Enter' && $f6c31cce2adf654f$var$isHTMLAnchorLink(state.target) && state.target.contains(target) && !e[$f6c31cce2adf654f$var$LINK_CLICKED]) { | ||
if (e.key !== 'Enter' && $f6c31cce2adf654f$var$isHTMLAnchorLink(state.target) && (0, $7mdmh$nodeContains)(state.target, target) && !e[$f6c31cce2adf654f$var$LINK_CLICKED]) { | ||
// Store a hidden property on the event so we only trigger link click once, | ||
@@ -268,3 +276,3 @@ // even if there are multiple usePress instances attached to the element. | ||
// Only handle left clicks, and ignore events that bubbled through portals. | ||
if (e.button !== 0 || !e.currentTarget.contains(e.target)) return; | ||
if (e.button !== 0 || !(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
// iOS safari fires pointer events from VoiceOver with incorrect coordinates/target. | ||
@@ -278,5 +286,2 @@ // Ignore and let the onClick handler take care of it instead. | ||
} | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent | ||
// default on pointer down and handle focusing the pressable element ourselves. | ||
if ($f6c31cce2adf654f$var$shouldPreventDefaultDown(e.currentTarget)) e.preventDefault(); | ||
state.pointerType = e.pointerType; | ||
@@ -289,3 +294,2 @@ let shouldStopPropagation = true; | ||
state.target = e.currentTarget; | ||
if (!isDisabled && !preventFocusOnPress) (0, $7mdmh$focusWithoutScrolling)(e.currentTarget); | ||
if (!allowTextSelectionOnPress) (0, $14c0b72509d70225$export$16a4697467175487)(state.target); | ||
@@ -295,3 +299,3 @@ shouldStopPropagation = triggerPressStart(e, state.pointerType); | ||
// This enables onPointerLeave and onPointerEnter to fire. | ||
let target = e.target; | ||
let target = (0, $7mdmh$getEventTarget)(e.nativeEvent); | ||
if ('releasePointerCapture' in target) target.releasePointerCapture(e.pointerId); | ||
@@ -304,8 +308,8 @@ addGlobalListener((0, $7mdmh$getOwnerDocument)(e.currentTarget), 'pointerup', onPointerUp, false); | ||
pressProps.onMouseDown = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
if (e.button === 0) { | ||
// Chrome and Firefox on touch Windows devices require mouse down events | ||
// to be canceled in addition to pointer events, or an extra asynchronous | ||
// focus event will be fired. | ||
if ($f6c31cce2adf654f$var$shouldPreventDefaultDown(e.currentTarget)) e.preventDefault(); | ||
if (preventFocusOnPress) { | ||
let dispose = (0, $8a9cb279dc87e130$export$cabe61c495ee3649)(e.target); | ||
if (dispose) state.disposables.push(dispose); | ||
} | ||
e.stopPropagation(); | ||
@@ -316,3 +320,3 @@ } | ||
// iOS fires pointerup with zero width and height, so check the pointerType recorded during pointerdown. | ||
if (!e.currentTarget.contains(e.target) || state.pointerType === 'virtual') return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent)) || state.pointerType === 'virtual') return; | ||
// Only handle left clicks | ||
@@ -336,27 +340,32 @@ if (e.button === 0) triggerPressUp(e, state.pointerType || e.pointerType); | ||
if (e.pointerId === state.activePointerId && state.isPressed && e.button === 0 && state.target) { | ||
if (state.target.contains(e.target) && state.pointerType != null) triggerPressEnd($f6c31cce2adf654f$var$createEvent(state.target, e), state.pointerType); | ||
else if (state.isOverTarget && state.pointerType != null) triggerPressEnd($f6c31cce2adf654f$var$createEvent(state.target, e), state.pointerType, false); | ||
state.isPressed = false; | ||
if ((0, $7mdmh$nodeContains)(state.target, (0, $7mdmh$getEventTarget)(e)) && state.pointerType != null) { | ||
// Wait for onClick to fire onPress. This avoids browser issues when the DOM | ||
// is mutated between onPointerUp and onClick, and is more compatible with third party libraries. | ||
// https://github.com/adobe/react-spectrum/issues/1513 | ||
// https://issues.chromium.org/issues/40732224 | ||
// However, iOS and Android do not focus or fire onClick after a long press. | ||
// We work around this by triggering a click ourselves after a timeout. | ||
// This timeout is canceled during the click event in case the real one fires first. | ||
// The timeout must be at least 32ms, because Safari on iOS delays the click event on | ||
// non-form elements without certain ARIA roles (for hover emulation). | ||
// https://github.com/WebKit/WebKit/blob/dccfae42bb29bd4bdef052e469f604a9387241c0/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L875-L892 | ||
let clicked = false; | ||
let timeout = setTimeout(()=>{ | ||
if (state.isPressed && state.target instanceof HTMLElement) { | ||
if (clicked) cancel(e); | ||
else { | ||
(0, $7mdmh$focusWithoutScrolling)(state.target); | ||
state.target.click(); | ||
} | ||
} | ||
}, 80); | ||
// Use a capturing listener to track if a click occurred. | ||
// If stopPropagation is called it may never reach our handler. | ||
addGlobalListener(e.currentTarget, 'click', ()=>clicked = true, true); | ||
state.disposables.push(()=>clearTimeout(timeout)); | ||
} else cancel(e); | ||
// Ignore subsequent onPointerLeave event before onClick on touch devices. | ||
state.isOverTarget = false; | ||
state.activePointerId = null; | ||
state.pointerType = null; | ||
removeAllGlobalListeners(); | ||
if (!allowTextSelectionOnPress) (0, $14c0b72509d70225$export$b0d6fa1ab32e3295)(state.target); | ||
// Prevent subsequent touchend event from triggering onClick on unrelated elements on Android. See below. | ||
// Both 'touch' and 'pen' pointerTypes trigger onTouchEnd, but 'mouse' does not. | ||
if ('ontouchend' in state.target && e.pointerType !== 'mouse') addGlobalListener(state.target, 'touchend', onTouchEnd, { | ||
once: true | ||
}); | ||
} | ||
}; | ||
// This is a workaround for an Android Chrome/Firefox issue where click events are fired on an incorrect element | ||
// if the original target is removed during onPointerUp (before onClick). | ||
// https://github.com/adobe/react-spectrum/issues/1513 | ||
// https://issues.chromium.org/issues/40732224 | ||
// Note: this event must be registered directly on the element, not via React props in order to work. | ||
// https://github.com/facebook/react/issues/9809 | ||
let onTouchEnd = (e)=>{ | ||
// Don't preventDefault if we actually want the default (e.g. submit/link click). | ||
if ($f6c31cce2adf654f$var$shouldPreventDefaultUp(e.currentTarget)) e.preventDefault(); | ||
}; | ||
let onPointerCancel = (e)=>{ | ||
@@ -366,3 +375,3 @@ cancel(e); | ||
pressProps.onDragStart = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
// Safari does not call onPointerCancel when a drag starts, whereas Chrome and Firefox do. | ||
@@ -372,8 +381,7 @@ cancel(e); | ||
} else { | ||
// NOTE: this fallback branch is almost entirely used by unit tests. | ||
// All browsers now support pointer events, but JSDOM still does not. | ||
pressProps.onMouseDown = (e)=>{ | ||
// Only handle left clicks | ||
if (e.button !== 0 || !e.currentTarget.contains(e.target)) return; | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent | ||
// default on mouse down and handle focusing the pressable element ourselves. | ||
if ($f6c31cce2adf654f$var$shouldPreventDefaultDown(e.currentTarget)) e.preventDefault(); | ||
if (e.button !== 0 || !(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
if (state.ignoreEmulatedMouseEvents) { | ||
@@ -387,9 +395,13 @@ e.stopPropagation(); | ||
state.pointerType = (0, $7mdmh$isVirtualClick)(e.nativeEvent) ? 'virtual' : 'mouse'; | ||
if (!isDisabled && !preventFocusOnPress) (0, $7mdmh$focusWithoutScrolling)(e.currentTarget); | ||
let shouldStopPropagation = triggerPressStart(e, state.pointerType); | ||
// Flush sync so that focus moved during react re-renders occurs before we yield back to the browser. | ||
let shouldStopPropagation = (0, $7mdmh$flushSync)(()=>triggerPressStart(e, state.pointerType)); | ||
if (shouldStopPropagation) e.stopPropagation(); | ||
if (preventFocusOnPress) { | ||
let dispose = (0, $8a9cb279dc87e130$export$cabe61c495ee3649)(e.target); | ||
if (dispose) state.disposables.push(dispose); | ||
} | ||
addGlobalListener((0, $7mdmh$getOwnerDocument)(e.currentTarget), 'mouseup', onMouseUp, false); | ||
}; | ||
pressProps.onMouseEnter = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
let shouldStopPropagation = true; | ||
@@ -403,3 +415,3 @@ if (state.isPressed && !state.ignoreEmulatedMouseEvents && state.pointerType != null) { | ||
pressProps.onMouseLeave = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
let shouldStopPropagation = true; | ||
@@ -414,3 +426,3 @@ if (state.isPressed && !state.ignoreEmulatedMouseEvents && state.pointerType != null) { | ||
pressProps.onMouseUp = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
if (!state.ignoreEmulatedMouseEvents && e.button === 0) triggerPressUp(e, state.pointerType || 'mouse'); | ||
@@ -421,4 +433,2 @@ }; | ||
if (e.button !== 0) return; | ||
state.isPressed = false; | ||
removeAllGlobalListeners(); | ||
if (state.ignoreEmulatedMouseEvents) { | ||
@@ -428,8 +438,8 @@ state.ignoreEmulatedMouseEvents = false; | ||
} | ||
if (state.target && $f6c31cce2adf654f$var$isOverTarget(e, state.target) && state.pointerType != null) triggerPressEnd($f6c31cce2adf654f$var$createEvent(state.target, e), state.pointerType); | ||
else if (state.target && state.isOverTarget && state.pointerType != null) triggerPressEnd($f6c31cce2adf654f$var$createEvent(state.target, e), state.pointerType, false); | ||
if (state.target && state.target.contains(e.target) && state.pointerType != null) ; | ||
else cancel(e); | ||
state.isOverTarget = false; | ||
}; | ||
pressProps.onTouchStart = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
let touch = $f6c31cce2adf654f$var$getTouchFromEvent(e.nativeEvent); | ||
@@ -443,5 +453,2 @@ if (!touch) return; | ||
state.pointerType = 'touch'; | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent default | ||
// on the emulated mouse event and handle focusing the pressable element ourselves. | ||
if (!isDisabled && !preventFocusOnPress) (0, $7mdmh$focusWithoutScrolling)(e.currentTarget); | ||
if (!allowTextSelectionOnPress) (0, $14c0b72509d70225$export$16a4697467175487)(state.target); | ||
@@ -453,3 +460,3 @@ let shouldStopPropagation = triggerPressStart($f6c31cce2adf654f$var$createTouchEvent(state.target, e), state.pointerType); | ||
pressProps.onTouchMove = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
if (!state.isPressed) { | ||
@@ -474,3 +481,3 @@ e.stopPropagation(); | ||
pressProps.onTouchEnd = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
if (!state.isPressed) { | ||
@@ -495,3 +502,3 @@ e.stopPropagation(); | ||
pressProps.onTouchCancel = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
e.stopPropagation(); | ||
@@ -501,3 +508,3 @@ if (state.isPressed) cancel($f6c31cce2adf654f$var$createTouchEvent(state.target, e)); | ||
let onScroll = (e)=>{ | ||
if (state.isPressed && e.target.contains(state.target)) cancel({ | ||
if (state.isPressed && (0, $7mdmh$nodeContains)((0, $7mdmh$getEventTarget)(e), state.target)) cancel({ | ||
currentTarget: state.target, | ||
@@ -511,3 +518,3 @@ shiftKey: false, | ||
pressProps.onDragStart = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
if (!(0, $7mdmh$nodeContains)(e.currentTarget, (0, $7mdmh$getEventTarget)(e.nativeEvent))) return; | ||
cancel(e); | ||
@@ -531,6 +538,8 @@ }; | ||
(0, $7mdmh$useEffect)(()=>{ | ||
let state = ref.current; | ||
return ()=>{ | ||
var _ref_current_target; | ||
if (!allowTextSelectionOnPress) // eslint-disable-next-line react-hooks/exhaustive-deps | ||
(0, $14c0b72509d70225$export$b0d6fa1ab32e3295)((_ref_current_target = ref.current.target) !== null && _ref_current_target !== void 0 ? _ref_current_target : undefined); | ||
var _state_target; | ||
if (!allowTextSelectionOnPress) (0, $14c0b72509d70225$export$b0d6fa1ab32e3295)((_state_target = state.target) !== null && _state_target !== void 0 ? _state_target : undefined); | ||
for (let dispose of state.disposables)dispose(); | ||
state.disposables = []; | ||
}; | ||
@@ -626,6 +635,2 @@ }, [ | ||
} | ||
function $f6c31cce2adf654f$var$shouldPreventDefaultDown(target) { | ||
// We cannot prevent default if the target is a draggable element. | ||
return !(target instanceof HTMLElement) || !target.hasAttribute('draggable'); | ||
} | ||
function $f6c31cce2adf654f$var$shouldPreventDefaultUp(target) { | ||
@@ -632,0 +637,0 @@ if (target instanceof HTMLInputElement) return false; |
@@ -0,3 +1,3 @@ | ||
var $iJhOP$reactariautils = require("@react-aria/utils"); | ||
var $iJhOP$react = require("react"); | ||
var $iJhOP$reactariautils = require("@react-aria/utils"); | ||
@@ -9,3 +9,6 @@ | ||
$parcel$export(module.exports, "SyntheticFocusEvent", () => $625cf83917e112ad$export$905e7fc544a71f36); | ||
$parcel$export(module.exports, "useSyntheticBlurEvent", () => $625cf83917e112ad$export$715c682d09d639cc); | ||
$parcel$export(module.exports, "ignoreFocusEvent", () => $625cf83917e112ad$export$fda7da73ab5d4c48); | ||
$parcel$export(module.exports, "preventFocus", () => $625cf83917e112ad$export$cabe61c495ee3649); | ||
/* | ||
@@ -118,4 +121,57 @@ * Copyright 2020 Adobe. All rights reserved. | ||
} | ||
let $625cf83917e112ad$export$fda7da73ab5d4c48 = false; | ||
function $625cf83917e112ad$export$cabe61c495ee3649(target) { | ||
// The browser will focus the nearest focusable ancestor of our target. | ||
while(target && !(0, $iJhOP$reactariautils.isFocusable)(target))target = target.parentElement; | ||
let window = (0, $iJhOP$reactariautils.getOwnerWindow)(target); | ||
let activeElement = window.document.activeElement; | ||
if (!activeElement || activeElement === target) return; | ||
$625cf83917e112ad$export$fda7da73ab5d4c48 = true; | ||
let isRefocusing = false; | ||
let onBlur = (e)=>{ | ||
if (e.target === activeElement || isRefocusing) e.stopImmediatePropagation(); | ||
}; | ||
let onFocusOut = (e)=>{ | ||
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; | ||
(0, $iJhOP$reactariautils.focusWithoutScrolling)(activeElement); | ||
cleanup(); | ||
} | ||
} | ||
}; | ||
let onFocus = (e)=>{ | ||
if (e.target === target || isRefocusing) e.stopImmediatePropagation(); | ||
}; | ||
let onFocusIn = (e)=>{ | ||
if (e.target === target || isRefocusing) { | ||
e.stopImmediatePropagation(); | ||
if (!isRefocusing) { | ||
isRefocusing = true; | ||
(0, $iJhOP$reactariautils.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); | ||
$625cf83917e112ad$export$fda7da73ab5d4c48 = false; | ||
isRefocusing = false; | ||
}; | ||
let raf = requestAnimationFrame(cleanup); | ||
return cleanup; | ||
} | ||
//# sourceMappingURL=utils.main.js.map |
@@ -0,3 +1,3 @@ | ||
import {useLayoutEffect as $6dfIe$useLayoutEffect, useEffectEvent as $6dfIe$useEffectEvent, isFocusable as $6dfIe$isFocusable, getOwnerWindow as $6dfIe$getOwnerWindow, focusWithoutScrolling as $6dfIe$focusWithoutScrolling} from "@react-aria/utils"; | ||
import {useRef as $6dfIe$useRef, useCallback as $6dfIe$useCallback} from "react"; | ||
import {useLayoutEffect as $6dfIe$useLayoutEffect, useEffectEvent as $6dfIe$useEffectEvent} from "@react-aria/utils"; | ||
@@ -111,5 +111,58 @@ /* | ||
} | ||
let $8a9cb279dc87e130$export$fda7da73ab5d4c48 = false; | ||
function $8a9cb279dc87e130$export$cabe61c495ee3649(target) { | ||
// The browser will focus the nearest focusable ancestor of our target. | ||
while(target && !(0, $6dfIe$isFocusable)(target))target = target.parentElement; | ||
let window = (0, $6dfIe$getOwnerWindow)(target); | ||
let activeElement = window.document.activeElement; | ||
if (!activeElement || activeElement === target) return; | ||
$8a9cb279dc87e130$export$fda7da73ab5d4c48 = true; | ||
let isRefocusing = false; | ||
let onBlur = (e)=>{ | ||
if (e.target === activeElement || isRefocusing) e.stopImmediatePropagation(); | ||
}; | ||
let onFocusOut = (e)=>{ | ||
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; | ||
(0, $6dfIe$focusWithoutScrolling)(activeElement); | ||
cleanup(); | ||
} | ||
} | ||
}; | ||
let onFocus = (e)=>{ | ||
if (e.target === target || isRefocusing) e.stopImmediatePropagation(); | ||
}; | ||
let onFocusIn = (e)=>{ | ||
if (e.target === target || isRefocusing) { | ||
e.stopImmediatePropagation(); | ||
if (!isRefocusing) { | ||
isRefocusing = true; | ||
(0, $6dfIe$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); | ||
$8a9cb279dc87e130$export$fda7da73ab5d4c48 = false; | ||
isRefocusing = false; | ||
}; | ||
let raf = requestAnimationFrame(cleanup); | ||
return cleanup; | ||
} | ||
export {$8a9cb279dc87e130$export$905e7fc544a71f36 as SyntheticFocusEvent, $8a9cb279dc87e130$export$715c682d09d639cc as useSyntheticBlurEvent}; | ||
export {$8a9cb279dc87e130$export$905e7fc544a71f36 as SyntheticFocusEvent, $8a9cb279dc87e130$export$715c682d09d639cc as useSyntheticBlurEvent, $8a9cb279dc87e130$export$fda7da73ab5d4c48 as ignoreFocusEvent, $8a9cb279dc87e130$export$cabe61c495ee3649 as preventFocus}; | ||
//# sourceMappingURL=utils.module.js.map |
{ | ||
"name": "@react-aria/interactions", | ||
"version": "3.0.0-nightly-50c7ada5d-241223", | ||
"version": "3.0.0-nightly-527c98a84-250305", | ||
"description": "Spectrum UI components in React", | ||
@@ -25,9 +25,11 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"@react-aria/ssr": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-aria/utils": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-types/shared": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-aria/ssr": "3.0.0-nightly-527c98a84-250305", | ||
"@react-aria/utils": "3.0.0-nightly-527c98a84-250305", | ||
"@react-stately/flags": "3.0.0-nightly-527c98a84-250305", | ||
"@react-types/shared": "3.0.0-nightly-527c98a84-250305", | ||
"@swc/helpers": "^0.5.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", | ||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" | ||
}, | ||
@@ -34,0 +36,0 @@ "publishConfig": { |
@@ -35,6 +35,13 @@ /* | ||
stopPropagation() { | ||
console.error('stopPropagation is now the default behavior for events in React Spectrum. You can use continuePropagation() to revert this behavior.'); | ||
if (shouldStopPropagation) { | ||
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; | ||
} | ||
@@ -41,0 +48,0 @@ }; |
@@ -33,2 +33,4 @@ /* | ||
export {useLongPress} from './useLongPress'; | ||
export {useFocusable, FocusableProvider, Focusable, FocusableContext} from './useFocusable'; | ||
export {focusSafely} from './focusSafely'; | ||
@@ -46,1 +48,2 @@ export type {FocusProps, FocusResult} from './useFocus'; | ||
export type {ScrollWheelProps} from './useScrollWheel'; | ||
export type {FocusableAria, FocusableOptions, FocusableProviderProps} from './useFocusable'; |
@@ -49,4 +49,5 @@ /* | ||
// Ignore state since it doesn't apply for non iOS | ||
modifiedElementMap.set(target, target.style.userSelect); | ||
target.style.userSelect = 'none'; | ||
let property = 'userSelect' in target.style ? 'userSelect' : 'webkitUserSelect'; | ||
modifiedElementMap.set(target, target.style[property]); | ||
target.style[property] = 'none'; | ||
} | ||
@@ -89,5 +90,6 @@ } | ||
let targetOldUserSelect = modifiedElementMap.get(target) as string; | ||
let property = 'userSelect' in target.style ? 'userSelect' : 'webkitUserSelect'; | ||
if (target.style.userSelect === 'none') { | ||
target.style.userSelect = targetOldUserSelect; | ||
if (target.style[property] === 'none') { | ||
target.style[property] = targetOldUserSelect; | ||
} | ||
@@ -94,0 +96,0 @@ |
@@ -20,3 +20,3 @@ /* | ||
import {FocusEvent, useCallback} from 'react'; | ||
import {getOwnerDocument} from '@react-aria/utils'; | ||
import {getActiveElement, getEventTarget, getOwnerDocument} from '@react-aria/utils'; | ||
import {useSyntheticBlurEvent} from './utils'; | ||
@@ -68,4 +68,4 @@ | ||
const ownerDocument = getOwnerDocument(e.target); | ||
if (e.target === e.currentTarget && ownerDocument.activeElement === e.target) { | ||
const activeElement = ownerDocument ? getActiveElement(ownerDocument) : getActiveElement(); | ||
if (e.target === e.currentTarget && activeElement === getEventTarget(e.nativeEvent)) { | ||
if (onFocusProp) { | ||
@@ -72,0 +72,0 @@ onFocusProp(e); |
@@ -19,2 +19,3 @@ /* | ||
import {getOwnerDocument, getOwnerWindow, isMac, isVirtualClick} from '@react-aria/utils'; | ||
import {ignoreFocusEvent} from './utils'; | ||
import {useEffect, useState} from 'react'; | ||
@@ -96,3 +97,3 @@ import {useIsSSR} from '@react-aria/ssr'; | ||
// cause keyboard focus rings to appear. | ||
if (e.target === window || e.target === document) { | ||
if (e.target === window || e.target === document || ignoreFocusEvent || !e.isTrusted) { | ||
return; | ||
@@ -113,2 +114,6 @@ } | ||
function handleWindowBlur() { | ||
if (ignoreFocusEvent) { | ||
return; | ||
} | ||
// When the window is blurred, reset state. This is necessary when tabbing out of the window, | ||
@@ -182,2 +187,3 @@ // for example, since a subsequent focus event won't be fired. | ||
documentObject.removeEventListener('click', handleClickEvent, true); | ||
windowObject.removeEventListener('focus', handleFocusEvent, true); | ||
@@ -291,2 +297,3 @@ windowObject.removeEventListener('blur', handleWindowBlur, false); | ||
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; | ||
@@ -297,6 +304,8 @@ const IHTMLTextAreaElement = typeof window !== 'undefined' ? getOwnerWindow(e?.target as Element).HTMLTextAreaElement : HTMLTextAreaElement; | ||
// 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 || | ||
(e?.target instanceof IHTMLInputElement && !nonTextInputTypes.has(e?.target?.type)) || | ||
e?.target instanceof IHTMLTextAreaElement || | ||
(e?.target instanceof IHTMLElement && e?.target.isContentEditable); | ||
(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]); | ||
@@ -326,2 +335,3 @@ } | ||
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)) { | ||
@@ -328,0 +338,0 @@ return; |
@@ -20,3 +20,4 @@ /* | ||
import {FocusEvent, useCallback, useRef} from 'react'; | ||
import {useSyntheticBlurEvent} from './utils'; | ||
import {getActiveElement, getEventTarget, getOwnerDocument, nodeContains, useGlobalListeners} from '@react-aria/utils'; | ||
import {SyntheticFocusEvent, useSyntheticBlurEvent} from './utils'; | ||
@@ -53,3 +54,10 @@ export interface FocusWithinProps { | ||
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 | ||
@@ -60,2 +68,3 @@ // when moving focus inside the element. Only trigger if the currentTarget doesn't | ||
state.current.isFocusWithin = false; | ||
removeAllGlobalListeners(); | ||
@@ -70,9 +79,16 @@ if (onBlurWithin) { | ||
} | ||
}, [onBlurWithin, onFocusWithinChange, state]); | ||
}, [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. | ||
if (!state.current.isFocusWithin && document.activeElement === e.target) { | ||
const ownerDocument = getOwnerDocument(e.target); | ||
const activeElement = getActiveElement(ownerDocument); | ||
if (!state.current.isFocusWithin && activeElement === getEventTarget(e.nativeEvent)) { | ||
if (onFocusWithin) { | ||
@@ -88,4 +104,17 @@ onFocusWithin(e); | ||
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 event = new SyntheticFocusEvent('blur', new ownerDocument.defaultView!.FocusEvent('blur', {relatedTarget: e.target})); | ||
event.target = currentTarget; | ||
event.currentTarget = currentTarget; | ||
onBlur(event); | ||
} | ||
}, {capture: true}); | ||
} | ||
}, [onFocusWithin, onFocusWithinChange, onSyntheticFocus]); | ||
}, [onFocusWithin, onFocusWithinChange, onSyntheticFocus, addGlobalListener, onBlur]); | ||
@@ -95,3 +124,3 @@ if (isDisabled) { | ||
focusWithinProps: { | ||
// These should not have been null, that would conflict in mergeProps | ||
// These cannot be null, that would conflict in mergeProps | ||
onFocus: undefined, | ||
@@ -98,0 +127,0 @@ onBlur: undefined |
@@ -19,2 +19,3 @@ /* | ||
import {DOMAttributes, HoverEvents} from '@react-types/shared'; | ||
import {getOwnerDocument, nodeContains, useGlobalListeners} from '@react-aria/utils'; | ||
import {useEffect, useMemo, useRef, useState} from 'react'; | ||
@@ -104,2 +105,3 @@ | ||
useEffect(setupGlobalTouchEvents, []); | ||
let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners(); | ||
@@ -117,2 +119,12 @@ let {hoverProps, triggerHoverEnd} = useMemo(() => { | ||
// 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) { | ||
@@ -134,6 +146,7 @@ onHoverStart({ | ||
let triggerHoverEnd = (event, pointerType) => { | ||
let target = state.target; | ||
state.pointerType = ''; | ||
state.target = null; | ||
if (pointerType === 'touch' || !state.isHovered) { | ||
if (pointerType === 'touch' || !state.isHovered || !target) { | ||
return; | ||
@@ -143,3 +156,4 @@ } | ||
state.isHovered = false; | ||
let target = event.currentTarget; | ||
removeAllGlobalListeners(); | ||
if (onHoverEnd) { | ||
@@ -196,3 +210,3 @@ onHoverEnd({ | ||
return {hoverProps, triggerHoverEnd}; | ||
}, [onHoverStart, onHoverChange, onHoverEnd, isDisabled, state]); | ||
}, [onHoverStart, onHoverChange, onHoverEnd, isDisabled, state, addGlobalListener, removeAllGlobalListeners]); | ||
@@ -199,0 +213,0 @@ useEffect(() => { |
@@ -119,3 +119,2 @@ /* | ||
} | ||
if (event.target) { | ||
@@ -127,3 +126,2 @@ // if the event target is no longer in the document, ignore | ||
} | ||
// If the target is within a top layer element (e.g. toasts), ignore. | ||
@@ -135,3 +133,11 @@ if (event.target.closest('[data-react-aria-top-layer]')) { | ||
return ref.current && !ref.current.contains(event.target); | ||
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); | ||
} |
@@ -13,4 +13,4 @@ /* | ||
import {DOMAttributes, LongPressEvent} from '@react-types/shared'; | ||
import {mergeProps, useDescription, useGlobalListeners} from '@react-aria/utils'; | ||
import {DOMAttributes, FocusableElement, LongPressEvent} from '@react-types/shared'; | ||
import {focusWithoutScrolling, getOwnerDocument, mergeProps, useDescription, useGlobalListeners} from '@react-aria/utils'; | ||
import {usePress} from './usePress'; | ||
@@ -85,2 +85,8 @@ import {useRef} from 'react'; | ||
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) { | ||
@@ -87,0 +93,0 @@ onLongPress({ |
@@ -18,6 +18,23 @@ /* | ||
import {chain, focusWithoutScrolling, getOwnerDocument, getOwnerWindow, isMac, isVirtualClick, isVirtualPointerEvent, mergeProps, openLink, useEffectEvent, useGlobalListeners, useSyncRef} from '@react-aria/utils'; | ||
import { | ||
chain, | ||
focusWithoutScrolling, | ||
getEventTarget, | ||
getOwnerDocument, | ||
getOwnerWindow, | ||
isMac, | ||
isVirtualClick, | ||
isVirtualPointerEvent, | ||
mergeProps, | ||
nodeContains, | ||
openLink, | ||
useEffectEvent, | ||
useGlobalListeners, | ||
useSyncRef | ||
} from '@react-aria/utils'; | ||
import {disableTextSelection, restoreTextSelection} from './textSelection'; | ||
import {DOMAttributes, FocusableElement, PressEvent as IPressEvent, PointerType, PressEvents, RefObject} from '@react-types/shared'; | ||
import {flushSync} from 'react-dom'; | ||
import {PressResponderContext} from './context'; | ||
import {preventFocus} from './utils'; | ||
import {TouchEvent as RTouchEvent, useContext, useEffect, useMemo, useRef, useState} from 'react'; | ||
@@ -51,3 +68,2 @@ | ||
ignoreEmulatedMouseEvents: boolean, | ||
ignoreClickAfterPress: boolean, | ||
didFirePressStart: boolean, | ||
@@ -60,3 +76,4 @@ isTriggeringEvent: boolean, | ||
userSelect?: string, | ||
metaKeyEvents?: Map<string, KeyboardEvent> | ||
metaKeyEvents?: Map<string, KeyboardEvent>, | ||
disposables: Array<() => void> | ||
} | ||
@@ -173,3 +190,2 @@ | ||
ignoreEmulatedMouseEvents: false, | ||
ignoreClickAfterPress: false, | ||
didFirePressStart: false, | ||
@@ -180,3 +196,4 @@ isTriggeringEvent: false, | ||
isOverTarget: false, | ||
pointerType: null | ||
pointerType: null, | ||
disposables: [] | ||
}); | ||
@@ -216,3 +233,2 @@ | ||
state.ignoreClickAfterPress = true; | ||
state.didFirePressStart = false; | ||
@@ -264,3 +280,3 @@ state.isTriggeringEvent = true; | ||
if (state.isPressed && state.target) { | ||
if (state.isOverTarget && state.pointerType != null) { | ||
if (state.didFirePressStart && state.pointerType != null) { | ||
triggerPressEnd(createEvent(state.target, e), state.pointerType, false); | ||
@@ -276,2 +292,6 @@ } | ||
} | ||
for (let dispose of state.disposables) { | ||
dispose(); | ||
} | ||
state.disposables = []; | ||
} | ||
@@ -290,4 +310,4 @@ }); | ||
onKeyDown(e) { | ||
if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && e.currentTarget.contains(e.target as Element)) { | ||
if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) { | ||
if (isValidKeyboardEvent(e.nativeEvent, e.currentTarget) && nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
if (shouldPreventDefaultKeyboard(getEventTarget(e.nativeEvent), e.key)) { | ||
e.preventDefault(); | ||
@@ -303,2 +323,3 @@ } | ||
state.isPressed = true; | ||
state.pointerType = 'keyboard'; | ||
shouldStopPropagation = triggerPressStart(e, 'keyboard'); | ||
@@ -311,3 +332,3 @@ | ||
let pressUp = (e) => { | ||
if (isValidKeyboardEvent(e, originalTarget) && !e.repeat && originalTarget.contains(e.target as Element) && state.target) { | ||
if (isValidKeyboardEvent(e, originalTarget) && !e.repeat && nodeContains(originalTarget, getEventTarget(e)) && state.target) { | ||
triggerPressUp(createEvent(state.target, e), 'keyboard'); | ||
@@ -339,3 +360,3 @@ } | ||
onClick(e) { | ||
if (e && !e.currentTarget.contains(e.target as Element)) { | ||
if (e && !nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -349,11 +370,6 @@ } | ||
} | ||
// If triggered from a screen reader or by using element.click(), | ||
// trigger as if it were a keyboard click. | ||
if (!state.ignoreClickAfterPress && !state.ignoreEmulatedMouseEvents && !state.isPressed && (state.pointerType === 'virtual' || isVirtualClick(e.nativeEvent))) { | ||
// Ensure the element receives focus (VoiceOver on iOS does not do this) | ||
if (!isDisabled && !preventFocusOnPress) { | ||
focusWithoutScrolling(e.currentTarget); | ||
} | ||
if (!state.ignoreEmulatedMouseEvents && !state.isPressed && (state.pointerType === 'virtual' || isVirtualClick(e.nativeEvent))) { | ||
let stopPressStart = triggerPressStart(e, 'virtual'); | ||
@@ -363,6 +379,10 @@ let stopPressUp = triggerPressUp(e, 'virtual'); | ||
shouldStopPropagation = stopPressStart && stopPressUp && stopPressEnd; | ||
} else if (state.isPressed && state.pointerType !== 'keyboard') { | ||
let pointerType = state.pointerType || (e.nativeEvent as PointerEvent).pointerType as PointerType || 'virtual'; | ||
shouldStopPropagation = triggerPressEnd(createEvent(e.currentTarget, e), pointerType, true); | ||
state.isOverTarget = false; | ||
cancel(e); | ||
} | ||
state.ignoreEmulatedMouseEvents = false; | ||
state.ignoreClickAfterPress = false; | ||
if (shouldStopPropagation) { | ||
@@ -377,8 +397,8 @@ e.stopPropagation(); | ||
if (state.isPressed && state.target && isValidKeyboardEvent(e, state.target)) { | ||
if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) { | ||
if (shouldPreventDefaultKeyboard(getEventTarget(e), e.key)) { | ||
e.preventDefault(); | ||
} | ||
let target = e.target as Element; | ||
triggerPressEnd(createEvent(state.target, e), 'keyboard', state.target.contains(target)); | ||
let target = getEventTarget(e); | ||
triggerPressEnd(createEvent(state.target, e), 'keyboard', nodeContains(state.target, getEventTarget(e))); | ||
removeAllGlobalListeners(); | ||
@@ -389,3 +409,3 @@ | ||
// only applies when using the Enter key. | ||
if (e.key !== 'Enter' && isHTMLAnchorLink(state.target) && state.target.contains(target) && !e[LINK_CLICKED]) { | ||
if (e.key !== 'Enter' && isHTMLAnchorLink(state.target) && nodeContains(state.target, target) && !e[LINK_CLICKED]) { | ||
// Store a hidden property on the event so we only trigger link click once, | ||
@@ -414,3 +434,3 @@ // even if there are multiple usePress instances attached to the element. | ||
// Only handle left clicks, and ignore events that bubbled through portals. | ||
if (e.button !== 0 || !e.currentTarget.contains(e.target as Element)) { | ||
if (e.button !== 0 || !nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -428,8 +448,2 @@ } | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent | ||
// default on pointer down and handle focusing the pressable element ourselves. | ||
if (shouldPreventDefaultDown(e.currentTarget as Element)) { | ||
e.preventDefault(); | ||
} | ||
state.pointerType = e.pointerType; | ||
@@ -442,8 +456,4 @@ | ||
state.activePointerId = e.pointerId; | ||
state.target = e.currentTarget; | ||
state.target = e.currentTarget as FocusableElement; | ||
if (!isDisabled && !preventFocusOnPress) { | ||
focusWithoutScrolling(e.currentTarget); | ||
} | ||
if (!allowTextSelectionOnPress) { | ||
@@ -457,3 +467,3 @@ disableTextSelection(state.target); | ||
// This enables onPointerLeave and onPointerEnter to fire. | ||
let target = e.target as Element; | ||
let target = getEventTarget(e.nativeEvent); | ||
if ('releasePointerCapture' in target) { | ||
@@ -473,3 +483,3 @@ target.releasePointerCapture(e.pointerId); | ||
pressProps.onMouseDown = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -479,7 +489,7 @@ } | ||
if (e.button === 0) { | ||
// Chrome and Firefox on touch Windows devices require mouse down events | ||
// to be canceled in addition to pointer events, or an extra asynchronous | ||
// focus event will be fired. | ||
if (shouldPreventDefaultDown(e.currentTarget as Element)) { | ||
e.preventDefault(); | ||
if (preventFocusOnPress) { | ||
let dispose = preventFocus(e.target as FocusableElement); | ||
if (dispose) { | ||
state.disposables.push(dispose); | ||
} | ||
} | ||
@@ -493,3 +503,3 @@ | ||
// iOS fires pointerup with zero width and height, so check the pointerType recorded during pointerdown. | ||
if (!e.currentTarget.contains(e.target as Element) || state.pointerType === 'virtual') { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent)) || state.pointerType === 'virtual') { | ||
return; | ||
@@ -521,38 +531,37 @@ } | ||
if (e.pointerId === state.activePointerId && state.isPressed && e.button === 0 && state.target) { | ||
if (state.target.contains(e.target as Element) && state.pointerType != null) { | ||
triggerPressEnd(createEvent(state.target, e), state.pointerType); | ||
} else if (state.isOverTarget && state.pointerType != null) { | ||
triggerPressEnd(createEvent(state.target, e), state.pointerType, false); | ||
if (nodeContains(state.target, getEventTarget(e)) && state.pointerType != null) { | ||
// Wait for onClick to fire onPress. This avoids browser issues when the DOM | ||
// is mutated between onPointerUp and onClick, and is more compatible with third party libraries. | ||
// https://github.com/adobe/react-spectrum/issues/1513 | ||
// https://issues.chromium.org/issues/40732224 | ||
// However, iOS and Android do not focus or fire onClick after a long press. | ||
// We work around this by triggering a click ourselves after a timeout. | ||
// This timeout is canceled during the click event in case the real one fires first. | ||
// The timeout must be at least 32ms, because Safari on iOS delays the click event on | ||
// non-form elements without certain ARIA roles (for hover emulation). | ||
// https://github.com/WebKit/WebKit/blob/dccfae42bb29bd4bdef052e469f604a9387241c0/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm#L875-L892 | ||
let clicked = false; | ||
let timeout = setTimeout(() => { | ||
if (state.isPressed && state.target instanceof HTMLElement) { | ||
if (clicked) { | ||
cancel(e); | ||
} else { | ||
focusWithoutScrolling(state.target); | ||
state.target.click(); | ||
} | ||
} | ||
}, 80); | ||
// Use a capturing listener to track if a click occurred. | ||
// If stopPropagation is called it may never reach our handler. | ||
addGlobalListener(e.currentTarget as Document, 'click', () => clicked = true, true); | ||
state.disposables.push(() => clearTimeout(timeout)); | ||
} else { | ||
cancel(e); | ||
} | ||
state.isPressed = false; | ||
// Ignore subsequent onPointerLeave event before onClick on touch devices. | ||
state.isOverTarget = false; | ||
state.activePointerId = null; | ||
state.pointerType = null; | ||
removeAllGlobalListeners(); | ||
if (!allowTextSelectionOnPress) { | ||
restoreTextSelection(state.target); | ||
} | ||
// Prevent subsequent touchend event from triggering onClick on unrelated elements on Android. See below. | ||
// Both 'touch' and 'pen' pointerTypes trigger onTouchEnd, but 'mouse' does not. | ||
if ('ontouchend' in state.target && e.pointerType !== 'mouse') { | ||
addGlobalListener(state.target, 'touchend', onTouchEnd, {once: true}); | ||
} | ||
} | ||
}; | ||
// This is a workaround for an Android Chrome/Firefox issue where click events are fired on an incorrect element | ||
// if the original target is removed during onPointerUp (before onClick). | ||
// https://github.com/adobe/react-spectrum/issues/1513 | ||
// https://issues.chromium.org/issues/40732224 | ||
// Note: this event must be registered directly on the element, not via React props in order to work. | ||
// https://github.com/facebook/react/issues/9809 | ||
let onTouchEnd = (e: TouchEvent) => { | ||
// Don't preventDefault if we actually want the default (e.g. submit/link click). | ||
if (shouldPreventDefaultUp(e.currentTarget as Element)) { | ||
e.preventDefault(); | ||
} | ||
}; | ||
let onPointerCancel = (e: PointerEvent) => { | ||
@@ -563,3 +572,3 @@ cancel(e); | ||
pressProps.onDragStart = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -572,14 +581,11 @@ } | ||
} else { | ||
// NOTE: this fallback branch is almost entirely used by unit tests. | ||
// All browsers now support pointer events, but JSDOM still does not. | ||
pressProps.onMouseDown = (e) => { | ||
// Only handle left clicks | ||
if (e.button !== 0 || !e.currentTarget.contains(e.target as Element)) { | ||
if (e.button !== 0 || !nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
} | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent | ||
// default on mouse down and handle focusing the pressable element ourselves. | ||
if (shouldPreventDefaultDown(e.currentTarget)) { | ||
e.preventDefault(); | ||
} | ||
if (state.ignoreEmulatedMouseEvents) { | ||
@@ -595,7 +601,4 @@ e.stopPropagation(); | ||
if (!isDisabled && !preventFocusOnPress) { | ||
focusWithoutScrolling(e.currentTarget); | ||
} | ||
let shouldStopPropagation = triggerPressStart(e, state.pointerType); | ||
// Flush sync so that focus moved during react re-renders occurs before we yield back to the browser. | ||
let shouldStopPropagation = flushSync(() => triggerPressStart(e, state.pointerType!)); | ||
if (shouldStopPropagation) { | ||
@@ -605,2 +608,9 @@ e.stopPropagation(); | ||
if (preventFocusOnPress) { | ||
let dispose = preventFocus(e.target as FocusableElement); | ||
if (dispose) { | ||
state.disposables.push(dispose); | ||
} | ||
} | ||
addGlobalListener(getOwnerDocument(e.currentTarget), 'mouseup', onMouseUp, false); | ||
@@ -610,3 +620,3 @@ }; | ||
pressProps.onMouseEnter = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -627,3 +637,3 @@ } | ||
pressProps.onMouseLeave = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -645,3 +655,3 @@ } | ||
pressProps.onMouseUp = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -661,5 +671,2 @@ } | ||
state.isPressed = false; | ||
removeAllGlobalListeners(); | ||
if (state.ignoreEmulatedMouseEvents) { | ||
@@ -670,6 +677,7 @@ state.ignoreEmulatedMouseEvents = false; | ||
if (state.target && isOverTarget(e, state.target) && state.pointerType != null) { | ||
triggerPressEnd(createEvent(state.target, e), state.pointerType); | ||
} else if (state.target && state.isOverTarget && state.pointerType != null) { | ||
triggerPressEnd(createEvent(state.target, e), state.pointerType, false); | ||
if (state.target && state.target.contains(e.target as Element) && state.pointerType != null) { | ||
// Wait for onClick to fire onPress. This avoids browser issues when the DOM | ||
// is mutated between onMouseUp and onClick, and is more compatible with third party libraries. | ||
} else { | ||
cancel(e); | ||
} | ||
@@ -681,3 +689,3 @@ | ||
pressProps.onTouchStart = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -697,8 +705,2 @@ } | ||
// Due to browser inconsistencies, especially on mobile browsers, we prevent default | ||
// on the emulated mouse event and handle focusing the pressable element ourselves. | ||
if (!isDisabled && !preventFocusOnPress) { | ||
focusWithoutScrolling(e.currentTarget); | ||
} | ||
if (!allowTextSelectionOnPress) { | ||
@@ -717,3 +719,3 @@ disableTextSelection(state.target); | ||
pressProps.onTouchMove = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -746,3 +748,3 @@ } | ||
pressProps.onTouchEnd = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -780,3 +782,3 @@ } | ||
pressProps.onTouchCancel = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -792,3 +794,3 @@ } | ||
let onScroll = (e: Event) => { | ||
if (state.isPressed && (e.target as Element).contains(state.target)) { | ||
if (state.isPressed && nodeContains(getEventTarget(e), state.target)) { | ||
cancel({ | ||
@@ -805,3 +807,3 @@ currentTarget: state.target, | ||
pressProps.onDragStart = (e) => { | ||
if (!e.currentTarget.contains(e.target as Element)) { | ||
if (!nodeContains(e.currentTarget, getEventTarget(e.nativeEvent))) { | ||
return; | ||
@@ -831,7 +833,12 @@ } | ||
useEffect(() => { | ||
let state = ref.current; | ||
return () => { | ||
if (!allowTextSelectionOnPress) { | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
restoreTextSelection(ref.current.target ?? undefined); | ||
restoreTextSelection(state.target ?? undefined); | ||
} | ||
for (let dispose of state.disposables) { | ||
dispose(); | ||
} | ||
state.disposables = []; | ||
}; | ||
@@ -976,7 +983,2 @@ }, [allowTextSelectionOnPress]); | ||
function shouldPreventDefaultDown(target: Element) { | ||
// We cannot prevent default if the target is a draggable element. | ||
return !(target instanceof HTMLElement) || !target.hasAttribute('draggable'); | ||
} | ||
function shouldPreventDefaultUp(target: Element) { | ||
@@ -983,0 +985,0 @@ if (target instanceof HTMLInputElement) { |
@@ -13,4 +13,5 @@ /* | ||
import {FocusableElement} from '@react-types/shared'; | ||
import {focusWithoutScrolling, getOwnerWindow, isFocusable, useEffectEvent, useLayoutEffect} from '@react-aria/utils'; | ||
import {FocusEvent as ReactFocusEvent, useCallback, useRef} from 'react'; | ||
import {useEffectEvent, useLayoutEffect} from '@react-aria/utils'; | ||
@@ -132,1 +133,79 @@ export class SyntheticFocusEvent<Target = Element> implements ReactFocusEvent<Target> { | ||
} | ||
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) { | ||
// 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; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 2 instances in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 4 instances in 1 package
831918
118
16
9587
7
+ Added@react-aria/ssr@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/utils@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/flags@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/utils@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-types/shared@3.0.0-nightly-527c98a84-250305(transitive)
- Removed@react-aria/ssr@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/utils@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-stately/utils@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-types/shared@3.0.0-nightly-50c7ada5d-241223(transitive)