@react-aria/utils 3.24.1 → 3.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/import.mjs +4 -2
- package/dist/main.js +4 -1
- package/dist/main.js.map +1 -1
- package/dist/mergeRefs.main.js +1 -1
- package/dist/mergeRefs.main.js.map +1 -1
- package/dist/mergeRefs.mjs +1 -1
- package/dist/mergeRefs.module.js +1 -1
- package/dist/mergeRefs.module.js.map +1 -1
- package/dist/module.js +4 -2
- package/dist/module.js.map +1 -1
- package/dist/openLink.main.js +4 -3
- package/dist/openLink.main.js.map +1 -1
- package/dist/openLink.mjs +4 -3
- package/dist/openLink.module.js +4 -3
- package/dist/openLink.module.js.map +1 -1
- package/dist/platform.main.js +25 -18
- package/dist/platform.main.js.map +1 -1
- package/dist/platform.mjs +25 -18
- package/dist/platform.module.js +25 -18
- package/dist/platform.module.js.map +1 -1
- package/dist/types.d.ts +35 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/useEvent.main.js.map +1 -1
- package/dist/useEvent.module.js.map +1 -1
- package/dist/useFormReset.main.js.map +1 -1
- package/dist/useFormReset.module.js.map +1 -1
- package/dist/useLoadMore.main.js +72 -0
- package/dist/useLoadMore.main.js.map +1 -0
- package/dist/useLoadMore.mjs +67 -0
- package/dist/useLoadMore.module.js +67 -0
- package/dist/useLoadMore.module.js.map +1 -0
- package/dist/useResizeObserver.main.js +6 -3
- package/dist/useResizeObserver.main.js.map +1 -1
- package/dist/useResizeObserver.mjs +6 -3
- package/dist/useResizeObserver.module.js +6 -3
- package/dist/useResizeObserver.module.js.map +1 -1
- package/dist/useSyncRef.main.js.map +1 -1
- package/dist/useSyncRef.module.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +2 -1
- package/src/mergeRefs.ts +2 -2
- package/src/openLink.tsx +3 -2
- package/src/platform.ts +32 -18
- package/src/useEvent.ts +4 -3
- package/src/useFormReset.ts +4 -2
- package/src/useLoadMore.ts +82 -0
- package/src/useResizeObserver.ts +8 -5
- package/src/useSyncRef.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/utils",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.25.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"url": "https://github.com/adobe/react-spectrum"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@react-aria/ssr": "^3.9.
|
|
26
|
-
"@react-stately/utils": "^3.10.
|
|
27
|
-
"@react-types/shared": "^3.
|
|
25
|
+
"@react-aria/ssr": "^3.9.5",
|
|
26
|
+
"@react-stately/utils": "^3.10.2",
|
|
27
|
+
"@react-types/shared": "^3.24.0",
|
|
28
28
|
"@swc/helpers": "^0.5.0",
|
|
29
29
|
"clsx": "^2.0.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
|
32
|
+
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "86d80e3216bc32e75108831cf3a5a720bc849206"
|
|
38
38
|
}
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ export {mergeRefs} from './mergeRefs';
|
|
|
17
17
|
export {filterDOMProps} from './filterDOMProps';
|
|
18
18
|
export {focusWithoutScrolling} from './focusWithoutScrolling';
|
|
19
19
|
export {getOffset} from './getOffset';
|
|
20
|
-
export {openLink,
|
|
20
|
+
export {openLink, useSyntheticLinkProps, RouterProvider, shouldClientNavigate, useRouter, useLinkProps} from './openLink';
|
|
21
21
|
export {runAfterTransition} from './runAfterTransition';
|
|
22
22
|
export {useDrag1D} from './useDrag1D';
|
|
23
23
|
export {useGlobalListeners} from './useGlobalListeners';
|
|
@@ -41,3 +41,4 @@ export {isVirtualClick, isVirtualPointerEvent} from './isVirtualEvent';
|
|
|
41
41
|
export {useEffectEvent} from './useEffectEvent';
|
|
42
42
|
export {useDeepMemo} from './useDeepMemo';
|
|
43
43
|
export {useFormReset} from './useFormReset';
|
|
44
|
+
export {useLoadMore} from './useLoadMore';
|
package/src/mergeRefs.ts
CHANGED
|
@@ -15,8 +15,8 @@ import {ForwardedRef, MutableRefObject} from 'react';
|
|
|
15
15
|
/**
|
|
16
16
|
* Merges multiple refs into one. Works with either callback or object refs.
|
|
17
17
|
*/
|
|
18
|
-
export function mergeRefs<T>(...refs: Array<ForwardedRef<T> | MutableRefObject<T
|
|
19
|
-
if (refs.length === 1) {
|
|
18
|
+
export function mergeRefs<T>(...refs: Array<ForwardedRef<T> | MutableRefObject<T> | null | undefined>): ForwardedRef<T> {
|
|
19
|
+
if (refs.length === 1 && refs[0]) {
|
|
20
20
|
return refs[0];
|
|
21
21
|
}
|
|
22
22
|
|
package/src/openLink.tsx
CHANGED
|
@@ -146,9 +146,10 @@ function openSyntheticLink(target: Element, modifiers: Modifiers) {
|
|
|
146
146
|
getSyntheticLink(target, link => openLink(link, modifiers));
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
export function
|
|
149
|
+
export function useSyntheticLinkProps(props: LinkDOMProps) {
|
|
150
|
+
let router = useRouter();
|
|
150
151
|
return {
|
|
151
|
-
'data-href': props.href,
|
|
152
|
+
'data-href': props.href ? router.useHref(props.href) : undefined,
|
|
152
153
|
'data-target': props.target,
|
|
153
154
|
'data-rel': props.rel,
|
|
154
155
|
'data-download': props.download,
|
package/src/platform.ts
CHANGED
|
@@ -26,40 +26,54 @@ function testPlatform(re: RegExp) {
|
|
|
26
26
|
: false;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
function cached(fn: () => boolean) {
|
|
30
|
+
if (process.env.NODE_ENV === 'test') {
|
|
31
|
+
return fn;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let res: boolean | null = null;
|
|
35
|
+
return () => {
|
|
36
|
+
if (res == null) {
|
|
37
|
+
res = fn();
|
|
38
|
+
}
|
|
39
|
+
return res;
|
|
40
|
+
};
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
export function
|
|
43
|
+
export const isMac = cached(function () {
|
|
44
|
+
return testPlatform(/^Mac/i);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const isIPhone = cached(function () {
|
|
34
48
|
return testPlatform(/^iPhone/i);
|
|
35
|
-
}
|
|
49
|
+
});
|
|
36
50
|
|
|
37
|
-
export
|
|
51
|
+
export const isIPad = cached(function () {
|
|
38
52
|
return testPlatform(/^iPad/i) ||
|
|
39
53
|
// iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
|
|
40
54
|
(isMac() && navigator.maxTouchPoints > 1);
|
|
41
|
-
}
|
|
55
|
+
});
|
|
42
56
|
|
|
43
|
-
export
|
|
57
|
+
export const isIOS = cached(function () {
|
|
44
58
|
return isIPhone() || isIPad();
|
|
45
|
-
}
|
|
59
|
+
});
|
|
46
60
|
|
|
47
|
-
export
|
|
61
|
+
export const isAppleDevice = cached(function () {
|
|
48
62
|
return isMac() || isIOS();
|
|
49
|
-
}
|
|
63
|
+
});
|
|
50
64
|
|
|
51
|
-
export
|
|
65
|
+
export const isWebKit = cached(function () {
|
|
52
66
|
return testUserAgent(/AppleWebKit/i) && !isChrome();
|
|
53
|
-
}
|
|
67
|
+
});
|
|
54
68
|
|
|
55
|
-
export
|
|
69
|
+
export const isChrome = cached(function () {
|
|
56
70
|
return testUserAgent(/Chrome/i);
|
|
57
|
-
}
|
|
71
|
+
});
|
|
58
72
|
|
|
59
|
-
export
|
|
73
|
+
export const isAndroid = cached(function () {
|
|
60
74
|
return testUserAgent(/Android/i);
|
|
61
|
-
}
|
|
75
|
+
});
|
|
62
76
|
|
|
63
|
-
export
|
|
77
|
+
export const isFirefox = cached(function () {
|
|
64
78
|
return testUserAgent(/Firefox/i);
|
|
65
|
-
}
|
|
79
|
+
});
|
package/src/useEvent.ts
CHANGED
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {RefObject
|
|
13
|
+
import {RefObject} from '@react-types/shared';
|
|
14
|
+
import {useEffect} from 'react';
|
|
14
15
|
import {useEffectEvent} from './useEffectEvent';
|
|
15
16
|
|
|
16
17
|
export function useEvent<K extends keyof GlobalEventHandlersEventMap>(
|
|
17
|
-
ref: RefObject<EventTarget>,
|
|
18
|
-
event: K,
|
|
18
|
+
ref: RefObject<EventTarget | null>,
|
|
19
|
+
event: K | (string & {}),
|
|
19
20
|
handler?: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,
|
|
20
21
|
options?: boolean | AddEventListenerOptions
|
|
21
22
|
) {
|
package/src/useFormReset.ts
CHANGED
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
import {RefObject} from '@react-types/shared';
|
|
14
|
+
import {useEffect, useRef} from 'react';
|
|
13
15
|
import {useEffectEvent} from './useEffectEvent';
|
|
14
16
|
|
|
15
17
|
export function useFormReset<T>(
|
|
16
|
-
ref: RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
|
|
18
|
+
ref: RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | null>,
|
|
17
19
|
initialValue: T,
|
|
18
20
|
onReset: (value: T) => void
|
|
19
21
|
) {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {RefObject, useCallback, useRef} from 'react';
|
|
14
|
+
import {useEvent} from './useEvent';
|
|
15
|
+
// eslint-disable-next-line rulesdir/useLayoutEffectRule
|
|
16
|
+
import {useLayoutEffect} from './useLayoutEffect';
|
|
17
|
+
|
|
18
|
+
export interface LoadMoreProps {
|
|
19
|
+
/** Whether data is currently being loaded. */
|
|
20
|
+
isLoading?: boolean,
|
|
21
|
+
/** Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. */
|
|
22
|
+
onLoadMore?: () => void,
|
|
23
|
+
/**
|
|
24
|
+
* The amount of offset from the bottom of your scrollable region that should trigger load more.
|
|
25
|
+
* Uses a percentage value relative to the scroll body's client height. Load more is then triggered
|
|
26
|
+
* when your current scroll position's distance from the bottom of the currently loaded list of items is less than
|
|
27
|
+
* or equal to the provided value. (e.g. 1 = 100% of the scroll region's height).
|
|
28
|
+
* @default 1
|
|
29
|
+
*/
|
|
30
|
+
scrollOffset?: number,
|
|
31
|
+
/** The data currently loaded. */
|
|
32
|
+
items?: any[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useLoadMore(props: LoadMoreProps, ref: RefObject<HTMLElement | null>) {
|
|
36
|
+
let {isLoading, onLoadMore, scrollOffset = 1, items} = props;
|
|
37
|
+
|
|
38
|
+
// Handle scrolling, and call onLoadMore when nearing the bottom.
|
|
39
|
+
let isLoadingRef = useRef(isLoading);
|
|
40
|
+
let prevProps = useRef(props);
|
|
41
|
+
let onScroll = useCallback(() => {
|
|
42
|
+
if (ref.current && !isLoadingRef.current && onLoadMore) {
|
|
43
|
+
let shouldLoadMore = ref.current.scrollHeight - ref.current.scrollTop - ref.current.clientHeight < ref.current.clientHeight * scrollOffset;
|
|
44
|
+
|
|
45
|
+
if (shouldLoadMore) {
|
|
46
|
+
isLoadingRef.current = true;
|
|
47
|
+
onLoadMore();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}, [onLoadMore, ref, scrollOffset]);
|
|
51
|
+
|
|
52
|
+
let lastItems = useRef(items);
|
|
53
|
+
useLayoutEffect(() => {
|
|
54
|
+
// Only update isLoadingRef if props object actually changed,
|
|
55
|
+
// not if a local state change occurred.
|
|
56
|
+
if (props !== prevProps.current) {
|
|
57
|
+
isLoadingRef.current = isLoading;
|
|
58
|
+
prevProps.current = props;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// TODO: Eventually this hook will move back into RAC during which we will accept the collection as a option to this hook.
|
|
62
|
+
// We will only load more if the collection has changed after the last load to prevent multiple onLoadMore from being called
|
|
63
|
+
// while the data from the last onLoadMore is being processed by RAC collection.
|
|
64
|
+
let shouldLoadMore = ref?.current
|
|
65
|
+
&& !isLoadingRef.current
|
|
66
|
+
&& onLoadMore
|
|
67
|
+
&& (!items || items !== lastItems.current)
|
|
68
|
+
&& ref.current.clientHeight === ref.current.scrollHeight;
|
|
69
|
+
|
|
70
|
+
if (shouldLoadMore) {
|
|
71
|
+
isLoadingRef.current = true;
|
|
72
|
+
onLoadMore?.();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
lastItems.current = items;
|
|
76
|
+
}, [isLoading, onLoadMore, props, ref]);
|
|
77
|
+
|
|
78
|
+
// TODO: maybe this should still just return scroll props?
|
|
79
|
+
// Test against case where the ref isn't defined when this is called
|
|
80
|
+
// Think this was a problem when trying to attach to the scrollable body of the table in OnLoadMoreTableBodyScroll
|
|
81
|
+
useEvent(ref, 'scroll', onScroll);
|
|
82
|
+
}
|
package/src/useResizeObserver.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import {RefObject} from '@react-types/shared';
|
|
3
|
+
import {useEffect} from 'react';
|
|
2
4
|
|
|
3
5
|
function hasResizeObserver() {
|
|
4
6
|
return typeof window.ResizeObserver !== 'undefined';
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
type useResizeObserverOptionsType<T> = {
|
|
8
|
-
ref: RefObject<T | undefined> | undefined,
|
|
10
|
+
ref: RefObject<T | undefined | null> | undefined,
|
|
11
|
+
box?: ResizeObserverBoxOptions,
|
|
9
12
|
onResize: () => void
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
export function useResizeObserver<T extends Element>(options: useResizeObserverOptionsType<T>) {
|
|
13
|
-
const {ref, onResize} = options;
|
|
16
|
+
const {ref, box, onResize} = options;
|
|
14
17
|
|
|
15
18
|
useEffect(() => {
|
|
16
19
|
let element = ref?.current;
|
|
@@ -32,7 +35,7 @@ export function useResizeObserver<T extends Element>(options: useResizeObserverO
|
|
|
32
35
|
|
|
33
36
|
onResize();
|
|
34
37
|
});
|
|
35
|
-
resizeObserverInstance.observe(element);
|
|
38
|
+
resizeObserverInstance.observe(element, {box});
|
|
36
39
|
|
|
37
40
|
return () => {
|
|
38
41
|
if (element) {
|
|
@@ -41,5 +44,5 @@ export function useResizeObserver<T extends Element>(options: useResizeObserverO
|
|
|
41
44
|
};
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
}, [onResize, ref]);
|
|
47
|
+
}, [onResize, ref, box]);
|
|
45
48
|
}
|
package/src/useSyncRef.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {MutableRefObject
|
|
13
|
+
import {MutableRefObject} from 'react';
|
|
14
|
+
import {RefObject} from '@react-types/shared';
|
|
14
15
|
import {useLayoutEffect} from './';
|
|
15
16
|
|
|
16
17
|
interface ContextValue<T> {
|
|
@@ -18,7 +19,7 @@ interface ContextValue<T> {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
// Syncs ref from context with ref passed to hook
|
|
21
|
-
export function useSyncRef<T>(context?: ContextValue<T> | null, ref?: RefObject<T>) {
|
|
22
|
+
export function useSyncRef<T>(context?: ContextValue<T> | null, ref?: RefObject<T | null>) {
|
|
22
23
|
useLayoutEffect(() => {
|
|
23
24
|
if (context && context.ref && ref) {
|
|
24
25
|
context.ref.current = ref.current;
|