rebuild hrm
This commit is contained in:
12
node_modules/@react-aria/utils/src/animation.ts
generated
vendored
12
node_modules/@react-aria/utils/src/animation.ts
generated
vendored
@@ -14,10 +14,10 @@ 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) {
|
||||
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.
|
||||
@@ -38,7 +38,7 @@ export function useEnterAnimation(ref: RefObject<HTMLElement | null>, isReady: b
|
||||
return isAnimationReady;
|
||||
}
|
||||
|
||||
export function useExitAnimation(ref: RefObject<HTMLElement | null>, isOpen: boolean) {
|
||||
export function useExitAnimation(ref: RefObject<HTMLElement | null>, isOpen: boolean): boolean {
|
||||
let [exitState, setExitState] = useState<'closed' | 'open' | 'exiting'>(isOpen ? 'open' : 'closed');
|
||||
|
||||
switch (exitState) {
|
||||
@@ -71,7 +71,7 @@ export function useExitAnimation(ref: RefObject<HTMLElement | null>, isOpen: boo
|
||||
return isExiting;
|
||||
}
|
||||
|
||||
function useAnimation(ref: RefObject<HTMLElement | null>, isActive: boolean, onEnd: () => void) {
|
||||
function useAnimation(ref: RefObject<HTMLElement | null>, isActive: boolean, onEnd: () => void): void {
|
||||
useLayoutEffect(() => {
|
||||
if (isActive && ref.current) {
|
||||
if (!('getAnimations' in ref.current)) {
|
||||
@@ -79,7 +79,7 @@ function useAnimation(ref: RefObject<HTMLElement | null>, isActive: boolean, onE
|
||||
onEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let animations = ref.current.getAnimations();
|
||||
if (animations.length === 0) {
|
||||
onEnd();
|
||||
@@ -94,7 +94,7 @@ function useAnimation(ref: RefObject<HTMLElement | null>, isActive: boolean, onE
|
||||
});
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
|
||||
1
node_modules/@react-aria/utils/src/constants.ts
generated
vendored
1
node_modules/@react-aria/utils/src/constants.ts
generated
vendored
@@ -13,4 +13,3 @@
|
||||
// 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';
|
||||
export const UPDATE_ACTIVEDESCENDANT = 'react-aria-update-activedescendant';
|
||||
|
||||
23
node_modules/@react-aria/utils/src/domHelpers.ts
generated
vendored
23
node_modules/@react-aria/utils/src/domHelpers.ts
generated
vendored
@@ -3,8 +3,8 @@ export const getOwnerDocument = (el: Element | null | undefined): Document => {
|
||||
};
|
||||
|
||||
export const getOwnerWindow = (
|
||||
el: (Window & typeof global) | Element | null | undefined
|
||||
): Window & typeof global => {
|
||||
el: (Window & typeof globalThis) | Element | null | undefined
|
||||
): Window & typeof globalThis => {
|
||||
if (el && 'window' in el && el.window === el) {
|
||||
return el;
|
||||
}
|
||||
@@ -12,3 +12,22 @@ export const getOwnerWindow = (
|
||||
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;
|
||||
}
|
||||
|
||||
57
node_modules/@react-aria/utils/src/filterDOMProps.ts
generated
vendored
57
node_modules/@react-aria/utils/src/filterDOMProps.ts
generated
vendored
@@ -10,7 +10,7 @@
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {AriaLabelingProps, DOMProps, LinkDOMProps} from '@react-types/shared';
|
||||
import {AriaLabelingProps, DOMProps, GlobalDOMAttributes, LinkDOMProps} from '@react-types/shared';
|
||||
|
||||
const DOMPropNames = new Set([
|
||||
'id'
|
||||
@@ -34,6 +34,51 @@ const linkPropNames = new Set([
|
||||
'referrerPolicy'
|
||||
]);
|
||||
|
||||
const globalAttrs = new Set([
|
||||
'dir',
|
||||
'lang',
|
||||
'hidden',
|
||||
'inert',
|
||||
'translate'
|
||||
]);
|
||||
|
||||
const globalEvents = new Set([
|
||||
'onClick',
|
||||
'onAuxClick',
|
||||
'onContextMenu',
|
||||
'onDoubleClick',
|
||||
'onMouseDown',
|
||||
'onMouseEnter',
|
||||
'onMouseLeave',
|
||||
'onMouseMove',
|
||||
'onMouseOut',
|
||||
'onMouseOver',
|
||||
'onMouseUp',
|
||||
'onTouchCancel',
|
||||
'onTouchEnd',
|
||||
'onTouchMove',
|
||||
'onTouchStart',
|
||||
'onPointerDown',
|
||||
'onPointerMove',
|
||||
'onPointerUp',
|
||||
'onPointerCancel',
|
||||
'onPointerEnter',
|
||||
'onPointerLeave',
|
||||
'onPointerOver',
|
||||
'onPointerOut',
|
||||
'onGotPointerCapture',
|
||||
'onLostPointerCapture',
|
||||
'onScroll',
|
||||
'onWheel',
|
||||
'onAnimationStart',
|
||||
'onAnimationEnd',
|
||||
'onAnimationIteration',
|
||||
'onTransitionCancel',
|
||||
'onTransitionEnd',
|
||||
'onTransitionRun',
|
||||
'onTransitionStart'
|
||||
]);
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* If labelling associated aria properties should be included in the filter.
|
||||
@@ -41,6 +86,10 @@ interface Options {
|
||||
labelable?: boolean,
|
||||
/** Whether the element is a link and should include DOM props for <a> elements. */
|
||||
isLink?: boolean,
|
||||
/** Whether to include global DOM attributes. */
|
||||
global?: boolean,
|
||||
/** Whether to include DOM events. */
|
||||
events?: boolean,
|
||||
/**
|
||||
* A Set of other property names that should be included in the filter.
|
||||
*/
|
||||
@@ -54,8 +103,8 @@ const propRe = /^(data-.*)$/;
|
||||
* @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;
|
||||
export function filterDOMProps(props: DOMProps & AriaLabelingProps & LinkDOMProps & GlobalDOMAttributes, opts: Options = {}): DOMProps & AriaLabelingProps & GlobalDOMAttributes {
|
||||
let {labelable, isLink, global, events = global, propNames} = opts;
|
||||
let filteredProps = {};
|
||||
|
||||
for (const prop in props) {
|
||||
@@ -64,6 +113,8 @@ export function filterDOMProps(props: DOMProps & AriaLabelingProps & LinkDOMProp
|
||||
DOMPropNames.has(prop) ||
|
||||
(labelable && labelablePropNames.has(prop)) ||
|
||||
(isLink && linkPropNames.has(prop)) ||
|
||||
(global && globalAttrs.has(prop)) ||
|
||||
(events && (globalEvents.has(prop) || (prop.endsWith('Capture') && globalEvents.has(prop.slice(0, -7))))) ||
|
||||
propNames?.has(prop) ||
|
||||
propRe.test(prop)
|
||||
)
|
||||
|
||||
2
node_modules/@react-aria/utils/src/focusWithoutScrolling.ts
generated
vendored
2
node_modules/@react-aria/utils/src/focusWithoutScrolling.ts
generated
vendored
@@ -28,7 +28,7 @@ interface ScrollableElement {
|
||||
scrollLeft: number
|
||||
}
|
||||
|
||||
export function focusWithoutScrolling(element: FocusableElement) {
|
||||
export function focusWithoutScrolling(element: FocusableElement): void {
|
||||
if (supportsPreventScroll()) {
|
||||
element.focus({preventScroll: true});
|
||||
} else {
|
||||
|
||||
4
node_modules/@react-aria/utils/src/getOffset.ts
generated
vendored
4
node_modules/@react-aria/utils/src/getOffset.ts
generated
vendored
@@ -10,7 +10,9 @@
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
export function getOffset(element, reverse, orientation = 'horizontal') {
|
||||
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;
|
||||
|
||||
15
node_modules/@react-aria/utils/src/index.ts
generated
vendored
15
node_modules/@react-aria/utils/src/index.ts
generated
vendored
@@ -11,13 +11,15 @@
|
||||
*/
|
||||
export {useId, mergeIds, useSlotId} from './useId';
|
||||
export {chain} from './chain';
|
||||
export {getOwnerDocument, getOwnerWindow} from './domHelpers';
|
||||
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 {openLink, getSyntheticLinkProps, useSyntheticLinkProps, RouterProvider, shouldClientNavigate, useRouter, useLinkProps, handleLinkClick} from './openLink';
|
||||
export {runAfterTransition} from './runAfterTransition';
|
||||
export {useDrag1D} from './useDrag1D';
|
||||
export {useGlobalListeners} from './useGlobalListeners';
|
||||
@@ -43,6 +45,11 @@ export {useEffectEvent} from './useEffectEvent';
|
||||
export {useDeepMemo} from './useDeepMemo';
|
||||
export {useFormReset} from './useFormReset';
|
||||
export {useLoadMore} from './useLoadMore';
|
||||
export {CLEAR_FOCUS_EVENT, FOCUS_EVENT, UPDATE_ACTIVEDESCENDANT} from './constants';
|
||||
export {isCtrlKeyPressed} from './keyboard';
|
||||
export {useLoadMoreSentinel, useLoadMoreSentinel as UNSTABLE_useLoadMoreSentinel} from './useLoadMoreSentinel';
|
||||
export {inertValue} from './inertValue';
|
||||
export {CLEAR_FOCUS_EVENT, FOCUS_EVENT} from './constants';
|
||||
export {isCtrlKeyPressed, willOpenKeyboard} 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;
|
||||
}
|
||||
75
node_modules/@react-aria/utils/src/isElementVisible.ts
generated
vendored
Normal file
75
node_modules/@react-aria/utils/src/isElementVisible.ts
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 {getOwnerWindow} from './domHelpers';
|
||||
|
||||
const supportsCheckVisibility = typeof Element !== 'undefined' && 'checkVisibility' in Element.prototype;
|
||||
|
||||
function isStyleVisible(element: Element) {
|
||||
const windowObject = getOwnerWindow(element);
|
||||
if (!(element instanceof windowObject.HTMLElement) && !(element instanceof windowObject.SVGElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let {display, visibility} = element.style;
|
||||
|
||||
let isVisible = (
|
||||
display !== 'none' &&
|
||||
visibility !== 'hidden' &&
|
||||
visibility !== 'collapse'
|
||||
);
|
||||
|
||||
if (isVisible) {
|
||||
const {getComputedStyle} = element.ownerDocument.defaultView as unknown as Window;
|
||||
let {display: computedDisplay, visibility: computedVisibility} = getComputedStyle(element);
|
||||
|
||||
isVisible = (
|
||||
computedDisplay !== 'none' &&
|
||||
computedVisibility !== 'hidden' &&
|
||||
computedVisibility !== 'collapse'
|
||||
);
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
function isAttributeVisible(element: Element, childElement?: Element) {
|
||||
return (
|
||||
!element.hasAttribute('hidden') &&
|
||||
// Ignore HiddenSelect when tree walking.
|
||||
!element.hasAttribute('data-react-aria-prevent-focus') &&
|
||||
(element.nodeName === 'DETAILS' &&
|
||||
childElement &&
|
||||
childElement.nodeName !== 'SUMMARY'
|
||||
? element.hasAttribute('open')
|
||||
: true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapted from https://github.com/testing-library/jest-dom and
|
||||
* https://github.com/vuejs/vue-test-utils-next/.
|
||||
* Licensed under the MIT License.
|
||||
* @param element - Element to evaluate for display or visibility.
|
||||
*/
|
||||
export function isElementVisible(element: Element, childElement?: Element): boolean {
|
||||
if (supportsCheckVisibility) {
|
||||
return element.checkVisibility({visibilityProperty: true}) && !element.closest('[data-react-aria-prevent-focus]');
|
||||
}
|
||||
|
||||
return (
|
||||
element.nodeName !== '#comment' &&
|
||||
isStyleVisible(element) &&
|
||||
isAttributeVisible(element, childElement) &&
|
||||
(!element.parentElement || isElementVisible(element.parentElement, element))
|
||||
);
|
||||
}
|
||||
56
node_modules/@react-aria/utils/src/isFocusable.ts
generated
vendored
Normal file
56
node_modules/@react-aria/utils/src/isFocusable.ts
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 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 {isElementVisible} from './isElementVisible';
|
||||
|
||||
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"])',
|
||||
'permission'
|
||||
];
|
||||
|
||||
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) && isElementVisible(element) && !isInert(element);
|
||||
}
|
||||
|
||||
export function isTabbable(element: Element): boolean {
|
||||
return element.matches(TABBABLE_ELEMENT_SELECTOR) && isElementVisible(element) && !isInert(element);
|
||||
}
|
||||
|
||||
function isInert(element: Element): boolean {
|
||||
let node: Element | null = element;
|
||||
while (node != null) {
|
||||
if (node instanceof node.ownerDocument.defaultView!.HTMLElement && node.inert) {
|
||||
return true;
|
||||
}
|
||||
|
||||
node = node.parentElement;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
4
node_modules/@react-aria/utils/src/isVirtualEvent.ts
generated
vendored
4
node_modules/@react-aria/utils/src/isVirtualEvent.ts
generated
vendored
@@ -25,7 +25,7 @@ import {isAndroid} from './platform';
|
||||
|
||||
export function isVirtualClick(event: MouseEvent | PointerEvent): boolean {
|
||||
// JAWS/NVDA with Firefox.
|
||||
if ((event as any).mozInputSource === 0 && event.isTrusted) {
|
||||
if ((event as PointerEvent).pointerType === '' && event.isTrusted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function isVirtualClick(event: MouseEvent | PointerEvent): boolean {
|
||||
return event.detail === 0 && !(event as PointerEvent).pointerType;
|
||||
}
|
||||
|
||||
export function isVirtualPointerEvent(event: PointerEvent) {
|
||||
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.
|
||||
|
||||
23
node_modules/@react-aria/utils/src/keyboard.tsx
generated
vendored
23
node_modules/@react-aria/utils/src/keyboard.tsx
generated
vendored
@@ -18,10 +18,31 @@ interface Event {
|
||||
metaKey: boolean
|
||||
}
|
||||
|
||||
export function isCtrlKeyPressed(e: Event) {
|
||||
export function isCtrlKeyPressed(e: Event): boolean {
|
||||
if (isMac()) {
|
||||
return e.metaKey;
|
||||
}
|
||||
|
||||
return e.ctrlKey;
|
||||
}
|
||||
|
||||
// HTML input types that do not cause the software keyboard to appear.
|
||||
const nonTextInputTypes = new Set([
|
||||
'checkbox',
|
||||
'radio',
|
||||
'range',
|
||||
'color',
|
||||
'file',
|
||||
'image',
|
||||
'button',
|
||||
'submit',
|
||||
'reset'
|
||||
]);
|
||||
|
||||
export function willOpenKeyboard(target: Element) {
|
||||
return (
|
||||
(target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type)) ||
|
||||
target instanceof HTMLTextAreaElement ||
|
||||
(target instanceof HTMLElement && target.isContentEditable)
|
||||
);
|
||||
}
|
||||
|
||||
8
node_modules/@react-aria/utils/src/mergeProps.ts
generated
vendored
8
node_modules/@react-aria/utils/src/mergeProps.ts
generated
vendored
@@ -13,6 +13,7 @@
|
||||
import {chain} from './chain';
|
||||
import clsx from 'clsx';
|
||||
import {mergeIds} from './useId';
|
||||
import {mergeRefs} from './mergeRefs';
|
||||
|
||||
interface Props {
|
||||
[key: string]: any
|
||||
@@ -23,13 +24,12 @@ 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`.
|
||||
* classNames are combined, ids are deduplicated, and refs are merged.
|
||||
* For all other props, the last prop object overrides all previous ones.
|
||||
* @param args - Multiple sets of props to merge together.
|
||||
*/
|
||||
@@ -64,6 +64,8 @@ export function mergeProps<T extends PropsArg[]>(...args: T): UnionToIntersectio
|
||||
result[key] = clsx(a, b);
|
||||
} else if (key === 'id' && a && b) {
|
||||
result.id = mergeIds(a, b);
|
||||
} else if (key === 'ref' && a && b) {
|
||||
result.ref = mergeRefs(a, b);
|
||||
// Override others
|
||||
} else {
|
||||
result[key] = b !== undefined ? b : a;
|
||||
|
||||
36
node_modules/@react-aria/utils/src/mergeRefs.ts
generated
vendored
36
node_modules/@react-aria/utils/src/mergeRefs.ts
generated
vendored
@@ -10,23 +10,43 @@
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {ForwardedRef, MutableRefObject} from 'react';
|
||||
import {MutableRefObject, Ref} from 'react';
|
||||
|
||||
/**
|
||||
* Merges multiple refs into one. Works with either callback or object refs.
|
||||
*/
|
||||
export function mergeRefs<T>(...refs: Array<ForwardedRef<T> | MutableRefObject<T> | null | undefined>): ForwardedRef<T> {
|
||||
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) => {
|
||||
for (let ref of refs) {
|
||||
if (typeof ref === 'function') {
|
||||
ref(value);
|
||||
} else if (ref != null) {
|
||||
ref.current = value;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
36
node_modules/@react-aria/utils/src/openLink.tsx
generated
vendored
36
node_modules/@react-aria/utils/src/openLink.tsx
generated
vendored
@@ -13,7 +13,7 @@
|
||||
import {focusWithoutScrolling, isMac, isWebKit} from './index';
|
||||
import {Href, LinkDOMProps, RouterOptions} from '@react-types/shared';
|
||||
import {isFirefox, isIPad} from './platform';
|
||||
import React, {createContext, ReactNode, useContext, useMemo} from 'react';
|
||||
import React, {createContext, DOMAttributes, JSX, MouseEvent as ReactMouseEvent, ReactNode, useContext, useMemo} from 'react';
|
||||
|
||||
interface Router {
|
||||
isNative: boolean,
|
||||
@@ -37,7 +37,7 @@ interface RouterProviderProps {
|
||||
* 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) {
|
||||
export function RouterProvider(props: RouterProviderProps): JSX.Element {
|
||||
let {children, navigate, useHref} = props;
|
||||
|
||||
let ctx = useMemo(() => ({
|
||||
@@ -72,7 +72,7 @@ interface Modifiers {
|
||||
shiftKey?: boolean
|
||||
}
|
||||
|
||||
export function shouldClientNavigate(link: HTMLAnchorElement, modifiers: Modifiers) {
|
||||
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 (
|
||||
@@ -86,7 +86,7 @@ export function shouldClientNavigate(link: HTMLAnchorElement, modifiers: Modifie
|
||||
);
|
||||
}
|
||||
|
||||
export function openLink(target: HTMLAnchorElement, modifiers: Modifiers, setOpening = true) {
|
||||
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
|
||||
@@ -106,7 +106,7 @@ export function openLink(target: HTMLAnchorElement, modifiers: Modifiers, setOpe
|
||||
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});
|
||||
: new MouseEvent('click', {metaKey, ctrlKey, altKey, shiftKey, detail: 1, bubbles: true, cancelable: true});
|
||||
(openLink as any).isOpening = setOpening;
|
||||
focusWithoutScrolling(target);
|
||||
target.dispatchEvent(event);
|
||||
@@ -146,7 +146,7 @@ function openSyntheticLink(target: Element, modifiers: Modifiers) {
|
||||
getSyntheticLink(target, link => openLink(link, modifiers));
|
||||
}
|
||||
|
||||
export function useSyntheticLinkProps(props: LinkDOMProps) {
|
||||
export function useSyntheticLinkProps(props: LinkDOMProps): DOMAttributes<HTMLElement> {
|
||||
let router = useRouter();
|
||||
const href = router.useHref(props.href ?? '');
|
||||
return {
|
||||
@@ -156,11 +156,11 @@ export function useSyntheticLinkProps(props: LinkDOMProps) {
|
||||
'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) {
|
||||
export function getSyntheticLinkProps(props: LinkDOMProps): DOMAttributes<HTMLElement> {
|
||||
return {
|
||||
'data-href': props.href,
|
||||
'data-target': props.target,
|
||||
@@ -168,10 +168,10 @@ export function getSyntheticLinkProps(props: LinkDOMProps) {
|
||||
'data-download': props.download,
|
||||
'data-ping': props.ping,
|
||||
'data-referrer-policy': props.referrerPolicy
|
||||
};
|
||||
} as DOMAttributes<HTMLElement>;
|
||||
}
|
||||
|
||||
export function useLinkProps(props?: LinkDOMProps) {
|
||||
export function useLinkProps(props?: LinkDOMProps): LinkDOMProps {
|
||||
let router = useRouter();
|
||||
const href = router.useHref(props?.href ?? '');
|
||||
return {
|
||||
@@ -183,3 +183,19 @@ export function useLinkProps(props?: LinkDOMProps) {
|
||||
referrerPolicy: props?.referrerPolicy
|
||||
};
|
||||
}
|
||||
|
||||
export function handleLinkClick(e: ReactMouseEvent, router: Router, href: Href | undefined, routerOptions: RouterOptions | undefined): void {
|
||||
// If a custom router is provided, prevent default and forward if this link should client navigate.
|
||||
if (
|
||||
!router.isNative &&
|
||||
e.currentTarget instanceof HTMLAnchorElement &&
|
||||
e.currentTarget.href &&
|
||||
// If props are applied to a router Link component, it may have already prevented default.
|
||||
!e.isDefaultPrevented() &&
|
||||
shouldClientNavigate(e.currentTarget, e) &&
|
||||
href
|
||||
) {
|
||||
e.preventDefault();
|
||||
router.open(e.currentTarget, e, href, routerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
27
node_modules/@react-aria/utils/src/platform.ts
generated
vendored
27
node_modules/@react-aria/utils/src/platform.ts
generated
vendored
@@ -14,10 +14,9 @@ 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);
|
||||
let brands = window.navigator['userAgentData']?.brands;
|
||||
return Array.isArray(brands) && brands.some((brand: {brand: string, version: string}) => re.test(brand.brand)) ||
|
||||
re.test(window.navigator.userAgent);
|
||||
}
|
||||
|
||||
function testPlatform(re: RegExp) {
|
||||
@@ -30,7 +29,7 @@ function cached(fn: () => boolean) {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return fn;
|
||||
}
|
||||
|
||||
|
||||
let res: boolean | null = null;
|
||||
return () => {
|
||||
if (res == null) {
|
||||
@@ -40,40 +39,40 @@ function cached(fn: () => boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
export const isMac = cached(function () {
|
||||
export const isMac: () => boolean = cached(function () {
|
||||
return testPlatform(/^Mac/i);
|
||||
});
|
||||
|
||||
export const isIPhone = cached(function () {
|
||||
export const isIPhone: () => boolean = cached(function () {
|
||||
return testPlatform(/^iPhone/i);
|
||||
});
|
||||
|
||||
export const isIPad = cached(function () {
|
||||
export const isIPad: () => boolean = 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 () {
|
||||
export const isIOS: () => boolean = cached(function () {
|
||||
return isIPhone() || isIPad();
|
||||
});
|
||||
|
||||
export const isAppleDevice = cached(function () {
|
||||
export const isAppleDevice: () => boolean = cached(function () {
|
||||
return isMac() || isIOS();
|
||||
});
|
||||
|
||||
export const isWebKit = cached(function () {
|
||||
export const isWebKit: () => boolean = cached(function () {
|
||||
return testUserAgent(/AppleWebKit/i) && !isChrome();
|
||||
});
|
||||
|
||||
export const isChrome = cached(function () {
|
||||
export const isChrome: () => boolean = cached(function () {
|
||||
return testUserAgent(/Chrome/i);
|
||||
});
|
||||
|
||||
export const isAndroid = cached(function () {
|
||||
export const isAndroid: () => boolean = cached(function () {
|
||||
return testUserAgent(/Android/i);
|
||||
});
|
||||
|
||||
export const isFirefox = cached(function () {
|
||||
export const isFirefox: () => boolean = cached(function () {
|
||||
return testUserAgent(/Firefox/i);
|
||||
});
|
||||
|
||||
18
node_modules/@react-aria/utils/src/runAfterTransition.ts
generated
vendored
18
node_modules/@react-aria/utils/src/runAfterTransition.ts
generated
vendored
@@ -91,9 +91,25 @@ if (typeof document !== 'undefined') {
|
||||
}
|
||||
}
|
||||
|
||||
export function runAfterTransition(fn: () => void) {
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
12
node_modules/@react-aria/utils/src/scrollIntoView.ts
generated
vendored
12
node_modules/@react-aria/utils/src/scrollIntoView.ts
generated
vendored
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
import {getScrollParents} from './getScrollParents';
|
||||
import {nodeContains} from './shadowdom/DOMFunctions';
|
||||
|
||||
interface ScrollIntoViewportOpts {
|
||||
/** The optional containing element of the target to be centered in the viewport. */
|
||||
@@ -22,7 +23,7 @@ interface ScrollIntoViewportOpts {
|
||||
* Similar to `element.scrollIntoView({block: 'nearest'})` (not supported in Edge),
|
||||
* but doesn't affect parents above `scrollView`.
|
||||
*/
|
||||
export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement) {
|
||||
export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement): void {
|
||||
let offsetX = relativeOffset(scrollView, element, 'left');
|
||||
let offsetY = relativeOffset(scrollView, element, 'top');
|
||||
let width = element.offsetWidth;
|
||||
@@ -80,7 +81,7 @@ function relativeOffset(ancestor: HTMLElement, child: HTMLElement, axis: 'left'|
|
||||
if (child.offsetParent === ancestor) {
|
||||
// Stop once we have found the ancestor we are interested in.
|
||||
break;
|
||||
} else if (child.offsetParent.contains(ancestor)) {
|
||||
} else if (nodeContains(child.offsetParent, 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.
|
||||
@@ -97,8 +98,8 @@ function relativeOffset(ancestor: HTMLElement, child: HTMLElement, axis: 'left'|
|
||||
* 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) {
|
||||
if (targetElement && document.contains(targetElement)) {
|
||||
export function scrollIntoViewport(targetElement: Element | null, opts?: ScrollIntoViewportOpts): void {
|
||||
if (targetElement && nodeContains(document, 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
|
||||
@@ -117,6 +118,9 @@ export function scrollIntoViewport(targetElement: Element | null, opts?: ScrollI
|
||||
} 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.
|
||||
if (!isScrollPrevented) {
|
||||
scrollParents.push(root);
|
||||
}
|
||||
for (let scrollParent of scrollParents) {
|
||||
scrollIntoView(scrollParent as HTMLElement, targetElement as HTMLElement);
|
||||
}
|
||||
|
||||
71
node_modules/@react-aria/utils/src/shadowdom/DOMFunctions.ts
generated
vendored
Normal file
71
node_modules/@react-aria/utils/src/shadowdom/DOMFunctions.ts
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Source: https://github.com/microsoft/tabster/blob/a89fc5d7e332d48f68d03b1ca6e344489d1c3898/src/Shadowdomize/DOMFunctions.ts#L16
|
||||
/* eslint-disable rsp-rules/no-non-shadow-contains */
|
||||
|
||||
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);
|
||||
}
|
||||
3
node_modules/@react-aria/utils/src/useDrag1D.ts
generated
vendored
3
node_modules/@react-aria/utils/src/useDrag1D.ts
generated
vendored
@@ -13,6 +13,7 @@
|
||||
/* eslint-disable rulesdir/pure-render */
|
||||
|
||||
import {getOffset} from './getOffset';
|
||||
import {nodeContains} from './shadowdom/DOMFunctions';
|
||||
import {Orientation} from '@react-types/shared';
|
||||
import React, {HTMLAttributes, MutableRefObject, useRef} from 'react';
|
||||
|
||||
@@ -99,7 +100,7 @@ export function useDrag1D(props: UseDrag1DProps): HTMLAttributes<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))) {
|
||||
if (draggingElements.some(elt => nodeContains(target, elt))) {
|
||||
return;
|
||||
}
|
||||
draggingElements.push(target);
|
||||
|
||||
8
node_modules/@react-aria/utils/src/useEffectEvent.ts
generated
vendored
8
node_modules/@react-aria/utils/src/useEffectEvent.ts
generated
vendored
@@ -10,12 +10,16 @@
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {useCallback, useRef} from 'react';
|
||||
import React, {useCallback, useRef} from 'react';
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
|
||||
// Use the earliest effect type possible. useInsertionEffect runs during the mutation phase,
|
||||
// before all layout effects, but is available only in React 18 and later.
|
||||
const useEarlyEffect = React['useInsertionEffect'] ?? useLayoutEffect;
|
||||
|
||||
export function useEffectEvent<T extends Function>(fn?: T): T {
|
||||
const ref = useRef<T | null | undefined>(null);
|
||||
useLayoutEffect(() => {
|
||||
useEarlyEffect(() => {
|
||||
ref.current = fn;
|
||||
}, [fn]);
|
||||
// @ts-ignore
|
||||
|
||||
4
node_modules/@react-aria/utils/src/useEvent.ts
generated
vendored
4
node_modules/@react-aria/utils/src/useEvent.ts
generated
vendored
@@ -19,7 +19,7 @@ export function useEvent<K extends keyof GlobalEventHandlersEventMap>(
|
||||
event: K | (string & {}),
|
||||
handler?: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
) {
|
||||
): void {
|
||||
let handleEvent = useEffectEvent(handler);
|
||||
let isDisabled = handler == null;
|
||||
|
||||
@@ -33,5 +33,5 @@ export function useEvent<K extends keyof GlobalEventHandlersEventMap>(
|
||||
return () => {
|
||||
element.removeEventListener(event, handleEvent as EventListener, options);
|
||||
};
|
||||
}, [ref, event, options, isDisabled, handleEvent]);
|
||||
}, [ref, event, options, isDisabled]);
|
||||
}
|
||||
|
||||
10
node_modules/@react-aria/utils/src/useFormReset.ts
generated
vendored
10
node_modules/@react-aria/utils/src/useFormReset.ts
generated
vendored
@@ -11,26 +11,26 @@
|
||||
*/
|
||||
|
||||
import {RefObject} from '@react-types/shared';
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {useEffect} from 'react';
|
||||
import {useEffectEvent} from './useEffectEvent';
|
||||
|
||||
export function useFormReset<T>(
|
||||
ref: RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | null> | undefined,
|
||||
initialValue: T,
|
||||
onReset: (value: T) => void
|
||||
) {
|
||||
let resetValue = useRef(initialValue);
|
||||
): void {
|
||||
let handleReset = useEffectEvent(() => {
|
||||
if (onReset) {
|
||||
onReset(resetValue.current);
|
||||
onReset(initialValue);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let form = ref?.current?.form;
|
||||
|
||||
form?.addEventListener('reset', handleReset);
|
||||
return () => {
|
||||
form?.removeEventListener('reset', handleReset);
|
||||
};
|
||||
}, [ref, handleReset]);
|
||||
}, [ref]);
|
||||
}
|
||||
|
||||
1
node_modules/@react-aria/utils/src/useGlobalListeners.ts
generated
vendored
1
node_modules/@react-aria/utils/src/useGlobalListeners.ts
generated
vendored
@@ -13,6 +13,7 @@
|
||||
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,
|
||||
|
||||
46
node_modules/@react-aria/utils/src/useId.ts
generated
vendored
46
node_modules/@react-aria/utils/src/useId.ts
generated
vendored
@@ -22,7 +22,16 @@ let canUseDOM = Boolean(
|
||||
window.document.createElement
|
||||
);
|
||||
|
||||
let idsUpdaterMap: Map<string, Array<(v: string) => void>> = new Map();
|
||||
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.
|
||||
@@ -33,35 +42,42 @@ export function useId(defaultId?: string): string {
|
||||
let nextId = useRef(null);
|
||||
|
||||
let res = useSSRSafeId(value);
|
||||
let cleanupRef = useRef(null);
|
||||
|
||||
let updateValue = useCallback((val) => {
|
||||
nextId.current = val;
|
||||
}, []);
|
||||
if (registry) {
|
||||
registry.register(cleanupRef, res);
|
||||
}
|
||||
|
||||
if (canUseDOM) {
|
||||
// TS not smart enough to know that `has` means the value exists
|
||||
if (idsUpdaterMap.has(res) && !idsUpdaterMap.get(res)!.includes(updateValue)) {
|
||||
idsUpdaterMap.set(res, [...idsUpdaterMap.get(res)!, updateValue]);
|
||||
const cacheIdRef = idsUpdaterMap.get(res);
|
||||
if (cacheIdRef && !cacheIdRef.includes(nextId)) {
|
||||
cacheIdRef.push(nextId);
|
||||
} else {
|
||||
idsUpdaterMap.set(res, [updateValue]);
|
||||
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 updated first.
|
||||
// This cannot cause an infinite loop because the ref is always cleaned up.
|
||||
// eslint-disable-next-line
|
||||
useEffect(() => {
|
||||
let newId = nextId.current;
|
||||
if (newId) {
|
||||
nextId.current = null;
|
||||
setValue(newId);
|
||||
}
|
||||
if (newId) { setValue(newId); }
|
||||
|
||||
return () => {
|
||||
if (newId) { nextId.current = null; }
|
||||
};
|
||||
});
|
||||
|
||||
return res;
|
||||
@@ -78,13 +94,13 @@ export function mergeIds(idA: string, idB: string): string {
|
||||
|
||||
let setIdsA = idsUpdaterMap.get(idA);
|
||||
if (setIdsA) {
|
||||
setIdsA.forEach(fn => fn(idB));
|
||||
setIdsA.forEach(ref => (ref.current = idB));
|
||||
return idB;
|
||||
}
|
||||
|
||||
let setIdsB = idsUpdaterMap.get(idB);
|
||||
if (setIdsB) {
|
||||
setIdsB.forEach(fn => fn(idA));
|
||||
setIdsB.forEach((ref) => (ref.current = idA));
|
||||
return idA;
|
||||
}
|
||||
|
||||
|
||||
2
node_modules/@react-aria/utils/src/useLayoutEffect.ts
generated
vendored
2
node_modules/@react-aria/utils/src/useLayoutEffect.ts
generated
vendored
@@ -15,6 +15,6 @@ 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'
|
||||
export const useLayoutEffect: typeof React.useLayoutEffect = typeof document !== 'undefined'
|
||||
? React.useLayoutEffect
|
||||
: () => {};
|
||||
|
||||
4
node_modules/@react-aria/utils/src/useLoadMore.ts
generated
vendored
4
node_modules/@react-aria/utils/src/useLoadMore.ts
generated
vendored
@@ -12,7 +12,7 @@
|
||||
|
||||
import {RefObject, useCallback, useRef} from 'react';
|
||||
import {useEvent} from './useEvent';
|
||||
|
||||
|
||||
import {useLayoutEffect} from './useLayoutEffect';
|
||||
|
||||
export interface LoadMoreProps {
|
||||
@@ -32,7 +32,7 @@ export interface LoadMoreProps {
|
||||
items?: any
|
||||
}
|
||||
|
||||
export function useLoadMore(props: LoadMoreProps, ref: RefObject<HTMLElement | null>) {
|
||||
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.
|
||||
|
||||
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} 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<any>,
|
||||
/**
|
||||
* 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 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, ref, scrollOffset]);
|
||||
}
|
||||
62
node_modules/@react-aria/utils/src/useObjectRef.ts
generated
vendored
62
node_modules/@react-aria/utils/src/useObjectRef.ts
generated
vendored
@@ -10,30 +10,60 @@
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {MutableRefObject, useMemo, useRef} from 'react';
|
||||
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 forwardedRef The original ref intended to be used.
|
||||
* @param ref The original ref intended to be used.
|
||||
* @returns An object ref that updates the given ref.
|
||||
* @see https://reactjs.org/docs/forwarding-refs.html
|
||||
* @see https://react.dev/reference/react/forwardRef
|
||||
*/
|
||||
export function useObjectRef<T>(forwardedRef?: ((instance: T | null) => void) | MutableRefObject<T | null> | null): MutableRefObject<T | null> {
|
||||
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);
|
||||
return useMemo(() => ({
|
||||
get current() {
|
||||
return objRef.current;
|
||||
},
|
||||
set current(value) {
|
||||
objRef.current = value;
|
||||
if (typeof forwardedRef === 'function') {
|
||||
forwardedRef(value);
|
||||
} else if (forwardedRef) {
|
||||
forwardedRef.current = value;
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}), [forwardedRef]);
|
||||
},
|
||||
[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]
|
||||
);
|
||||
}
|
||||
|
||||
14
node_modules/@react-aria/utils/src/useResizeObserver.ts
generated
vendored
14
node_modules/@react-aria/utils/src/useResizeObserver.ts
generated
vendored
@@ -1,6 +1,7 @@
|
||||
|
||||
import {RefObject} from '@react-types/shared';
|
||||
import {useEffect} from 'react';
|
||||
import {useEffectEvent} from './useEffectEvent';
|
||||
|
||||
function hasResizeObserver() {
|
||||
return typeof window.ResizeObserver !== 'undefined';
|
||||
@@ -12,8 +13,11 @@ type useResizeObserverOptionsType<T> = {
|
||||
onResize: () => void
|
||||
}
|
||||
|
||||
export function useResizeObserver<T extends Element>(options: useResizeObserverOptionsType<T>) {
|
||||
export function useResizeObserver<T extends Element>(options: useResizeObserverOptionsType<T>): void {
|
||||
// Only call onResize from inside the effect, otherwise we'll void our assumption that
|
||||
// useEffectEvents are safe to pass in.
|
||||
const {ref, box, onResize} = options;
|
||||
let onResizeEvent = useEffectEvent(onResize);
|
||||
|
||||
useEffect(() => {
|
||||
let element = ref?.current;
|
||||
@@ -22,9 +26,9 @@ export function useResizeObserver<T extends Element>(options: useResizeObserverO
|
||||
}
|
||||
|
||||
if (!hasResizeObserver()) {
|
||||
window.addEventListener('resize', onResize, false);
|
||||
window.addEventListener('resize', onResizeEvent, false);
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize, false);
|
||||
window.removeEventListener('resize', onResizeEvent, false);
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -33,7 +37,7 @@ export function useResizeObserver<T extends Element>(options: useResizeObserverO
|
||||
return;
|
||||
}
|
||||
|
||||
onResize();
|
||||
onResizeEvent();
|
||||
});
|
||||
resizeObserverInstance.observe(element, {box});
|
||||
|
||||
@@ -44,5 +48,5 @@ export function useResizeObserver<T extends Element>(options: useResizeObserverO
|
||||
};
|
||||
}
|
||||
|
||||
}, [onResize, ref, box]);
|
||||
}, [ref, box]);
|
||||
}
|
||||
|
||||
2
node_modules/@react-aria/utils/src/useSyncRef.ts
generated
vendored
2
node_modules/@react-aria/utils/src/useSyncRef.ts
generated
vendored
@@ -19,7 +19,7 @@ interface ContextValue<T> {
|
||||
}
|
||||
|
||||
// Syncs ref from context with ref passed to hook
|
||||
export function useSyncRef<T>(context?: ContextValue<T> | null, ref?: RefObject<T | null>) {
|
||||
export function useSyncRef<T>(context?: ContextValue<T> | null, ref?: RefObject<T | null>): void {
|
||||
useLayoutEffect(() => {
|
||||
if (context && context.ref && ref) {
|
||||
context.ref.current = ref.current;
|
||||
|
||||
11
node_modules/@react-aria/utils/src/useUpdateEffect.ts
generated
vendored
11
node_modules/@react-aria/utils/src/useUpdateEffect.ts
generated
vendored
@@ -11,11 +11,13 @@
|
||||
*/
|
||||
|
||||
import {EffectCallback, useEffect, useRef} from 'react';
|
||||
import {useEffectEvent} from './useEffectEvent';
|
||||
|
||||
// Like useEffect, but only called for updates after the initial render.
|
||||
export function useUpdateEffect(effect: EffectCallback, dependencies: any[]) {
|
||||
export function useUpdateEffect(cb: EffectCallback, dependencies: any[]): void {
|
||||
const isInitialMount = useRef(true);
|
||||
const lastDeps = useRef<any[] | null>(null);
|
||||
let cbEvent = useEffectEvent(cb);
|
||||
|
||||
useEffect(() => {
|
||||
isInitialMount.current = true;
|
||||
@@ -25,12 +27,13 @@ export function useUpdateEffect(effect: EffectCallback, dependencies: any[]) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let prevDeps = lastDeps.current;
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
} else if (!lastDeps.current || dependencies.some((dep, i) => !Object.is(dep, lastDeps[i]))) {
|
||||
effect();
|
||||
} else if (!prevDeps || dependencies.some((dep, i) => !Object.is(dep, prevDeps[i]))) {
|
||||
cbEvent();
|
||||
}
|
||||
lastDeps.current = dependencies;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, dependencies);
|
||||
}
|
||||
|
||||
2
node_modules/@react-aria/utils/src/useUpdateLayoutEffect.ts
generated
vendored
2
node_modules/@react-aria/utils/src/useUpdateLayoutEffect.ts
generated
vendored
@@ -14,7 +14,7 @@ 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[]) {
|
||||
export function useUpdateLayoutEffect(effect: EffectCallback, dependencies: any[]): void {
|
||||
const isInitialMount = useRef(true);
|
||||
const lastDeps = useRef<any[] | null>(null);
|
||||
|
||||
|
||||
26
node_modules/@react-aria/utils/src/useValueEffect.ts
generated
vendored
26
node_modules/@react-aria/utils/src/useValueEffect.ts
generated
vendored
@@ -10,8 +10,8 @@
|
||||
* governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
import {Dispatch, MutableRefObject, useRef, useState} from 'react';
|
||||
import {useEffectEvent, useLayoutEffect} from './';
|
||||
import {Dispatch, RefObject, useCallback, useRef, useState} from 'react';
|
||||
import {useLayoutEffect} from './';
|
||||
|
||||
type SetValueAction<S> = (prev: S) => Generator<any, void, unknown>;
|
||||
|
||||
@@ -21,11 +21,14 @@ type SetValueAction<S> = (prev: S) => Generator<any, void, unknown>;
|
||||
// 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);
|
||||
// Keep an up to date copy of value in a ref so we can access the current value in the generator.
|
||||
// This allows us to maintain a stable queue function.
|
||||
let currValue = useRef(value);
|
||||
let effect: RefObject<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(() => {
|
||||
let nextRef = useRef(() => {
|
||||
if (!effect.current) {
|
||||
return;
|
||||
}
|
||||
@@ -41,24 +44,25 @@ export function useValueEffect<S>(defaultValue: S | (() => S)): [S, Dispatch<Set
|
||||
// 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();
|
||||
if (currValue.current === newValue.value) {
|
||||
nextRef.current();
|
||||
} else {
|
||||
setValue(newValue.value);
|
||||
}
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
currValue.current = value;
|
||||
// If there is an effect currently running, continue to the next yield.
|
||||
if (effect.current) {
|
||||
nextRef();
|
||||
nextRef.current();
|
||||
}
|
||||
});
|
||||
|
||||
let queue = useEffectEvent(fn => {
|
||||
effect.current = fn(value);
|
||||
nextRef();
|
||||
});
|
||||
let queue = useCallback(fn => {
|
||||
effect.current = fn(currValue.current);
|
||||
nextRef.current();
|
||||
}, [nextRef]);
|
||||
|
||||
return [value, queue];
|
||||
}
|
||||
|
||||
44
node_modules/@react-aria/utils/src/useViewportSize.ts
generated
vendored
44
node_modules/@react-aria/utils/src/useViewportSize.ts
generated
vendored
@@ -12,6 +12,7 @@
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useIsSSR} from '@react-aria/ssr';
|
||||
import {willOpenKeyboard} from './keyboard';
|
||||
|
||||
interface ViewportSize {
|
||||
width: number,
|
||||
@@ -25,10 +26,8 @@ export function useViewportSize(): ViewportSize {
|
||||
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 = () => {
|
||||
let updateSize = (newSize: ViewportSize) => {
|
||||
setSize(size => {
|
||||
let newSize = getViewportSize();
|
||||
if (newSize.width === size.width && newSize.height === size.height) {
|
||||
return size;
|
||||
}
|
||||
@@ -36,6 +35,38 @@ export function useViewportSize(): ViewportSize {
|
||||
});
|
||||
};
|
||||
|
||||
// Use visualViewport api to track available height even on iOS virtual keyboard opening
|
||||
let onResize = () => {
|
||||
// Ignore updates when zoomed.
|
||||
if (visualViewport && visualViewport.scale > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateSize(getViewportSize());
|
||||
};
|
||||
|
||||
// When closing the keyboard, iOS does not fire the visual viewport resize event until the animation is complete.
|
||||
// We can anticipate this and resize early by handling the blur event and using the layout size.
|
||||
let frame: number;
|
||||
let onBlur = (e: FocusEvent) => {
|
||||
if (visualViewport && visualViewport.scale > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (willOpenKeyboard(e.target as Element)) {
|
||||
// Wait one frame to see if a new element gets focused.
|
||||
frame = requestAnimationFrame(() => {
|
||||
if (!document.activeElement || !willOpenKeyboard(document.activeElement)) {
|
||||
updateSize({width: document.documentElement.clientWidth, height: document.documentElement.clientHeight});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateSize(getViewportSize());
|
||||
|
||||
window.addEventListener('blur', onBlur, true);
|
||||
|
||||
if (!visualViewport) {
|
||||
window.addEventListener('resize', onResize);
|
||||
} else {
|
||||
@@ -43,6 +74,8 @@ export function useViewportSize(): ViewportSize {
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frame);
|
||||
window.removeEventListener('blur', onBlur, true);
|
||||
if (!visualViewport) {
|
||||
window.removeEventListener('resize', onResize);
|
||||
} else {
|
||||
@@ -56,7 +89,8 @@ export function useViewportSize(): ViewportSize {
|
||||
|
||||
function getViewportSize(): ViewportSize {
|
||||
return {
|
||||
width: (visualViewport && visualViewport?.width) || window.innerWidth,
|
||||
height: (visualViewport && visualViewport?.height) || window.innerHeight
|
||||
// Multiply by the visualViewport scale to get the "natural" size, unaffected by pinch zooming.
|
||||
width: visualViewport ? visualViewport.width * visualViewport.scale : document.documentElement.clientWidth,
|
||||
height: visualViewport ? visualViewport.height * visualViewport.scale : document.documentElement.clientHeight
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user