This commit is contained in:
103
node_modules/@react-aria/utils/src/animation.ts
generated
vendored
Normal file
103
node_modules/@react-aria/utils/src/animation.ts
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {flushSync} from 'react-dom';
|
||||
import {RefObject, useCallback, useState} from 'react';
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
|
||||
export function useEnterAnimation(ref: RefObject<HTMLElement | null>, isReady: boolean = true): boolean {
|
||||
let [isEntering, setEntering] = useState(true);
|
||||
let isAnimationReady = isEntering && isReady;
|
||||
|
||||
// There are two cases for entry animations:
|
||||
// 1. CSS @keyframes. The `animation` property is set during the isEntering state, and it is removed after the animation finishes.
|
||||
// 2. CSS transitions. The initial styles are applied during the isEntering state, and removed immediately, causing the transition to occur.
|
||||
//
|
||||
// In the second case, cancel any transitions that were triggered prior to the isEntering = false state (when the transition is supposed to start).
|
||||
// This can happen when isReady starts as false (e.g. popovers prior to placement calculation).
|
||||
useLayoutEffect(() => {
|
||||
if (isAnimationReady && ref.current && 'getAnimations' in ref.current) {
|
||||
for (let animation of ref.current.getAnimations()) {
|
||||
if (animation instanceof CSSTransition) {
|
||||
animation.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [ref, isAnimationReady]);
|
||||
|
||||
useAnimation(ref, isAnimationReady, useCallback(() => setEntering(false), []));
|
||||
return isAnimationReady;
|
||||
}
|
||||
|
||||
export function useExitAnimation(ref: RefObject<HTMLElement | null>, isOpen: boolean): boolean {
|
||||
let [exitState, setExitState] = useState<'closed' | 'open' | 'exiting'>(isOpen ? 'open' : 'closed');
|
||||
|
||||
switch (exitState) {
|
||||
case 'open':
|
||||
// If isOpen becomes false, set the state to exiting.
|
||||
if (!isOpen) {
|
||||
setExitState('exiting');
|
||||
}
|
||||
break;
|
||||
case 'closed':
|
||||
case 'exiting':
|
||||
// If we are exiting and isOpen becomes true, the animation was interrupted.
|
||||
// Reset the state to open.
|
||||
if (isOpen) {
|
||||
setExitState('open');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let isExiting = exitState === 'exiting';
|
||||
useAnimation(
|
||||
ref,
|
||||
isExiting,
|
||||
useCallback(() => {
|
||||
// Set the state to closed, which will cause the element to be unmounted.
|
||||
setExitState(state => state === 'exiting' ? 'closed' : state);
|
||||
}, [])
|
||||
);
|
||||
|
||||
return isExiting;
|
||||
}
|
||||
|
||||
function useAnimation(ref: RefObject<HTMLElement | null>, isActive: boolean, onEnd: () => void): void {
|
||||
useLayoutEffect(() => {
|
||||
if (isActive && ref.current) {
|
||||
if (!('getAnimations' in ref.current)) {
|
||||
// JSDOM
|
||||
onEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
let animations = ref.current.getAnimations();
|
||||
if (animations.length === 0) {
|
||||
onEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
let canceled = false;
|
||||
Promise.all(animations.map(a => a.finished)).then(() => {
|
||||
if (!canceled) {
|
||||
flushSync(() => {
|
||||
onEnd();
|
||||
});
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}
|
||||
}, [ref, isActive, onEnd]);
|
||||
}
|
||||
24
node_modules/@react-aria/utils/src/chain.ts
generated
vendored
Normal file
24
node_modules/@react-aria/utils/src/chain.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calls all functions in the order they were chained with the same arguments.
|
||||
*/
|
||||
export function chain(...callbacks: any[]): (...args: any[]) => void {
|
||||
return (...args: any[]) => {
|
||||
for (let callback of callbacks) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(...args);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
15
node_modules/@react-aria/utils/src/constants.ts
generated
vendored
Normal file
15
node_modules/@react-aria/utils/src/constants.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2024 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// Custom event names for updating the autocomplete's aria-activedecendant.
|
||||
export const CLEAR_FOCUS_EVENT = 'react-aria-clear-focus';
|
||||
export const FOCUS_EVENT = 'react-aria-focus';
|
||||
33
node_modules/@react-aria/utils/src/domHelpers.ts
generated
vendored
Normal file
33
node_modules/@react-aria/utils/src/domHelpers.ts
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
export const getOwnerDocument = (el: Element | null | undefined): Document => {
|
||||
return el?.ownerDocument ?? document;
|
||||
};
|
||||
|
||||
export const getOwnerWindow = (
|
||||
el: (Window & typeof global) | Element | null | undefined
|
||||
): Window & typeof global => {
|
||||
if (el && 'window' in el && el.window === el) {
|
||||
return el;
|
||||
}
|
||||
|
||||
const doc = getOwnerDocument(el as Element | null | undefined);
|
||||
return doc.defaultView || window;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard that checks if a value is a Node. Verifies the presence and type of the nodeType property.
|
||||
*/
|
||||
function isNode(value: unknown): value is Node {
|
||||
return value !== null &&
|
||||
typeof value === 'object' &&
|
||||
'nodeType' in value &&
|
||||
typeof (value as Node).nodeType === 'number';
|
||||
}
|
||||
/**
|
||||
* Type guard that checks if a node is a ShadowRoot. Uses nodeType and host property checks to
|
||||
* distinguish ShadowRoot from other DocumentFragments.
|
||||
*/
|
||||
export function isShadowRoot(node: Node | null): node is ShadowRoot {
|
||||
return isNode(node) &&
|
||||
node.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
|
||||
'host' in node;
|
||||
}
|
||||
76
node_modules/@react-aria/utils/src/filterDOMProps.ts
generated
vendored
Normal file
76
node_modules/@react-aria/utils/src/filterDOMProps.ts
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {AriaLabelingProps, DOMProps, LinkDOMProps} from '@react-types/shared';
|
||||
|
||||
const DOMPropNames = new Set([
|
||||
'id'
|
||||
]);
|
||||
|
||||
const labelablePropNames = new Set([
|
||||
'aria-label',
|
||||
'aria-labelledby',
|
||||
'aria-describedby',
|
||||
'aria-details'
|
||||
]);
|
||||
|
||||
// See LinkDOMProps in dom.d.ts.
|
||||
const linkPropNames = new Set([
|
||||
'href',
|
||||
'hrefLang',
|
||||
'target',
|
||||
'rel',
|
||||
'download',
|
||||
'ping',
|
||||
'referrerPolicy'
|
||||
]);
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* If labelling associated aria properties should be included in the filter.
|
||||
*/
|
||||
labelable?: boolean,
|
||||
/** Whether the element is a link and should include DOM props for <a> elements. */
|
||||
isLink?: boolean,
|
||||
/**
|
||||
* A Set of other property names that should be included in the filter.
|
||||
*/
|
||||
propNames?: Set<string>
|
||||
}
|
||||
|
||||
const propRe = /^(data-.*)$/;
|
||||
|
||||
/**
|
||||
* Filters out all props that aren't valid DOM props or defined via override prop obj.
|
||||
* @param props - The component props to be filtered.
|
||||
* @param opts - Props to override.
|
||||
*/
|
||||
export function filterDOMProps(props: DOMProps & AriaLabelingProps & LinkDOMProps, opts: Options = {}): DOMProps & AriaLabelingProps {
|
||||
let {labelable, isLink, propNames} = opts;
|
||||
let filteredProps = {};
|
||||
|
||||
for (const prop in props) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(props, prop) && (
|
||||
DOMPropNames.has(prop) ||
|
||||
(labelable && labelablePropNames.has(prop)) ||
|
||||
(isLink && linkPropNames.has(prop)) ||
|
||||
propNames?.has(prop) ||
|
||||
propRe.test(prop)
|
||||
)
|
||||
) {
|
||||
filteredProps[prop] = props[prop];
|
||||
}
|
||||
}
|
||||
|
||||
return filteredProps;
|
||||
}
|
||||
96
node_modules/@react-aria/utils/src/focusWithoutScrolling.ts
generated
vendored
Normal file
96
node_modules/@react-aria/utils/src/focusWithoutScrolling.ts
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {FocusableElement} from '@react-types/shared';
|
||||
|
||||
// This is a polyfill for element.focus({preventScroll: true});
|
||||
// Currently necessary for Safari and old Edge:
|
||||
// https://caniuse.com/#feat=mdn-api_htmlelement_focus_preventscroll_option
|
||||
// See https://bugs.webkit.org/show_bug.cgi?id=178583
|
||||
//
|
||||
|
||||
// Original licensing for the following methods can be found in the
|
||||
// NOTICE file in the root directory of this source tree.
|
||||
// See https://github.com/calvellido/focus-options-polyfill
|
||||
|
||||
interface ScrollableElement {
|
||||
element: HTMLElement,
|
||||
scrollTop: number,
|
||||
scrollLeft: number
|
||||
}
|
||||
|
||||
export function focusWithoutScrolling(element: FocusableElement): void {
|
||||
if (supportsPreventScroll()) {
|
||||
element.focus({preventScroll: true});
|
||||
} else {
|
||||
let scrollableElements = getScrollableElements(element);
|
||||
element.focus();
|
||||
restoreScrollPosition(scrollableElements);
|
||||
}
|
||||
}
|
||||
|
||||
let supportsPreventScrollCached: boolean | null = null;
|
||||
function supportsPreventScroll() {
|
||||
if (supportsPreventScrollCached == null) {
|
||||
supportsPreventScrollCached = false;
|
||||
try {
|
||||
let focusElem = document.createElement('div');
|
||||
focusElem.focus({
|
||||
get preventScroll() {
|
||||
supportsPreventScrollCached = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
return supportsPreventScrollCached;
|
||||
}
|
||||
|
||||
function getScrollableElements(element: FocusableElement): ScrollableElement[] {
|
||||
let parent = element.parentNode;
|
||||
let scrollableElements: ScrollableElement[] = [];
|
||||
let rootScrollingElement = document.scrollingElement || document.documentElement;
|
||||
|
||||
while (parent instanceof HTMLElement && parent !== rootScrollingElement) {
|
||||
if (
|
||||
parent.offsetHeight < parent.scrollHeight ||
|
||||
parent.offsetWidth < parent.scrollWidth
|
||||
) {
|
||||
scrollableElements.push({
|
||||
element: parent,
|
||||
scrollTop: parent.scrollTop,
|
||||
scrollLeft: parent.scrollLeft
|
||||
});
|
||||
}
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
|
||||
if (rootScrollingElement instanceof HTMLElement) {
|
||||
scrollableElements.push({
|
||||
element: rootScrollingElement,
|
||||
scrollTop: rootScrollingElement.scrollTop,
|
||||
scrollLeft: rootScrollingElement.scrollLeft
|
||||
});
|
||||
}
|
||||
|
||||
return scrollableElements;
|
||||
}
|
||||
|
||||
function restoreScrollPosition(scrollableElements: ScrollableElement[]) {
|
||||
for (let {element, scrollTop, scrollLeft} of scrollableElements) {
|
||||
element.scrollTop = scrollTop;
|
||||
element.scrollLeft = scrollLeft;
|
||||
}
|
||||
}
|
||||
21
node_modules/@react-aria/utils/src/getOffset.ts
generated
vendored
Normal file
21
node_modules/@react-aria/utils/src/getOffset.ts
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {Orientation} from '@react-types/shared';
|
||||
|
||||
export function getOffset(element: HTMLElement, reverse?: boolean, orientation: Orientation = 'horizontal'): number {
|
||||
let rect = element.getBoundingClientRect();
|
||||
if (reverse) {
|
||||
return orientation === 'horizontal' ? rect.right : rect.bottom;
|
||||
}
|
||||
return orientation === 'horizontal' ? rect.left : rect.top;
|
||||
}
|
||||
27
node_modules/@react-aria/utils/src/getScrollParent.ts
generated
vendored
Normal file
27
node_modules/@react-aria/utils/src/getScrollParent.ts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {isScrollable} from './isScrollable';
|
||||
|
||||
export function getScrollParent(node: Element, checkForOverflow?: boolean): Element {
|
||||
let scrollableNode: Element | null = node;
|
||||
if (isScrollable(scrollableNode, checkForOverflow)) {
|
||||
scrollableNode = scrollableNode.parentElement;
|
||||
}
|
||||
|
||||
while (scrollableNode && !isScrollable(scrollableNode, checkForOverflow)) {
|
||||
scrollableNode = scrollableNode.parentElement;
|
||||
}
|
||||
|
||||
return scrollableNode || document.scrollingElement || document.documentElement;
|
||||
}
|
||||
|
||||
26
node_modules/@react-aria/utils/src/getScrollParents.ts
generated
vendored
Normal file
26
node_modules/@react-aria/utils/src/getScrollParents.ts
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2024 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {isScrollable} from './isScrollable';
|
||||
|
||||
export function getScrollParents(node: Element, checkForOverflow?: boolean): Element[] {
|
||||
const scrollParents: Element[] = [];
|
||||
|
||||
while (node && node !== document.documentElement) {
|
||||
if (isScrollable(node, checkForOverflow)) {
|
||||
scrollParents.push(node);
|
||||
}
|
||||
node = node.parentElement as Element;
|
||||
}
|
||||
|
||||
return scrollParents;
|
||||
}
|
||||
55
node_modules/@react-aria/utils/src/index.ts
generated
vendored
Normal file
55
node_modules/@react-aria/utils/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
export {useId, mergeIds, useSlotId} from './useId';
|
||||
export {chain} from './chain';
|
||||
export {createShadowTreeWalker, ShadowTreeWalker} from './shadowdom/ShadowTreeWalker';
|
||||
export {getActiveElement, getEventTarget, nodeContains} from './shadowdom/DOMFunctions';
|
||||
export {getOwnerDocument, getOwnerWindow, isShadowRoot} from './domHelpers';
|
||||
export {mergeProps} from './mergeProps';
|
||||
export {mergeRefs} from './mergeRefs';
|
||||
export {filterDOMProps} from './filterDOMProps';
|
||||
export {focusWithoutScrolling} from './focusWithoutScrolling';
|
||||
export {getOffset} from './getOffset';
|
||||
export {openLink, getSyntheticLinkProps, useSyntheticLinkProps, RouterProvider, shouldClientNavigate, useRouter, useLinkProps} from './openLink';
|
||||
export {runAfterTransition} from './runAfterTransition';
|
||||
export {useDrag1D} from './useDrag1D';
|
||||
export {useGlobalListeners} from './useGlobalListeners';
|
||||
export {useLabels} from './useLabels';
|
||||
export {useObjectRef} from './useObjectRef';
|
||||
export {useUpdateEffect} from './useUpdateEffect';
|
||||
export {useUpdateLayoutEffect} from './useUpdateLayoutEffect';
|
||||
export {useLayoutEffect} from './useLayoutEffect';
|
||||
export {useResizeObserver} from './useResizeObserver';
|
||||
export {useSyncRef} from './useSyncRef';
|
||||
export {getScrollParent} from './getScrollParent';
|
||||
export {getScrollParents} from './getScrollParents';
|
||||
export {isScrollable} from './isScrollable';
|
||||
export {useViewportSize} from './useViewportSize';
|
||||
export {useDescription} from './useDescription';
|
||||
export {isMac, isIPhone, isIPad, isIOS, isAppleDevice, isWebKit, isChrome, isAndroid, isFirefox} from './platform';
|
||||
export {useEvent} from './useEvent';
|
||||
export {useValueEffect} from './useValueEffect';
|
||||
export {scrollIntoView, scrollIntoViewport} from './scrollIntoView';
|
||||
export {clamp, snapValueToStep} from '@react-stately/utils';
|
||||
export {isVirtualClick, isVirtualPointerEvent} from './isVirtualEvent';
|
||||
export {useEffectEvent} from './useEffectEvent';
|
||||
export {useDeepMemo} from './useDeepMemo';
|
||||
export {useFormReset} from './useFormReset';
|
||||
export {useLoadMore} from './useLoadMore';
|
||||
export {UNSTABLE_useLoadMoreSentinel} from './useLoadMoreSentinel';
|
||||
export {inertValue} from './inertValue';
|
||||
export {CLEAR_FOCUS_EVENT, FOCUS_EVENT} from './constants';
|
||||
export {isCtrlKeyPressed} from './keyboard';
|
||||
export {useEnterAnimation, useExitAnimation} from './animation';
|
||||
export {isFocusable, isTabbable} from './isFocusable';
|
||||
|
||||
export type {LoadMoreSentinelProps} from './useLoadMoreSentinel';
|
||||
11
node_modules/@react-aria/utils/src/inertValue.ts
generated
vendored
Normal file
11
node_modules/@react-aria/utils/src/inertValue.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import {version} from 'react';
|
||||
|
||||
export function inertValue(value?: boolean): string | boolean | undefined {
|
||||
const pieces = version.split('.');
|
||||
const major = parseInt(pieces[0], 10);
|
||||
if (major >= 19) {
|
||||
return value;
|
||||
}
|
||||
// compatibility with React < 19
|
||||
return value ? 'true' : undefined;
|
||||
}
|
||||
28
node_modules/@react-aria/utils/src/isFocusable.ts
generated
vendored
Normal file
28
node_modules/@react-aria/utils/src/isFocusable.ts
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
const focusableElements = [
|
||||
'input:not([disabled]):not([type=hidden])',
|
||||
'select:not([disabled])',
|
||||
'textarea:not([disabled])',
|
||||
'button:not([disabled])',
|
||||
'a[href]',
|
||||
'area[href]',
|
||||
'summary',
|
||||
'iframe',
|
||||
'object',
|
||||
'embed',
|
||||
'audio[controls]',
|
||||
'video[controls]',
|
||||
'[contenteditable]:not([contenteditable^="false"])'
|
||||
];
|
||||
|
||||
const FOCUSABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]),') + ',[tabindex]:not([disabled]):not([hidden])';
|
||||
|
||||
focusableElements.push('[tabindex]:not([tabindex="-1"]):not([disabled])');
|
||||
const TABBABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]):not([tabindex="-1"]),');
|
||||
|
||||
export function isFocusable(element: Element): boolean {
|
||||
return element.matches(FOCUSABLE_ELEMENT_SELECTOR);
|
||||
}
|
||||
|
||||
export function isTabbable(element: Element): boolean {
|
||||
return element.matches(TABBABLE_ELEMENT_SELECTOR);
|
||||
}
|
||||
25
node_modules/@react-aria/utils/src/isScrollable.ts
generated
vendored
Normal file
25
node_modules/@react-aria/utils/src/isScrollable.ts
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2024 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
export function isScrollable(node: Element | null, checkForOverflow?: boolean): boolean {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
let style = window.getComputedStyle(node);
|
||||
let isScrollable = /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY);
|
||||
|
||||
if (isScrollable && checkForOverflow) {
|
||||
isScrollable = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
|
||||
}
|
||||
|
||||
return isScrollable;
|
||||
}
|
||||
58
node_modules/@react-aria/utils/src/isVirtualEvent.ts
generated
vendored
Normal file
58
node_modules/@react-aria/utils/src/isVirtualEvent.ts
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2022 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {isAndroid} from './platform';
|
||||
|
||||
// Original licensing for the following method can be found in the
|
||||
// NOTICE file in the root directory of this source tree.
|
||||
// See https://github.com/facebook/react/blob/3c713d513195a53788b3f8bb4b70279d68b15bcc/packages/react-interactions/events/src/dom/shared/index.js#L74-L87
|
||||
|
||||
// Keyboards, Assistive Technologies, and element.click() all produce a "virtual"
|
||||
// click event. This is a method of inferring such clicks. Every browser except
|
||||
// IE 11 only sets a zero value of "detail" for click events that are "virtual".
|
||||
// However, IE 11 uses a zero value for all click events. For IE 11 we rely on
|
||||
// the quirk that it produces click events that are of type PointerEvent, and
|
||||
// where only the "virtual" click lacks a pointerType field.
|
||||
|
||||
export function isVirtualClick(event: MouseEvent | PointerEvent): boolean {
|
||||
// JAWS/NVDA with Firefox.
|
||||
if ((event as any).mozInputSource === 0 && event.isTrusted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Android TalkBack's detail value varies depending on the event listener providing the event so we have specific logic here instead
|
||||
// If pointerType is defined, event is from a click listener. For events from mousedown listener, detail === 0 is a sufficient check
|
||||
// to detect TalkBack virtual clicks.
|
||||
if (isAndroid() && (event as PointerEvent).pointerType) {
|
||||
return event.type === 'click' && event.buttons === 1;
|
||||
}
|
||||
|
||||
return event.detail === 0 && !(event as PointerEvent).pointerType;
|
||||
}
|
||||
|
||||
export function isVirtualPointerEvent(event: PointerEvent): boolean {
|
||||
// If the pointer size is zero, then we assume it's from a screen reader.
|
||||
// Android TalkBack double tap will sometimes return a event with width and height of 1
|
||||
// and pointerType === 'mouse' so we need to check for a specific combination of event attributes.
|
||||
// Cannot use "event.pressure === 0" as the sole check due to Safari pointer events always returning pressure === 0
|
||||
// instead of .5, see https://bugs.webkit.org/show_bug.cgi?id=206216. event.pointerType === 'mouse' is to distingush
|
||||
// Talkback double tap from Windows Firefox touch screen press
|
||||
return (
|
||||
(!isAndroid() && event.width === 0 && event.height === 0) ||
|
||||
(event.width === 1 &&
|
||||
event.height === 1 &&
|
||||
event.pressure === 0 &&
|
||||
event.detail === 0 &&
|
||||
event.pointerType === 'mouse'
|
||||
)
|
||||
);
|
||||
}
|
||||
27
node_modules/@react-aria/utils/src/keyboard.tsx
generated
vendored
Normal file
27
node_modules/@react-aria/utils/src/keyboard.tsx
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2024 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {isMac} from './platform';
|
||||
|
||||
interface Event {
|
||||
altKey: boolean,
|
||||
ctrlKey: boolean,
|
||||
metaKey: boolean
|
||||
}
|
||||
|
||||
export function isCtrlKeyPressed(e: Event): boolean {
|
||||
if (isMac()) {
|
||||
return e.metaKey;
|
||||
}
|
||||
|
||||
return e.ctrlKey;
|
||||
}
|
||||
75
node_modules/@react-aria/utils/src/mergeProps.ts
generated
vendored
Normal file
75
node_modules/@react-aria/utils/src/mergeProps.ts
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {chain} from './chain';
|
||||
import clsx from 'clsx';
|
||||
import {mergeIds} from './useId';
|
||||
|
||||
interface Props {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type PropsArg = Props | null | undefined;
|
||||
|
||||
// taken from: https://stackoverflow.com/questions/51603250/typescript-3-parameter-list-intersection-type/51604379#51604379
|
||||
type TupleTypes<T> = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? NullToObject<V> : never;
|
||||
type NullToObject<T> = T extends (null | undefined) ? {} : T;
|
||||
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
||||
|
||||
/**
|
||||
* Merges multiple props objects together. Event handlers are chained,
|
||||
* classNames are combined, and ids are deduplicated - different ids
|
||||
* will trigger a side-effect and re-render components hooked up with `useId`.
|
||||
* For all other props, the last prop object overrides all previous ones.
|
||||
* @param args - Multiple sets of props to merge together.
|
||||
*/
|
||||
export function mergeProps<T extends PropsArg[]>(...args: T): UnionToIntersection<TupleTypes<T>> {
|
||||
// Start with a base clone of the first argument. This is a lot faster than starting
|
||||
// with an empty object and adding properties as we go.
|
||||
let result: Props = {...args[0]};
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
let props = args[i];
|
||||
for (let key in props) {
|
||||
let a = result[key];
|
||||
let b = props[key];
|
||||
|
||||
// Chain events
|
||||
if (
|
||||
typeof a === 'function' &&
|
||||
typeof b === 'function' &&
|
||||
// This is a lot faster than a regex.
|
||||
key[0] === 'o' &&
|
||||
key[1] === 'n' &&
|
||||
key.charCodeAt(2) >= /* 'A' */ 65 &&
|
||||
key.charCodeAt(2) <= /* 'Z' */ 90
|
||||
) {
|
||||
result[key] = chain(a, b);
|
||||
|
||||
// Merge classnames, sometimes classNames are empty string which eval to false, so we just need to do a type check
|
||||
} else if (
|
||||
(key === 'className' || key === 'UNSAFE_className') &&
|
||||
typeof a === 'string' &&
|
||||
typeof b === 'string'
|
||||
) {
|
||||
result[key] = clsx(a, b);
|
||||
} else if (key === 'id' && a && b) {
|
||||
result.id = mergeIds(a, b);
|
||||
// Override others
|
||||
} else {
|
||||
result[key] = b !== undefined ? b : a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result as UnionToIntersection<TupleTypes<T>>;
|
||||
}
|
||||
52
node_modules/@react-aria/utils/src/mergeRefs.ts
generated
vendored
Normal file
52
node_modules/@react-aria/utils/src/mergeRefs.ts
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {MutableRefObject, Ref} from 'react';
|
||||
|
||||
/**
|
||||
* Merges multiple refs into one. Works with either callback or object refs.
|
||||
*/
|
||||
export function mergeRefs<T>(...refs: Array<Ref<T> | MutableRefObject<T> | null | undefined>): Ref<T> {
|
||||
if (refs.length === 1 && refs[0]) {
|
||||
return refs[0];
|
||||
}
|
||||
|
||||
return (value: T | null) => {
|
||||
let hasCleanup = false;
|
||||
|
||||
const cleanups = refs.map(ref => {
|
||||
const cleanup = setRef(ref, value);
|
||||
hasCleanup ||= typeof cleanup == 'function';
|
||||
return cleanup;
|
||||
});
|
||||
|
||||
if (hasCleanup) {
|
||||
return () => {
|
||||
cleanups.forEach((cleanup, i) => {
|
||||
if (typeof cleanup === 'function') {
|
||||
cleanup();
|
||||
} else {
|
||||
setRef(refs[i], null);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function setRef<T>(ref: Ref<T> | MutableRefObject<T> | null | undefined, value: T) {
|
||||
if (typeof ref === 'function') {
|
||||
return ref(value);
|
||||
} else if (ref != null) {
|
||||
ref.current = value;
|
||||
}
|
||||
}
|
||||
185
node_modules/@react-aria/utils/src/openLink.tsx
generated
vendored
Normal file
185
node_modules/@react-aria/utils/src/openLink.tsx
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2023 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {focusWithoutScrolling, isMac, isWebKit} from './index';
|
||||
import {Href, LinkDOMProps, RouterOptions} from '@react-types/shared';
|
||||
import {isFirefox, isIPad} from './platform';
|
||||
import React, {createContext, DOMAttributes, JSX, ReactNode, useContext, useMemo} from 'react';
|
||||
|
||||
interface Router {
|
||||
isNative: boolean,
|
||||
open: (target: Element, modifiers: Modifiers, href: Href, routerOptions: RouterOptions | undefined) => void,
|
||||
useHref: (href: Href) => string
|
||||
}
|
||||
|
||||
const RouterContext = createContext<Router>({
|
||||
isNative: true,
|
||||
open: openSyntheticLink,
|
||||
useHref: (href) => href
|
||||
});
|
||||
|
||||
interface RouterProviderProps {
|
||||
navigate: (path: Href, routerOptions: RouterOptions | undefined) => void,
|
||||
useHref?: (href: Href) => string,
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* A RouterProvider accepts a `navigate` function from a framework or client side router,
|
||||
* and provides it to all nested React Aria links to enable client side navigation.
|
||||
*/
|
||||
export function RouterProvider(props: RouterProviderProps): JSX.Element {
|
||||
let {children, navigate, useHref} = props;
|
||||
|
||||
let ctx = useMemo(() => ({
|
||||
isNative: false,
|
||||
open: (target: Element, modifiers: Modifiers, href: Href, routerOptions: RouterOptions | undefined) => {
|
||||
getSyntheticLink(target, link => {
|
||||
if (shouldClientNavigate(link, modifiers)) {
|
||||
navigate(href, routerOptions);
|
||||
} else {
|
||||
openLink(link, modifiers);
|
||||
}
|
||||
});
|
||||
},
|
||||
useHref: useHref || ((href) => href)
|
||||
}), [navigate, useHref]);
|
||||
|
||||
return (
|
||||
<RouterContext.Provider value={ctx}>
|
||||
{children}
|
||||
</RouterContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useRouter(): Router {
|
||||
return useContext(RouterContext);
|
||||
}
|
||||
|
||||
interface Modifiers {
|
||||
metaKey?: boolean,
|
||||
ctrlKey?: boolean,
|
||||
altKey?: boolean,
|
||||
shiftKey?: boolean
|
||||
}
|
||||
|
||||
export function shouldClientNavigate(link: HTMLAnchorElement, modifiers: Modifiers): boolean {
|
||||
// Use getAttribute here instead of link.target. Firefox will default link.target to "_parent" when inside an iframe.
|
||||
let target = link.getAttribute('target');
|
||||
return (
|
||||
(!target || target === '_self') &&
|
||||
link.origin === location.origin &&
|
||||
!link.hasAttribute('download') &&
|
||||
!modifiers.metaKey && // open in new tab (mac)
|
||||
!modifiers.ctrlKey && // open in new tab (windows)
|
||||
!modifiers.altKey && // download
|
||||
!modifiers.shiftKey
|
||||
);
|
||||
}
|
||||
|
||||
export function openLink(target: HTMLAnchorElement, modifiers: Modifiers, setOpening = true): void {
|
||||
let {metaKey, ctrlKey, altKey, shiftKey} = modifiers;
|
||||
|
||||
// Firefox does not recognize keyboard events as a user action by default, and the popup blocker
|
||||
// will prevent links with target="_blank" from opening. However, it does allow the event if the
|
||||
// Command/Control key is held, which opens the link in a background tab. This seems like the best we can do.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=257870 and https://bugzilla.mozilla.org/show_bug.cgi?id=746640.
|
||||
if (isFirefox() && window.event?.type?.startsWith('key') && target.target === '_blank') {
|
||||
if (isMac()) {
|
||||
metaKey = true;
|
||||
} else {
|
||||
ctrlKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
// WebKit does not support firing click events with modifier keys, but does support keyboard events.
|
||||
// https://github.com/WebKit/WebKit/blob/c03d0ac6e6db178f90923a0a63080b5ca210d25f/Source/WebCore/html/HTMLAnchorElement.cpp#L184
|
||||
let event = isWebKit() && isMac() && !isIPad() && process.env.NODE_ENV !== 'test'
|
||||
// @ts-ignore - keyIdentifier is a non-standard property, but it's what webkit expects
|
||||
? new KeyboardEvent('keydown', {keyIdentifier: 'Enter', metaKey, ctrlKey, altKey, shiftKey})
|
||||
: new MouseEvent('click', {metaKey, ctrlKey, altKey, shiftKey, bubbles: true, cancelable: true});
|
||||
(openLink as any).isOpening = setOpening;
|
||||
focusWithoutScrolling(target);
|
||||
target.dispatchEvent(event);
|
||||
(openLink as any).isOpening = false;
|
||||
}
|
||||
// https://github.com/parcel-bundler/parcel/issues/8724
|
||||
(openLink as any).isOpening = false;
|
||||
|
||||
function getSyntheticLink(target: Element, open: (link: HTMLAnchorElement) => void) {
|
||||
if (target instanceof HTMLAnchorElement) {
|
||||
open(target);
|
||||
} else if (target.hasAttribute('data-href')) {
|
||||
let link = document.createElement('a');
|
||||
link.href = target.getAttribute('data-href')!;
|
||||
if (target.hasAttribute('data-target')) {
|
||||
link.target = target.getAttribute('data-target')!;
|
||||
}
|
||||
if (target.hasAttribute('data-rel')) {
|
||||
link.rel = target.getAttribute('data-rel')!;
|
||||
}
|
||||
if (target.hasAttribute('data-download')) {
|
||||
link.download = target.getAttribute('data-download')!;
|
||||
}
|
||||
if (target.hasAttribute('data-ping')) {
|
||||
link.ping = target.getAttribute('data-ping')!;
|
||||
}
|
||||
if (target.hasAttribute('data-referrer-policy')) {
|
||||
link.referrerPolicy = target.getAttribute('data-referrer-policy')!;
|
||||
}
|
||||
target.appendChild(link);
|
||||
open(link);
|
||||
target.removeChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
function openSyntheticLink(target: Element, modifiers: Modifiers) {
|
||||
getSyntheticLink(target, link => openLink(link, modifiers));
|
||||
}
|
||||
|
||||
export function useSyntheticLinkProps(props: LinkDOMProps): DOMAttributes<HTMLElement> {
|
||||
let router = useRouter();
|
||||
const href = router.useHref(props.href ?? '');
|
||||
return {
|
||||
'data-href': props.href ? href : undefined,
|
||||
'data-target': props.target,
|
||||
'data-rel': props.rel,
|
||||
'data-download': props.download,
|
||||
'data-ping': props.ping,
|
||||
'data-referrer-policy': props.referrerPolicy
|
||||
} as DOMAttributes<HTMLElement>;
|
||||
}
|
||||
|
||||
/** @deprecated - For backward compatibility. */
|
||||
export function getSyntheticLinkProps(props: LinkDOMProps): DOMAttributes<HTMLElement> {
|
||||
return {
|
||||
'data-href': props.href,
|
||||
'data-target': props.target,
|
||||
'data-rel': props.rel,
|
||||
'data-download': props.download,
|
||||
'data-ping': props.ping,
|
||||
'data-referrer-policy': props.referrerPolicy
|
||||
} as DOMAttributes<HTMLElement>;
|
||||
}
|
||||
|
||||
export function useLinkProps(props?: LinkDOMProps): LinkDOMProps {
|
||||
let router = useRouter();
|
||||
const href = router.useHref(props?.href ?? '');
|
||||
return {
|
||||
href: props?.href ? href : undefined,
|
||||
target: props?.target,
|
||||
rel: props?.rel,
|
||||
download: props?.download,
|
||||
ping: props?.ping,
|
||||
referrerPolicy: props?.referrerPolicy
|
||||
};
|
||||
}
|
||||
79
node_modules/@react-aria/utils/src/platform.ts
generated
vendored
Normal file
79
node_modules/@react-aria/utils/src/platform.ts
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
function testUserAgent(re: RegExp) {
|
||||
if (typeof window === 'undefined' || window.navigator == null) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
window.navigator['userAgentData']?.brands.some((brand: {brand: string, version: string}) => re.test(brand.brand))
|
||||
) ||
|
||||
re.test(window.navigator.userAgent);
|
||||
}
|
||||
|
||||
function testPlatform(re: RegExp) {
|
||||
return typeof window !== 'undefined' && window.navigator != null
|
||||
? re.test(window.navigator['userAgentData']?.platform || window.navigator.platform)
|
||||
: false;
|
||||
}
|
||||
|
||||
function cached(fn: () => boolean) {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return fn;
|
||||
}
|
||||
|
||||
let res: boolean | null = null;
|
||||
return () => {
|
||||
if (res == null) {
|
||||
res = fn();
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
export const isMac = cached(function () {
|
||||
return testPlatform(/^Mac/i);
|
||||
});
|
||||
|
||||
export const isIPhone = cached(function () {
|
||||
return testPlatform(/^iPhone/i);
|
||||
});
|
||||
|
||||
export const isIPad = cached(function () {
|
||||
return testPlatform(/^iPad/i) ||
|
||||
// iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
|
||||
(isMac() && navigator.maxTouchPoints > 1);
|
||||
});
|
||||
|
||||
export const isIOS = cached(function () {
|
||||
return isIPhone() || isIPad();
|
||||
});
|
||||
|
||||
export const isAppleDevice = cached(function () {
|
||||
return isMac() || isIOS();
|
||||
});
|
||||
|
||||
export const isWebKit = cached(function () {
|
||||
return testUserAgent(/AppleWebKit/i) && !isChrome();
|
||||
});
|
||||
|
||||
export const isChrome = cached(function () {
|
||||
return testUserAgent(/Chrome/i);
|
||||
});
|
||||
|
||||
export const isAndroid = cached(function () {
|
||||
return testUserAgent(/Android/i);
|
||||
});
|
||||
|
||||
export const isFirefox = cached(function () {
|
||||
return testUserAgent(/Firefox/i);
|
||||
});
|
||||
121
node_modules/@react-aria/utils/src/runAfterTransition.ts
generated
vendored
Normal file
121
node_modules/@react-aria/utils/src/runAfterTransition.ts
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
// We store a global list of elements that are currently transitioning,
|
||||
// mapped to a set of CSS properties that are transitioning for that element.
|
||||
// This is necessary rather than a simple count of transitions because of browser
|
||||
// bugs, e.g. Chrome sometimes fires both transitionend and transitioncancel rather
|
||||
// than one or the other. So we need to track what's actually transitioning so that
|
||||
// we can ignore these duplicate events.
|
||||
let transitionsByElement = new Map<EventTarget, Set<string>>();
|
||||
|
||||
// A list of callbacks to call once there are no transitioning elements.
|
||||
let transitionCallbacks = new Set<() => void>();
|
||||
|
||||
function setupGlobalEvents() {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
function isTransitionEvent(event: Event): event is TransitionEvent {
|
||||
return 'propertyName' in event;
|
||||
}
|
||||
|
||||
let onTransitionStart = (e: Event) => {
|
||||
if (!isTransitionEvent(e) || !e.target) {
|
||||
return;
|
||||
}
|
||||
// Add the transitioning property to the list for this element.
|
||||
let transitions = transitionsByElement.get(e.target);
|
||||
if (!transitions) {
|
||||
transitions = new Set();
|
||||
transitionsByElement.set(e.target, transitions);
|
||||
|
||||
// The transitioncancel event must be registered on the element itself, rather than as a global
|
||||
// event. This enables us to handle when the node is deleted from the document while it is transitioning.
|
||||
// In that case, the cancel event would have nowhere to bubble to so we need to handle it directly.
|
||||
e.target.addEventListener('transitioncancel', onTransitionEnd, {
|
||||
once: true
|
||||
});
|
||||
}
|
||||
|
||||
transitions.add(e.propertyName);
|
||||
};
|
||||
|
||||
let onTransitionEnd = (e: Event) => {
|
||||
if (!isTransitionEvent(e) || !e.target) {
|
||||
return;
|
||||
}
|
||||
// Remove property from list of transitioning properties.
|
||||
let properties = transitionsByElement.get(e.target);
|
||||
if (!properties) {
|
||||
return;
|
||||
}
|
||||
|
||||
properties.delete(e.propertyName);
|
||||
|
||||
// If empty, remove transitioncancel event, and remove the element from the list of transitioning elements.
|
||||
if (properties.size === 0) {
|
||||
e.target.removeEventListener('transitioncancel', onTransitionEnd);
|
||||
transitionsByElement.delete(e.target);
|
||||
}
|
||||
|
||||
// If no transitioning elements, call all of the queued callbacks.
|
||||
if (transitionsByElement.size === 0) {
|
||||
for (let cb of transitionCallbacks) {
|
||||
cb();
|
||||
}
|
||||
|
||||
transitionCallbacks.clear();
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener('transitionrun', onTransitionStart);
|
||||
document.body.addEventListener('transitionend', onTransitionEnd);
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
if (document.readyState !== 'loading') {
|
||||
setupGlobalEvents();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', setupGlobalEvents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up any elements that are no longer in the document.
|
||||
* This is necessary because we can't rely on transitionend events to fire
|
||||
* for elements that are removed from the document while transitioning.
|
||||
*/
|
||||
function cleanupDetachedElements() {
|
||||
for (const [eventTarget] of transitionsByElement) {
|
||||
// Similar to `eventTarget instanceof Element && !eventTarget.isConnected`, but avoids
|
||||
// the explicit instanceof check, since it may be different in different contexts.
|
||||
if ('isConnected' in eventTarget && !eventTarget.isConnected) {
|
||||
transitionsByElement.delete(eventTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runAfterTransition(fn: () => void): void {
|
||||
// Wait one frame to see if an animation starts, e.g. a transition on mount.
|
||||
requestAnimationFrame(() => {
|
||||
cleanupDetachedElements();
|
||||
// If no transitions are running, call the function immediately.
|
||||
// Otherwise, add it to a list of callbacks to run at the end of the animation.
|
||||
if (transitionsByElement.size === 0) {
|
||||
fn();
|
||||
} else {
|
||||
transitionCallbacks.add(fn);
|
||||
}
|
||||
});
|
||||
}
|
||||
125
node_modules/@react-aria/utils/src/scrollIntoView.ts
generated
vendored
Normal file
125
node_modules/@react-aria/utils/src/scrollIntoView.ts
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {getScrollParents} from './getScrollParents';
|
||||
|
||||
interface ScrollIntoViewportOpts {
|
||||
/** The optional containing element of the target to be centered in the viewport. */
|
||||
containingElement?: Element | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls `scrollView` so that `element` is visible.
|
||||
* Similar to `element.scrollIntoView({block: 'nearest'})` (not supported in Edge),
|
||||
* but doesn't affect parents above `scrollView`.
|
||||
*/
|
||||
export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement): void {
|
||||
let offsetX = relativeOffset(scrollView, element, 'left');
|
||||
let offsetY = relativeOffset(scrollView, element, 'top');
|
||||
let width = element.offsetWidth;
|
||||
let height = element.offsetHeight;
|
||||
let x = scrollView.scrollLeft;
|
||||
let y = scrollView.scrollTop;
|
||||
|
||||
// Account for top/left border offsetting the scroll top/Left + scroll padding
|
||||
let {
|
||||
borderTopWidth,
|
||||
borderLeftWidth,
|
||||
scrollPaddingTop,
|
||||
scrollPaddingRight,
|
||||
scrollPaddingBottom,
|
||||
scrollPaddingLeft
|
||||
} = getComputedStyle(scrollView);
|
||||
|
||||
let borderAdjustedX = x + parseInt(borderLeftWidth, 10);
|
||||
let borderAdjustedY = y + parseInt(borderTopWidth, 10);
|
||||
// Ignore end/bottom border via clientHeight/Width instead of offsetHeight/Width
|
||||
let maxX = borderAdjustedX + scrollView.clientWidth;
|
||||
let maxY = borderAdjustedY + scrollView.clientHeight;
|
||||
|
||||
// Get scroll padding values as pixels - defaults to 0 if no scroll padding
|
||||
// is used.
|
||||
let scrollPaddingTopNumber = parseInt(scrollPaddingTop, 10) || 0;
|
||||
let scrollPaddingBottomNumber = parseInt(scrollPaddingBottom, 10) || 0;
|
||||
let scrollPaddingRightNumber = parseInt(scrollPaddingRight, 10) || 0;
|
||||
let scrollPaddingLeftNumber = parseInt(scrollPaddingLeft, 10) || 0;
|
||||
|
||||
if (offsetX <= x + scrollPaddingLeftNumber) {
|
||||
x = offsetX - parseInt(borderLeftWidth, 10) - scrollPaddingLeftNumber;
|
||||
} else if (offsetX + width > maxX - scrollPaddingRightNumber) {
|
||||
x += offsetX + width - maxX + scrollPaddingRightNumber;
|
||||
}
|
||||
if (offsetY <= borderAdjustedY + scrollPaddingTopNumber) {
|
||||
y = offsetY - parseInt(borderTopWidth, 10) - scrollPaddingTopNumber;
|
||||
} else if (offsetY + height > maxY - scrollPaddingBottomNumber) {
|
||||
y += offsetY + height - maxY + scrollPaddingBottomNumber;
|
||||
}
|
||||
|
||||
scrollView.scrollLeft = x;
|
||||
scrollView.scrollTop = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the offset left or top from child to ancestor by accumulating
|
||||
* offsetLeft or offsetTop through intervening offsetParents.
|
||||
*/
|
||||
function relativeOffset(ancestor: HTMLElement, child: HTMLElement, axis: 'left'|'top') {
|
||||
const prop = axis === 'left' ? 'offsetLeft' : 'offsetTop';
|
||||
let sum = 0;
|
||||
while (child.offsetParent) {
|
||||
sum += child[prop];
|
||||
if (child.offsetParent === ancestor) {
|
||||
// Stop once we have found the ancestor we are interested in.
|
||||
break;
|
||||
} else if (child.offsetParent.contains(ancestor)) {
|
||||
// If the ancestor is not `position:relative`, then we stop at
|
||||
// _its_ offset parent, and we subtract off _its_ offset, so that
|
||||
// we end up with the proper offset from child to ancestor.
|
||||
sum -= ancestor[prop];
|
||||
break;
|
||||
}
|
||||
child = child.offsetParent as HTMLElement;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the `targetElement` so it is visible in the viewport. Accepts an optional `opts.containingElement`
|
||||
* that will be centered in the viewport prior to scrolling the targetElement into view. If scrolling is prevented on
|
||||
* the body (e.g. targetElement is in a popover), this will only scroll the scroll parents of the targetElement up to but not including the body itself.
|
||||
*/
|
||||
export function scrollIntoViewport(targetElement: Element | null, opts?: ScrollIntoViewportOpts): void {
|
||||
if (targetElement && document.contains(targetElement)) {
|
||||
let root = document.scrollingElement || document.documentElement;
|
||||
let isScrollPrevented = window.getComputedStyle(root).overflow === 'hidden';
|
||||
// If scrolling is not currently prevented then we aren’t in a overlay nor is a overlay open, just use element.scrollIntoView to bring the element into view
|
||||
if (!isScrollPrevented) {
|
||||
let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect();
|
||||
|
||||
// use scrollIntoView({block: 'nearest'}) instead of .focus to check if the element is fully in view or not since .focus()
|
||||
// won't cause a scroll if the element is already focused and doesn't behave consistently when an element is partially out of view horizontally vs vertically
|
||||
targetElement?.scrollIntoView?.({block: 'nearest'});
|
||||
let {left: newLeft, top: newTop} = targetElement.getBoundingClientRect();
|
||||
// Account for sub pixel differences from rounding
|
||||
if ((Math.abs(originalLeft - newLeft) > 1) || (Math.abs(originalTop - newTop) > 1)) {
|
||||
opts?.containingElement?.scrollIntoView?.({block: 'center', inline: 'center'});
|
||||
targetElement.scrollIntoView?.({block: 'nearest'});
|
||||
}
|
||||
} else {
|
||||
let scrollParents = getScrollParents(targetElement);
|
||||
// If scrolling is prevented, we don't want to scroll the body since it might move the overlay partially offscreen and the user can't scroll it back into view.
|
||||
for (let scrollParent of scrollParents) {
|
||||
scrollIntoView(scrollParent as HTMLElement, targetElement as HTMLElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
node_modules/@react-aria/utils/src/shadowdom/DOMFunctions.ts
generated
vendored
Normal file
70
node_modules/@react-aria/utils/src/shadowdom/DOMFunctions.ts
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Source: https://github.com/microsoft/tabster/blob/a89fc5d7e332d48f68d03b1ca6e344489d1c3898/src/Shadowdomize/DOMFunctions.ts#L16
|
||||
|
||||
import {isShadowRoot} from '../domHelpers';
|
||||
import {shadowDOM} from '@react-stately/flags';
|
||||
|
||||
/**
|
||||
* ShadowDOM safe version of Node.contains.
|
||||
*/
|
||||
export function nodeContains(
|
||||
node: Node | null | undefined,
|
||||
otherNode: Node | null | undefined
|
||||
): boolean {
|
||||
if (!shadowDOM()) {
|
||||
return otherNode && node ? node.contains(otherNode) : false;
|
||||
}
|
||||
|
||||
if (!node || !otherNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let currentNode: HTMLElement | Node | null | undefined = otherNode;
|
||||
|
||||
while (currentNode !== null) {
|
||||
if (currentNode === node) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((currentNode as HTMLSlotElement).tagName === 'SLOT' &&
|
||||
(currentNode as HTMLSlotElement).assignedSlot) {
|
||||
// Element is slotted
|
||||
currentNode = (currentNode as HTMLSlotElement).assignedSlot!.parentNode;
|
||||
} else if (isShadowRoot(currentNode)) {
|
||||
// Element is in shadow root
|
||||
currentNode = currentNode.host;
|
||||
} else {
|
||||
currentNode = currentNode.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ShadowDOM safe version of document.activeElement.
|
||||
*/
|
||||
export const getActiveElement = (doc: Document = document): Element | null => {
|
||||
if (!shadowDOM()) {
|
||||
return doc.activeElement;
|
||||
}
|
||||
let activeElement: Element | null = doc.activeElement;
|
||||
|
||||
while (activeElement && 'shadowRoot' in activeElement &&
|
||||
activeElement.shadowRoot?.activeElement) {
|
||||
activeElement = activeElement.shadowRoot.activeElement;
|
||||
}
|
||||
|
||||
return activeElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* ShadowDOM safe version of event.target.
|
||||
*/
|
||||
export function getEventTarget<T extends Event>(event: T): Element {
|
||||
if (shadowDOM() && (event.target as HTMLElement).shadowRoot) {
|
||||
if (event.composedPath) {
|
||||
return event.composedPath()[0] as Element;
|
||||
}
|
||||
}
|
||||
return event.target as Element;
|
||||
}
|
||||
319
node_modules/@react-aria/utils/src/shadowdom/ShadowTreeWalker.ts
generated
vendored
Normal file
319
node_modules/@react-aria/utils/src/shadowdom/ShadowTreeWalker.ts
generated
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
// https://github.com/microsoft/tabster/blob/a89fc5d7e332d48f68d03b1ca6e344489d1c3898/src/Shadowdomize/ShadowTreeWalker.ts
|
||||
|
||||
import {nodeContains} from './DOMFunctions';
|
||||
import {shadowDOM} from '@react-stately/flags';
|
||||
|
||||
export class ShadowTreeWalker implements TreeWalker {
|
||||
public readonly filter: NodeFilter | null;
|
||||
public readonly root: Node;
|
||||
public readonly whatToShow: number;
|
||||
|
||||
private _doc: Document;
|
||||
private _walkerStack: Array<TreeWalker> = [];
|
||||
private _currentNode: Node;
|
||||
private _currentSetFor: Set<TreeWalker> = new Set();
|
||||
|
||||
constructor(
|
||||
doc: Document,
|
||||
root: Node,
|
||||
whatToShow?: number,
|
||||
filter?: NodeFilter | null
|
||||
) {
|
||||
this._doc = doc;
|
||||
this.root = root;
|
||||
this.filter = filter ?? null;
|
||||
this.whatToShow = whatToShow ?? NodeFilter.SHOW_ALL;
|
||||
this._currentNode = root;
|
||||
|
||||
this._walkerStack.unshift(
|
||||
doc.createTreeWalker(root, whatToShow, this._acceptNode)
|
||||
);
|
||||
|
||||
const shadowRoot = (root as Element).shadowRoot;
|
||||
|
||||
if (shadowRoot) {
|
||||
const walker = this._doc.createTreeWalker(
|
||||
shadowRoot,
|
||||
this.whatToShow,
|
||||
{acceptNode: this._acceptNode}
|
||||
);
|
||||
|
||||
this._walkerStack.unshift(walker);
|
||||
}
|
||||
}
|
||||
|
||||
private _acceptNode = (node: Node): number => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const shadowRoot = (node as Element).shadowRoot;
|
||||
|
||||
if (shadowRoot) {
|
||||
const walker = this._doc.createTreeWalker(
|
||||
shadowRoot,
|
||||
this.whatToShow,
|
||||
{acceptNode: this._acceptNode}
|
||||
);
|
||||
|
||||
this._walkerStack.unshift(walker);
|
||||
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
} else {
|
||||
if (typeof this.filter === 'function') {
|
||||
return this.filter(node);
|
||||
} else if (this.filter?.acceptNode) {
|
||||
return this.filter.acceptNode(node);
|
||||
} else if (this.filter === null) {
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
};
|
||||
|
||||
public get currentNode(): Node {
|
||||
return this._currentNode;
|
||||
}
|
||||
|
||||
public set currentNode(node: Node) {
|
||||
if (!nodeContains(this.root, node)) {
|
||||
throw new Error(
|
||||
'Cannot set currentNode to a node that is not contained by the root node.'
|
||||
);
|
||||
}
|
||||
|
||||
const walkers: TreeWalker[] = [];
|
||||
let curNode: Node | null | undefined = node;
|
||||
let currentWalkerCurrentNode = node;
|
||||
|
||||
this._currentNode = node;
|
||||
|
||||
while (curNode && curNode !== this.root) {
|
||||
if (curNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
||||
const shadowRoot = curNode as ShadowRoot;
|
||||
|
||||
const walker = this._doc.createTreeWalker(
|
||||
shadowRoot,
|
||||
this.whatToShow,
|
||||
{acceptNode: this._acceptNode}
|
||||
);
|
||||
|
||||
walkers.push(walker);
|
||||
|
||||
walker.currentNode = currentWalkerCurrentNode;
|
||||
|
||||
this._currentSetFor.add(walker);
|
||||
|
||||
curNode = currentWalkerCurrentNode = shadowRoot.host;
|
||||
} else {
|
||||
curNode = curNode.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
const walker = this._doc.createTreeWalker(
|
||||
this.root,
|
||||
this.whatToShow,
|
||||
{acceptNode: this._acceptNode}
|
||||
);
|
||||
|
||||
walkers.push(walker);
|
||||
|
||||
walker.currentNode = currentWalkerCurrentNode;
|
||||
|
||||
this._currentSetFor.add(walker);
|
||||
|
||||
this._walkerStack = walkers;
|
||||
}
|
||||
|
||||
public get doc(): Document {
|
||||
return this._doc;
|
||||
}
|
||||
|
||||
public firstChild(): Node | null {
|
||||
let currentNode = this.currentNode;
|
||||
let newNode = this.nextNode();
|
||||
if (!nodeContains(currentNode, newNode)) {
|
||||
this.currentNode = currentNode;
|
||||
return null;
|
||||
}
|
||||
if (newNode) {
|
||||
this.currentNode = newNode;
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
public lastChild(): Node | null {
|
||||
let walker = this._walkerStack[0];
|
||||
let newNode = walker.lastChild();
|
||||
if (newNode) {
|
||||
this.currentNode = newNode;
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
public nextNode(): Node | null {
|
||||
const nextNode = this._walkerStack[0].nextNode();
|
||||
|
||||
if (nextNode) {
|
||||
const shadowRoot = (nextNode as Element).shadowRoot;
|
||||
|
||||
if (shadowRoot) {
|
||||
let nodeResult: number | undefined;
|
||||
|
||||
if (typeof this.filter === 'function') {
|
||||
nodeResult = this.filter(nextNode);
|
||||
} else if (this.filter?.acceptNode) {
|
||||
nodeResult = this.filter.acceptNode(nextNode);
|
||||
}
|
||||
|
||||
if (nodeResult === NodeFilter.FILTER_ACCEPT) {
|
||||
this.currentNode = nextNode;
|
||||
return nextNode;
|
||||
}
|
||||
|
||||
// _acceptNode should have added new walker for this shadow,
|
||||
// go in recursively.
|
||||
let newNode = this.nextNode();
|
||||
if (newNode) {
|
||||
this.currentNode = newNode;
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
if (nextNode) {
|
||||
this.currentNode = nextNode;
|
||||
}
|
||||
return nextNode;
|
||||
} else {
|
||||
if (this._walkerStack.length > 1) {
|
||||
this._walkerStack.shift();
|
||||
|
||||
let newNode = this.nextNode();
|
||||
if (newNode) {
|
||||
this.currentNode = newNode;
|
||||
}
|
||||
return newNode;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public previousNode(): Node | null {
|
||||
const currentWalker = this._walkerStack[0];
|
||||
|
||||
if (currentWalker.currentNode === currentWalker.root) {
|
||||
if (this._currentSetFor.has(currentWalker)) {
|
||||
this._currentSetFor.delete(currentWalker);
|
||||
|
||||
if (this._walkerStack.length > 1) {
|
||||
this._walkerStack.shift();
|
||||
let newNode = this.previousNode();
|
||||
if (newNode) {
|
||||
this.currentNode = newNode;
|
||||
}
|
||||
return newNode;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const previousNode = currentWalker.previousNode();
|
||||
|
||||
if (previousNode) {
|
||||
const shadowRoot = (previousNode as Element).shadowRoot;
|
||||
|
||||
if (shadowRoot) {
|
||||
let nodeResult: number | undefined;
|
||||
|
||||
if (typeof this.filter === 'function') {
|
||||
nodeResult = this.filter(previousNode);
|
||||
} else if (this.filter?.acceptNode) {
|
||||
nodeResult = this.filter.acceptNode(previousNode);
|
||||
}
|
||||
|
||||
if (nodeResult === NodeFilter.FILTER_ACCEPT) {
|
||||
if (previousNode) {
|
||||
this.currentNode = previousNode;
|
||||
}
|
||||
return previousNode;
|
||||
}
|
||||
|
||||
// _acceptNode should have added new walker for this shadow,
|
||||
// go in recursively.
|
||||
let newNode = this.lastChild();
|
||||
if (newNode) {
|
||||
this.currentNode = newNode;
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
if (previousNode) {
|
||||
this.currentNode = previousNode;
|
||||
}
|
||||
return previousNode;
|
||||
} else {
|
||||
if (this._walkerStack.length > 1) {
|
||||
this._walkerStack.shift();
|
||||
|
||||
let newNode = this.previousNode();
|
||||
if (newNode) {
|
||||
this.currentNode = newNode;
|
||||
}
|
||||
return newNode;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public nextSibling(): Node | null {
|
||||
// if (__DEV__) {
|
||||
// throw new Error("Method not implemented.");
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public previousSibling(): Node | null {
|
||||
// if (__DEV__) {
|
||||
// throw new Error("Method not implemented.");
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public parentNode(): Node | null {
|
||||
// if (__DEV__) {
|
||||
// throw new Error("Method not implemented.");
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ShadowDOM safe version of document.createTreeWalker.
|
||||
*/
|
||||
export function createShadowTreeWalker(
|
||||
doc: Document,
|
||||
root: Node,
|
||||
whatToShow?: number,
|
||||
filter?: NodeFilter | null
|
||||
): TreeWalker {
|
||||
if (shadowDOM()) {
|
||||
return new ShadowTreeWalker(doc, root, whatToShow, filter);
|
||||
}
|
||||
return doc.createTreeWalker(root, whatToShow, filter);
|
||||
}
|
||||
27
node_modules/@react-aria/utils/src/useDeepMemo.ts
generated
vendored
Normal file
27
node_modules/@react-aria/utils/src/useDeepMemo.ts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2023 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable rulesdir/pure-render */
|
||||
|
||||
import {useRef} from 'react';
|
||||
|
||||
export function useDeepMemo<T>(value: T, isEqual: (a: T, b: T) => boolean): T {
|
||||
// Using a ref during render is ok here because it's only an optimization – both values are equivalent.
|
||||
// If a render is thrown away, it'll still work the same no matter if the next render is the same or not.
|
||||
let lastValue = useRef<T | null>(null);
|
||||
if (value && lastValue.current && isEqual(value, lastValue.current)) {
|
||||
value = lastValue.current;
|
||||
}
|
||||
|
||||
lastValue.current = value;
|
||||
return value;
|
||||
}
|
||||
56
node_modules/@react-aria/utils/src/useDescription.ts
generated
vendored
Normal file
56
node_modules/@react-aria/utils/src/useDescription.ts
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {AriaLabelingProps} from '@react-types/shared';
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
import {useState} from 'react';
|
||||
|
||||
let descriptionId = 0;
|
||||
const descriptionNodes = new Map<string, {refCount: number, element: Element}>();
|
||||
|
||||
export function useDescription(description?: string): AriaLabelingProps {
|
||||
let [id, setId] = useState<string | undefined>();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!description) {
|
||||
return;
|
||||
}
|
||||
|
||||
let desc = descriptionNodes.get(description);
|
||||
if (!desc) {
|
||||
let id = `react-aria-description-${descriptionId++}`;
|
||||
setId(id);
|
||||
|
||||
let node = document.createElement('div');
|
||||
node.id = id;
|
||||
node.style.display = 'none';
|
||||
node.textContent = description;
|
||||
document.body.appendChild(node);
|
||||
desc = {refCount: 0, element: node};
|
||||
descriptionNodes.set(description, desc);
|
||||
} else {
|
||||
setId(desc.element.id);
|
||||
}
|
||||
|
||||
desc.refCount++;
|
||||
return () => {
|
||||
if (desc && --desc.refCount === 0) {
|
||||
desc.element.remove();
|
||||
descriptionNodes.delete(description);
|
||||
}
|
||||
};
|
||||
}, [description]);
|
||||
|
||||
return {
|
||||
'aria-describedby': description ? id : undefined
|
||||
};
|
||||
}
|
||||
190
node_modules/@react-aria/utils/src/useDrag1D.ts
generated
vendored
Normal file
190
node_modules/@react-aria/utils/src/useDrag1D.ts
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable rulesdir/pure-render */
|
||||
|
||||
import {getOffset} from './getOffset';
|
||||
import {Orientation} from '@react-types/shared';
|
||||
import React, {HTMLAttributes, MutableRefObject, useRef} from 'react';
|
||||
|
||||
interface UseDrag1DProps {
|
||||
containerRef: MutableRefObject<HTMLElement>,
|
||||
reverse?: boolean,
|
||||
orientation?: Orientation,
|
||||
onHover?: (hovered: boolean) => void,
|
||||
onDrag?: (dragging: boolean) => void,
|
||||
onPositionChange?: (position: number) => void,
|
||||
onIncrement?: () => void,
|
||||
onDecrement?: () => void,
|
||||
onIncrementToMax?: () => void,
|
||||
onDecrementToMin?: () => void,
|
||||
onCollapseToggle?: () => void
|
||||
}
|
||||
|
||||
// Keep track of elements that we are currently handling dragging for via useDrag1D.
|
||||
// If there's an ancestor and a descendant both using useDrag1D(), and the user starts
|
||||
// dragging the descendant, we don't want useDrag1D events to fire for the ancestor.
|
||||
const draggingElements: HTMLElement[] = [];
|
||||
|
||||
// created for splitview, this should be reusable for things like sliders/dials
|
||||
// It also handles keyboard events on the target allowing for increment/decrement by a given stepsize as well as minifying/maximizing and toggling between minified and previous size
|
||||
// It can also take a 'reverse' param to say if we should measure from the right/bottom instead of the top/left
|
||||
// It can also handle either a vertical or horizontal movement, but not both at the same time
|
||||
|
||||
export function useDrag1D(props: UseDrag1DProps): HTMLAttributes<HTMLElement> {
|
||||
console.warn('useDrag1D is deprecated, please use `useMove` instead https://react-spectrum.adobe.com/react-aria/useMove.html');
|
||||
let {containerRef, reverse, orientation, onHover, onDrag, onPositionChange, onIncrement, onDecrement, onIncrementToMax, onDecrementToMin, onCollapseToggle} = props;
|
||||
let getPosition = (e) => orientation === 'horizontal' ? e.clientX : e.clientY;
|
||||
let getNextOffset = (e: MouseEvent) => {
|
||||
let containerOffset = getOffset(containerRef.current, reverse, orientation);
|
||||
let mouseOffset = getPosition(e);
|
||||
let nextOffset = reverse ? containerOffset - mouseOffset : mouseOffset - containerOffset;
|
||||
return nextOffset;
|
||||
};
|
||||
let dragging = useRef(false);
|
||||
let prevPosition = useRef(0);
|
||||
|
||||
// Keep track of the current handlers in a ref so that the events can access them.
|
||||
let handlers = useRef({onPositionChange, onDrag});
|
||||
handlers.current.onDrag = onDrag;
|
||||
handlers.current.onPositionChange = onPositionChange;
|
||||
|
||||
let onMouseDragged = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
let nextOffset = getNextOffset(e);
|
||||
if (!dragging.current) {
|
||||
dragging.current = true;
|
||||
if (handlers.current.onDrag) {
|
||||
handlers.current.onDrag(true);
|
||||
}
|
||||
if (handlers.current.onPositionChange) {
|
||||
handlers.current.onPositionChange(nextOffset);
|
||||
}
|
||||
}
|
||||
if (prevPosition.current === nextOffset) {
|
||||
return;
|
||||
}
|
||||
prevPosition.current = nextOffset;
|
||||
if (onPositionChange) {
|
||||
onPositionChange(nextOffset);
|
||||
}
|
||||
};
|
||||
|
||||
let onMouseUp = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
dragging.current = false;
|
||||
let nextOffset = getNextOffset(e);
|
||||
if (handlers.current.onDrag) {
|
||||
handlers.current.onDrag(false);
|
||||
}
|
||||
if (handlers.current.onPositionChange) {
|
||||
handlers.current.onPositionChange(nextOffset);
|
||||
}
|
||||
|
||||
draggingElements.splice(draggingElements.indexOf(target), 1);
|
||||
window.removeEventListener('mouseup', onMouseUp, false);
|
||||
window.removeEventListener('mousemove', onMouseDragged, false);
|
||||
};
|
||||
|
||||
let onMouseDown = (e: React.MouseEvent<HTMLElement>) => {
|
||||
const target = e.currentTarget;
|
||||
// If we're already handling dragging on a descendant with useDrag1D, then
|
||||
// we don't want to handle the drag motion on this target as well.
|
||||
if (draggingElements.some(elt => target.contains(elt))) {
|
||||
return;
|
||||
}
|
||||
draggingElements.push(target);
|
||||
window.addEventListener('mousemove', onMouseDragged, false);
|
||||
window.addEventListener('mouseup', onMouseUp, false);
|
||||
};
|
||||
|
||||
let onMouseEnter = () => {
|
||||
if (onHover) {
|
||||
onHover(true);
|
||||
}
|
||||
};
|
||||
|
||||
let onMouseOut = () => {
|
||||
if (onHover) {
|
||||
onHover(false);
|
||||
}
|
||||
};
|
||||
|
||||
let onKeyDown = (e) => {
|
||||
switch (e.key) {
|
||||
case 'Left':
|
||||
case 'ArrowLeft':
|
||||
if (orientation === 'horizontal') {
|
||||
e.preventDefault();
|
||||
if (onDecrement && !reverse) {
|
||||
onDecrement();
|
||||
} else if (onIncrement && reverse) {
|
||||
onIncrement();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Up':
|
||||
case 'ArrowUp':
|
||||
if (orientation === 'vertical') {
|
||||
e.preventDefault();
|
||||
if (onDecrement && !reverse) {
|
||||
onDecrement();
|
||||
} else if (onIncrement && reverse) {
|
||||
onIncrement();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Right':
|
||||
case 'ArrowRight':
|
||||
if (orientation === 'horizontal') {
|
||||
e.preventDefault();
|
||||
if (onIncrement && !reverse) {
|
||||
onIncrement();
|
||||
} else if (onDecrement && reverse) {
|
||||
onDecrement();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Down':
|
||||
case 'ArrowDown':
|
||||
if (orientation === 'vertical') {
|
||||
e.preventDefault();
|
||||
if (onIncrement && !reverse) {
|
||||
onIncrement();
|
||||
} else if (onDecrement && reverse) {
|
||||
onDecrement();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
if (onDecrementToMin) {
|
||||
onDecrementToMin();
|
||||
}
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
if (onIncrementToMax) {
|
||||
onIncrementToMax();
|
||||
}
|
||||
break;
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
if (onCollapseToggle) {
|
||||
onCollapseToggle();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return {onMouseDown, onMouseEnter, onMouseOut, onKeyDown};
|
||||
}
|
||||
26
node_modules/@react-aria/utils/src/useEffectEvent.ts
generated
vendored
Normal file
26
node_modules/@react-aria/utils/src/useEffectEvent.ts
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2023 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {useCallback, useRef} from 'react';
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
|
||||
export function useEffectEvent<T extends Function>(fn?: T): T {
|
||||
const ref = useRef<T | null | undefined>(null);
|
||||
useLayoutEffect(() => {
|
||||
ref.current = fn;
|
||||
}, [fn]);
|
||||
// @ts-ignore
|
||||
return useCallback<T>((...args) => {
|
||||
const f = ref.current!;
|
||||
return f?.(...args);
|
||||
}, []);
|
||||
}
|
||||
37
node_modules/@react-aria/utils/src/useEvent.ts
generated
vendored
Normal file
37
node_modules/@react-aria/utils/src/useEvent.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2021 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {RefObject} from '@react-types/shared';
|
||||
import {useEffect} from 'react';
|
||||
import {useEffectEvent} from './useEffectEvent';
|
||||
|
||||
export function useEvent<K extends keyof GlobalEventHandlersEventMap>(
|
||||
ref: RefObject<EventTarget | null>,
|
||||
event: K | (string & {}),
|
||||
handler?: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void {
|
||||
let handleEvent = useEffectEvent(handler);
|
||||
let isDisabled = handler == null;
|
||||
|
||||
useEffect(() => {
|
||||
if (isDisabled || !ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
let element = ref.current;
|
||||
element.addEventListener(event, handleEvent as EventListener, options);
|
||||
return () => {
|
||||
element.removeEventListener(event, handleEvent as EventListener, options);
|
||||
};
|
||||
}, [ref, event, options, isDisabled, handleEvent]);
|
||||
}
|
||||
36
node_modules/@react-aria/utils/src/useFormReset.ts
generated
vendored
Normal file
36
node_modules/@react-aria/utils/src/useFormReset.ts
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2023 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {RefObject} from '@react-types/shared';
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {useEffectEvent} from './useEffectEvent';
|
||||
|
||||
export function useFormReset<T>(
|
||||
ref: RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | null> | undefined,
|
||||
initialValue: T,
|
||||
onReset: (value: T) => void
|
||||
): void {
|
||||
let resetValue = useRef(initialValue);
|
||||
let handleReset = useEffectEvent(() => {
|
||||
if (onReset) {
|
||||
onReset(resetValue.current);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let form = ref?.current?.form;
|
||||
form?.addEventListener('reset', handleReset);
|
||||
return () => {
|
||||
form?.removeEventListener('reset', handleReset);
|
||||
};
|
||||
}, [ref, handleReset]);
|
||||
}
|
||||
52
node_modules/@react-aria/utils/src/useGlobalListeners.ts
generated
vendored
Normal file
52
node_modules/@react-aria/utils/src/useGlobalListeners.ts
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {useCallback, useEffect, useRef} from 'react';
|
||||
|
||||
interface GlobalListeners {
|
||||
addGlobalListener<K extends keyof WindowEventMap>(el: Window, type: K, listener: (this: Document, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void,
|
||||
addGlobalListener<K extends keyof DocumentEventMap>(el: EventTarget, type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void,
|
||||
addGlobalListener(el: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void,
|
||||
removeGlobalListener<K extends keyof DocumentEventMap>(el: EventTarget, type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void,
|
||||
removeGlobalListener(el: EventTarget, type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void,
|
||||
removeAllGlobalListeners(): void
|
||||
}
|
||||
|
||||
export function useGlobalListeners(): GlobalListeners {
|
||||
let globalListeners = useRef(new Map());
|
||||
let addGlobalListener = useCallback((eventTarget, type, listener, options) => {
|
||||
// Make sure we remove the listener after it is called with the `once` option.
|
||||
let fn = options?.once ? (...args) => {
|
||||
globalListeners.current.delete(listener);
|
||||
listener(...args);
|
||||
} : listener;
|
||||
globalListeners.current.set(listener, {type, eventTarget, fn, options});
|
||||
eventTarget.addEventListener(type, fn, options);
|
||||
}, []);
|
||||
let removeGlobalListener = useCallback((eventTarget, type, listener, options) => {
|
||||
let fn = globalListeners.current.get(listener)?.fn || listener;
|
||||
eventTarget.removeEventListener(type, fn, options);
|
||||
globalListeners.current.delete(listener);
|
||||
}, []);
|
||||
let removeAllGlobalListeners = useCallback(() => {
|
||||
globalListeners.current.forEach((value, key) => {
|
||||
removeGlobalListener(value.eventTarget, value.type, key, value.options);
|
||||
});
|
||||
}, [removeGlobalListener]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
return removeAllGlobalListeners;
|
||||
}, [removeAllGlobalListeners]);
|
||||
|
||||
return {addGlobalListener, removeGlobalListener, removeAllGlobalListeners};
|
||||
}
|
||||
129
node_modules/@react-aria/utils/src/useId.ts
generated
vendored
Normal file
129
node_modules/@react-aria/utils/src/useId.ts
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
import {useSSRSafeId} from '@react-aria/ssr';
|
||||
import {useValueEffect} from './';
|
||||
|
||||
// copied from SSRProvider.tsx to reduce exports, if needed again, consider sharing
|
||||
let canUseDOM = Boolean(
|
||||
typeof window !== 'undefined' &&
|
||||
window.document &&
|
||||
window.document.createElement
|
||||
);
|
||||
|
||||
export let idsUpdaterMap: Map<string, { current: string | null }[]> = new Map();
|
||||
// This allows us to clean up the idsUpdaterMap when the id is no longer used.
|
||||
// Map is a strong reference, so unused ids wouldn't be cleaned up otherwise.
|
||||
// This can happen in suspended components where mount/unmount is not called.
|
||||
let registry;
|
||||
if (typeof FinalizationRegistry !== 'undefined') {
|
||||
registry = new FinalizationRegistry<string>((heldValue) => {
|
||||
idsUpdaterMap.delete(heldValue);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If a default is not provided, generate an id.
|
||||
* @param defaultId - Default component id.
|
||||
*/
|
||||
export function useId(defaultId?: string): string {
|
||||
let [value, setValue] = useState(defaultId);
|
||||
let nextId = useRef(null);
|
||||
|
||||
let res = useSSRSafeId(value);
|
||||
let cleanupRef = useRef(null);
|
||||
|
||||
if (registry) {
|
||||
registry.register(cleanupRef, res);
|
||||
}
|
||||
|
||||
if (canUseDOM) {
|
||||
const cacheIdRef = idsUpdaterMap.get(res);
|
||||
if (cacheIdRef && !cacheIdRef.includes(nextId)) {
|
||||
cacheIdRef.push(nextId);
|
||||
} else {
|
||||
idsUpdaterMap.set(res, [nextId]);
|
||||
}
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
let r = res;
|
||||
return () => {
|
||||
// In Suspense, the cleanup function may be not called
|
||||
// when it is though, also remove it from the finalization registry.
|
||||
if (registry) {
|
||||
registry.unregister(cleanupRef);
|
||||
}
|
||||
idsUpdaterMap.delete(r);
|
||||
};
|
||||
}, [res]);
|
||||
|
||||
// This cannot cause an infinite loop because the ref is always cleaned up.
|
||||
// eslint-disable-next-line
|
||||
useEffect(() => {
|
||||
let newId = nextId.current;
|
||||
if (newId) { setValue(newId); }
|
||||
|
||||
return () => {
|
||||
if (newId) { nextId.current = null; }
|
||||
};
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two ids.
|
||||
* Different ids will trigger a side-effect and re-render components hooked up with `useId`.
|
||||
*/
|
||||
export function mergeIds(idA: string, idB: string): string {
|
||||
if (idA === idB) {
|
||||
return idA;
|
||||
}
|
||||
|
||||
let setIdsA = idsUpdaterMap.get(idA);
|
||||
if (setIdsA) {
|
||||
setIdsA.forEach(ref => (ref.current = idB));
|
||||
return idB;
|
||||
}
|
||||
|
||||
let setIdsB = idsUpdaterMap.get(idB);
|
||||
if (setIdsB) {
|
||||
setIdsB.forEach((ref) => (ref.current = idA));
|
||||
return idA;
|
||||
}
|
||||
|
||||
return idB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to generate an id, and after render, check if that id is rendered so we know
|
||||
* if we can use it in places such as labelledby.
|
||||
* @param depArray - When to recalculate if the id is in the DOM.
|
||||
*/
|
||||
export function useSlotId(depArray: ReadonlyArray<any> = []): string {
|
||||
let id = useId();
|
||||
let [resolvedId, setResolvedId] = useValueEffect(id);
|
||||
let updateId = useCallback(() => {
|
||||
setResolvedId(function *() {
|
||||
yield id;
|
||||
|
||||
yield document.getElementById(id) ? id : undefined;
|
||||
});
|
||||
}, [id, setResolvedId]);
|
||||
|
||||
useLayoutEffect(updateId, [id, updateId, ...depArray]);
|
||||
|
||||
return resolvedId;
|
||||
}
|
||||
48
node_modules/@react-aria/utils/src/useLabels.ts
generated
vendored
Normal file
48
node_modules/@react-aria/utils/src/useLabels.ts
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {AriaLabelingProps, DOMProps} from '@react-types/shared';
|
||||
import {useId} from './useId';
|
||||
|
||||
/**
|
||||
* Merges aria-label and aria-labelledby into aria-labelledby when both exist.
|
||||
* @param props - Aria label props.
|
||||
* @param defaultLabel - Default value for aria-label when not present.
|
||||
*/
|
||||
export function useLabels(props: DOMProps & AriaLabelingProps, defaultLabel?: string): DOMProps & AriaLabelingProps {
|
||||
let {
|
||||
id,
|
||||
'aria-label': label,
|
||||
'aria-labelledby': labelledBy
|
||||
} = props;
|
||||
|
||||
// If there is both an aria-label and aria-labelledby,
|
||||
// combine them by pointing to the element itself.
|
||||
id = useId(id);
|
||||
if (labelledBy && label) {
|
||||
let ids = new Set([id, ...labelledBy.trim().split(/\s+/)]);
|
||||
labelledBy = [...ids].join(' ');
|
||||
} else if (labelledBy) {
|
||||
labelledBy = labelledBy.trim().split(/\s+/).join(' ');
|
||||
}
|
||||
|
||||
// If no labels are provided, use the default
|
||||
if (!label && !labelledBy && defaultLabel) {
|
||||
label = defaultLabel;
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
'aria-label': label,
|
||||
'aria-labelledby': labelledBy
|
||||
};
|
||||
}
|
||||
20
node_modules/@react-aria/utils/src/useLayoutEffect.ts
generated
vendored
Normal file
20
node_modules/@react-aria/utils/src/useLayoutEffect.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// During SSR, React emits a warning when calling useLayoutEffect.
|
||||
// Since neither useLayoutEffect nor useEffect run on the server,
|
||||
// we can suppress this by replace it with a noop on the server.
|
||||
export const useLayoutEffect = typeof document !== 'undefined'
|
||||
? React.useLayoutEffect
|
||||
: () => {};
|
||||
82
node_modules/@react-aria/utils/src/useLoadMore.ts
generated
vendored
Normal file
82
node_modules/@react-aria/utils/src/useLoadMore.ts
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2024 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {RefObject, useCallback, useRef} from 'react';
|
||||
import {useEvent} from './useEvent';
|
||||
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
|
||||
export interface LoadMoreProps {
|
||||
/** Whether data is currently being loaded. */
|
||||
isLoading?: boolean,
|
||||
/** Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. */
|
||||
onLoadMore?: () => void,
|
||||
/**
|
||||
* The amount of offset from the bottom of your scrollable region that should trigger load more.
|
||||
* Uses a percentage value relative to the scroll body's client height. Load more is then triggered
|
||||
* when your current scroll position's distance from the bottom of the currently loaded list of items is less than
|
||||
* or equal to the provided value. (e.g. 1 = 100% of the scroll region's height).
|
||||
* @default 1
|
||||
*/
|
||||
scrollOffset?: number,
|
||||
/** The data currently loaded. */
|
||||
items?: any
|
||||
}
|
||||
|
||||
export function useLoadMore(props: LoadMoreProps, ref: RefObject<HTMLElement | null>): void {
|
||||
let {isLoading, onLoadMore, scrollOffset = 1, items} = props;
|
||||
|
||||
// Handle scrolling, and call onLoadMore when nearing the bottom.
|
||||
let isLoadingRef = useRef(isLoading);
|
||||
let prevProps = useRef(props);
|
||||
let onScroll = useCallback(() => {
|
||||
if (ref.current && !isLoadingRef.current && onLoadMore) {
|
||||
let shouldLoadMore = ref.current.scrollHeight - ref.current.scrollTop - ref.current.clientHeight < ref.current.clientHeight * scrollOffset;
|
||||
|
||||
if (shouldLoadMore) {
|
||||
isLoadingRef.current = true;
|
||||
onLoadMore();
|
||||
}
|
||||
}
|
||||
}, [onLoadMore, ref, scrollOffset]);
|
||||
|
||||
let lastItems = useRef(items);
|
||||
useLayoutEffect(() => {
|
||||
// Only update isLoadingRef if props object actually changed,
|
||||
// not if a local state change occurred.
|
||||
if (props !== prevProps.current) {
|
||||
isLoadingRef.current = isLoading;
|
||||
prevProps.current = props;
|
||||
}
|
||||
|
||||
// TODO: Eventually this hook will move back into RAC during which we will accept the collection as a option to this hook.
|
||||
// We will only load more if the collection has changed after the last load to prevent multiple onLoadMore from being called
|
||||
// while the data from the last onLoadMore is being processed by RAC collection.
|
||||
let shouldLoadMore = ref?.current
|
||||
&& !isLoadingRef.current
|
||||
&& onLoadMore
|
||||
&& (!items || items !== lastItems.current)
|
||||
&& ref.current.clientHeight === ref.current.scrollHeight;
|
||||
|
||||
if (shouldLoadMore) {
|
||||
isLoadingRef.current = true;
|
||||
onLoadMore?.();
|
||||
}
|
||||
|
||||
lastItems.current = items;
|
||||
}, [isLoading, onLoadMore, props, ref, items]);
|
||||
|
||||
// TODO: maybe this should still just return scroll props?
|
||||
// Test against case where the ref isn't defined when this is called
|
||||
// Think this was a problem when trying to attach to the scrollable body of the table in OnLoadMoreTableBodyScroll
|
||||
useEvent(ref, 'scroll', onScroll);
|
||||
}
|
||||
63
node_modules/@react-aria/utils/src/useLoadMoreSentinel.ts
generated
vendored
Normal file
63
node_modules/@react-aria/utils/src/useLoadMoreSentinel.ts
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2024 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import type {AsyncLoadable, Collection, Node} from '@react-types/shared';
|
||||
import {getScrollParent} from './getScrollParent';
|
||||
import {RefObject, useRef} from 'react';
|
||||
import {useEffectEvent} from './useEffectEvent';
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
|
||||
export interface LoadMoreSentinelProps extends Omit<AsyncLoadable, 'isLoading'> {
|
||||
collection: Collection<Node<unknown>>,
|
||||
/**
|
||||
* The amount of offset from the bottom of your scrollable region that should trigger load more.
|
||||
* Uses a percentage value relative to the scroll body's client height. Load more is then triggered
|
||||
* when your current scroll position's distance from the bottom of the currently loaded list of items is less than
|
||||
* or equal to the provided value. (e.g. 1 = 100% of the scroll region's height).
|
||||
* @default 1
|
||||
*/
|
||||
scrollOffset?: number
|
||||
}
|
||||
|
||||
export function UNSTABLE_useLoadMoreSentinel(props: LoadMoreSentinelProps, ref: RefObject<HTMLElement | null>): void {
|
||||
let {collection, onLoadMore, scrollOffset = 1} = props;
|
||||
|
||||
let sentinelObserver = useRef<IntersectionObserver>(null);
|
||||
|
||||
let triggerLoadMore = useEffectEvent((entries: IntersectionObserverEntry[]) => {
|
||||
// Use "isIntersecting" over an equality check of 0 since it seems like there is cases where
|
||||
// a intersection ratio of 0 can be reported when isIntersecting is actually true
|
||||
for (let entry of entries) {
|
||||
// Note that this will be called if the collection changes, even if onLoadMore was already called and is being processed.
|
||||
// Up to user discretion as to how to handle these multiple onLoadMore calls
|
||||
if (entry.isIntersecting && onLoadMore) {
|
||||
onLoadMore();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current) {
|
||||
// Tear down and set up a new IntersectionObserver when the collection changes so that we can properly trigger additional loadMores if there is room for more items
|
||||
// Need to do this tear down and set up since using a large rootMargin will mean the observer's callback isn't called even when scrolling the item into view beause its visibility hasn't actually changed
|
||||
// https://codesandbox.io/p/sandbox/magical-swanson-dhgp89?file=%2Fsrc%2FApp.js%3A21%2C21
|
||||
sentinelObserver.current = new IntersectionObserver(triggerLoadMore, {root: getScrollParent(ref?.current) as HTMLElement, rootMargin: `0px ${100 * scrollOffset}% ${100 * scrollOffset}% ${100 * scrollOffset}%`});
|
||||
sentinelObserver.current.observe(ref.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (sentinelObserver.current) {
|
||||
sentinelObserver.current.disconnect();
|
||||
}
|
||||
};
|
||||
}, [collection, triggerLoadMore, ref, scrollOffset]);
|
||||
}
|
||||
69
node_modules/@react-aria/utils/src/useObjectRef.ts
generated
vendored
Normal file
69
node_modules/@react-aria/utils/src/useObjectRef.ts
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2021 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {MutableRefObject, useCallback, useMemo, useRef} from 'react';
|
||||
|
||||
/**
|
||||
* Offers an object ref for a given callback ref or an object ref. Especially
|
||||
* helfpul when passing forwarded refs (created using `React.forwardRef`) to
|
||||
* React Aria hooks.
|
||||
*
|
||||
* @param ref The original ref intended to be used.
|
||||
* @returns An object ref that updates the given ref.
|
||||
* @see https://react.dev/reference/react/forwardRef
|
||||
*/
|
||||
export function useObjectRef<T>(ref?: ((instance: T | null) => (() => void) | void) | MutableRefObject<T | null> | null): MutableRefObject<T | null> {
|
||||
const objRef: MutableRefObject<T | null> = useRef<T>(null);
|
||||
const cleanupRef: MutableRefObject<(() => void) | void> = useRef(undefined);
|
||||
|
||||
const refEffect = useCallback(
|
||||
(instance: T | null) => {
|
||||
if (typeof ref === 'function') {
|
||||
const refCallback = ref;
|
||||
const refCleanup = refCallback(instance);
|
||||
return () => {
|
||||
if (typeof refCleanup === 'function') {
|
||||
refCleanup();
|
||||
} else {
|
||||
refCallback(null);
|
||||
}
|
||||
};
|
||||
} else if (ref) {
|
||||
ref.current = instance;
|
||||
return () => {
|
||||
ref.current = null;
|
||||
};
|
||||
}
|
||||
},
|
||||
[ref]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
get current() {
|
||||
return objRef.current;
|
||||
},
|
||||
set current(value) {
|
||||
objRef.current = value;
|
||||
if (cleanupRef.current) {
|
||||
cleanupRef.current();
|
||||
cleanupRef.current = undefined;
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
cleanupRef.current = refEffect(value);
|
||||
}
|
||||
}
|
||||
}),
|
||||
[refEffect]
|
||||
);
|
||||
}
|
||||
48
node_modules/@react-aria/utils/src/useResizeObserver.ts
generated
vendored
Normal file
48
node_modules/@react-aria/utils/src/useResizeObserver.ts
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
import {RefObject} from '@react-types/shared';
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function hasResizeObserver() {
|
||||
return typeof window.ResizeObserver !== 'undefined';
|
||||
}
|
||||
|
||||
type useResizeObserverOptionsType<T> = {
|
||||
ref: RefObject<T | undefined | null> | undefined,
|
||||
box?: ResizeObserverBoxOptions,
|
||||
onResize: () => void
|
||||
}
|
||||
|
||||
export function useResizeObserver<T extends Element>(options: useResizeObserverOptionsType<T>): void {
|
||||
const {ref, box, onResize} = options;
|
||||
|
||||
useEffect(() => {
|
||||
let element = ref?.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasResizeObserver()) {
|
||||
window.addEventListener('resize', onResize, false);
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize, false);
|
||||
};
|
||||
} else {
|
||||
|
||||
const resizeObserverInstance = new window.ResizeObserver((entries) => {
|
||||
if (!entries.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
onResize();
|
||||
});
|
||||
resizeObserverInstance.observe(element, {box});
|
||||
|
||||
return () => {
|
||||
if (element) {
|
||||
resizeObserverInstance.unobserve(element);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}, [onResize, ref, box]);
|
||||
}
|
||||
33
node_modules/@react-aria/utils/src/useSyncRef.ts
generated
vendored
Normal file
33
node_modules/@react-aria/utils/src/useSyncRef.ts
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {MutableRefObject} from 'react';
|
||||
import {RefObject} from '@react-types/shared';
|
||||
import {useLayoutEffect} from './';
|
||||
|
||||
interface ContextValue<T> {
|
||||
ref?: MutableRefObject<T | null>
|
||||
}
|
||||
|
||||
// Syncs ref from context with ref passed to hook
|
||||
export function useSyncRef<T>(context?: ContextValue<T> | null, ref?: RefObject<T | null>): void {
|
||||
useLayoutEffect(() => {
|
||||
if (context && context.ref && ref) {
|
||||
context.ref.current = ref.current;
|
||||
return () => {
|
||||
if (context.ref) {
|
||||
context.ref.current = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
37
node_modules/@react-aria/utils/src/useUpdateEffect.ts
generated
vendored
Normal file
37
node_modules/@react-aria/utils/src/useUpdateEffect.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {EffectCallback, useEffect, useRef} from 'react';
|
||||
|
||||
// Like useEffect, but only called for updates after the initial render.
|
||||
export function useUpdateEffect(effect: EffectCallback, dependencies: any[]): void {
|
||||
const isInitialMount = useRef(true);
|
||||
const lastDeps = useRef<any[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
isInitialMount.current = true;
|
||||
return () => {
|
||||
isInitialMount.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let prevDeps = lastDeps.current;
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
} else if (!prevDeps || dependencies.some((dep, i) => !Object.is(dep, prevDeps[i]))) {
|
||||
effect();
|
||||
}
|
||||
lastDeps.current = dependencies;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, dependencies);
|
||||
}
|
||||
37
node_modules/@react-aria/utils/src/useUpdateLayoutEffect.ts
generated
vendored
Normal file
37
node_modules/@react-aria/utils/src/useUpdateLayoutEffect.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2024 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {EffectCallback, useRef} from 'react';
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
|
||||
// Like useLayoutEffect, but only called for updates after the initial render.
|
||||
export function useUpdateLayoutEffect(effect: EffectCallback, dependencies: any[]): void {
|
||||
const isInitialMount = useRef(true);
|
||||
const lastDeps = useRef<any[] | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
isInitialMount.current = true;
|
||||
return () => {
|
||||
isInitialMount.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
} else if (!lastDeps.current || dependencies.some((dep, i) => !Object.is(dep, lastDeps[i]))) {
|
||||
effect();
|
||||
}
|
||||
lastDeps.current = dependencies;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, dependencies);
|
||||
}
|
||||
64
node_modules/@react-aria/utils/src/useValueEffect.ts
generated
vendored
Normal file
64
node_modules/@react-aria/utils/src/useValueEffect.ts
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {Dispatch, MutableRefObject, useRef, useState} from 'react';
|
||||
import {useEffectEvent, useLayoutEffect} from './';
|
||||
|
||||
type SetValueAction<S> = (prev: S) => Generator<any, void, unknown>;
|
||||
|
||||
// This hook works like `useState`, but when setting the value, you pass a generator function
|
||||
// that can yield multiple values. Each yielded value updates the state and waits for the next
|
||||
// layout effect, then continues the generator. This allows sequential updates to state to be
|
||||
// written linearly.
|
||||
export function useValueEffect<S>(defaultValue: S | (() => S)): [S, Dispatch<SetValueAction<S>>] {
|
||||
let [value, setValue] = useState(defaultValue);
|
||||
let effect: MutableRefObject<Generator<S> | null> = useRef<Generator<S> | null>(null);
|
||||
|
||||
// Store the function in a ref so we can always access the current version
|
||||
// which has the proper `value` in scope.
|
||||
let nextRef = useEffectEvent(() => {
|
||||
if (!effect.current) {
|
||||
return;
|
||||
}
|
||||
// Run the generator to the next yield.
|
||||
let newValue = effect.current.next();
|
||||
|
||||
// If the generator is done, reset the effect.
|
||||
if (newValue.done) {
|
||||
effect.current = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the value is the same as the current value,
|
||||
// then continue to the next yield. Otherwise,
|
||||
// set the value in state and wait for the next layout effect.
|
||||
if (value === newValue.value) {
|
||||
nextRef();
|
||||
} else {
|
||||
setValue(newValue.value);
|
||||
}
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// If there is an effect currently running, continue to the next yield.
|
||||
if (effect.current) {
|
||||
nextRef();
|
||||
}
|
||||
});
|
||||
|
||||
let queue = useEffectEvent(fn => {
|
||||
effect.current = fn(value);
|
||||
nextRef();
|
||||
});
|
||||
|
||||
return [value, queue];
|
||||
}
|
||||
62
node_modules/@react-aria/utils/src/useViewportSize.ts
generated
vendored
Normal file
62
node_modules/@react-aria/utils/src/useViewportSize.ts
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2020 Adobe. All rights reserved.
|
||||
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
||||
* OF ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useIsSSR} from '@react-aria/ssr';
|
||||
|
||||
interface ViewportSize {
|
||||
width: number,
|
||||
height: number
|
||||
}
|
||||
|
||||
let visualViewport = typeof document !== 'undefined' && window.visualViewport;
|
||||
|
||||
export function useViewportSize(): ViewportSize {
|
||||
let isSSR = useIsSSR();
|
||||
let [size, setSize] = useState(() => isSSR ? {width: 0, height: 0} : getViewportSize());
|
||||
|
||||
useEffect(() => {
|
||||
// Use visualViewport api to track available height even on iOS virtual keyboard opening
|
||||
let onResize = () => {
|
||||
setSize(size => {
|
||||
let newSize = getViewportSize();
|
||||
if (newSize.width === size.width && newSize.height === size.height) {
|
||||
return size;
|
||||
}
|
||||
return newSize;
|
||||
});
|
||||
};
|
||||
|
||||
if (!visualViewport) {
|
||||
window.addEventListener('resize', onResize);
|
||||
} else {
|
||||
visualViewport.addEventListener('resize', onResize);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (!visualViewport) {
|
||||
window.removeEventListener('resize', onResize);
|
||||
} else {
|
||||
visualViewport.removeEventListener('resize', onResize);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
function getViewportSize(): ViewportSize {
|
||||
return {
|
||||
width: (visualViewport && visualViewport?.width) || window.innerWidth,
|
||||
height: (visualViewport && visualViewport?.height) || window.innerHeight
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user