@react-aria/utils 3.14.2 → 3.16.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 +1056 -0
- package/dist/main.js +57 -11
- package/dist/main.js.map +1 -1
- package/dist/module.js +56 -12
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +15 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -5
- package/src/getScrollParent.ts +5 -1
- package/src/index.ts +2 -2
- package/src/mergeProps.ts +5 -2
- package/src/mergeRefs.ts +4 -0
- package/src/scrollIntoView.ts +52 -6
- package/src/useObjectRef.ts +8 -0
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/utils",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
7
7
|
"module": "dist/module.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
"types": "./dist/types.d.ts",
|
|
10
|
+
"import": "./dist/import.mjs",
|
|
11
|
+
"require": "./dist/main.js"
|
|
12
|
+
},
|
|
8
13
|
"types": "dist/types.d.ts",
|
|
9
14
|
"source": "src/index.ts",
|
|
10
15
|
"files": [
|
|
@@ -17,9 +22,9 @@
|
|
|
17
22
|
"url": "https://github.com/adobe/react-spectrum"
|
|
18
23
|
},
|
|
19
24
|
"dependencies": {
|
|
20
|
-
"@react-aria/ssr": "^3.
|
|
21
|
-
"@react-stately/utils": "^3.
|
|
22
|
-
"@react-types/shared": "^3.
|
|
25
|
+
"@react-aria/ssr": "^3.6.0",
|
|
26
|
+
"@react-stately/utils": "^3.6.0",
|
|
27
|
+
"@react-types/shared": "^3.18.0",
|
|
23
28
|
"@swc/helpers": "^0.4.14",
|
|
24
29
|
"clsx": "^1.1.1"
|
|
25
30
|
},
|
|
@@ -29,5 +34,5 @@
|
|
|
29
34
|
"publishConfig": {
|
|
30
35
|
"access": "public"
|
|
31
36
|
},
|
|
32
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "9d1ba9bd8ebcd63bf3495ade16d349bcb71795ce"
|
|
33
38
|
}
|
package/src/getScrollParent.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
export function getScrollParent(node: Element): Element {
|
|
14
|
+
if (isScrollable(node)) {
|
|
15
|
+
node = node.parentElement;
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
while (node && !isScrollable(node)) {
|
|
15
19
|
node = node.parentElement;
|
|
16
20
|
}
|
|
@@ -18,7 +22,7 @@ export function getScrollParent(node: Element): Element {
|
|
|
18
22
|
return node || document.scrollingElement || document.documentElement;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
function isScrollable(node: Element): boolean {
|
|
25
|
+
export function isScrollable(node: Element): boolean {
|
|
22
26
|
let style = window.getComputedStyle(node);
|
|
23
27
|
return /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY);
|
|
24
28
|
}
|
package/src/index.ts
CHANGED
|
@@ -25,12 +25,12 @@ export {useUpdateEffect} from './useUpdateEffect';
|
|
|
25
25
|
export {useLayoutEffect} from './useLayoutEffect';
|
|
26
26
|
export {useResizeObserver} from './useResizeObserver';
|
|
27
27
|
export {useSyncRef} from './useSyncRef';
|
|
28
|
-
export {getScrollParent} from './getScrollParent';
|
|
28
|
+
export {getScrollParent, isScrollable} from './getScrollParent';
|
|
29
29
|
export {useViewportSize} from './useViewportSize';
|
|
30
30
|
export {useDescription} from './useDescription';
|
|
31
31
|
export {isMac, isIPhone, isIPad, isIOS, isAppleDevice, isWebKit, isChrome, isAndroid} from './platform';
|
|
32
32
|
export {useEvent} from './useEvent';
|
|
33
33
|
export {useValueEffect} from './useValueEffect';
|
|
34
|
-
export {scrollIntoView} from './scrollIntoView';
|
|
34
|
+
export {scrollIntoView, scrollIntoViewport} from './scrollIntoView';
|
|
35
35
|
export {clamp, snapValueToStep} from '@react-stately/utils';
|
|
36
36
|
export {isVirtualClick, isVirtualPointerEvent} from './isVirtualEvent';
|
package/src/mergeProps.ts
CHANGED
|
@@ -18,8 +18,11 @@ interface Props {
|
|
|
18
18
|
[key: string]: any
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
type PropsArg = Props | null | undefined;
|
|
22
|
+
|
|
21
23
|
// taken from: https://stackoverflow.com/questions/51603250/typescript-3-parameter-list-intersection-type/51604379#51604379
|
|
22
|
-
type TupleTypes<T> = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? V : never;
|
|
24
|
+
type TupleTypes<T> = { [P in keyof T]: T[P] } extends { [key: number]: infer V } ? NullToObject<V> : never;
|
|
25
|
+
type NullToObject<T> = T extends (null | undefined) ? {} : T;
|
|
23
26
|
// eslint-disable-next-line no-undef, @typescript-eslint/no-unused-vars
|
|
24
27
|
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
25
28
|
|
|
@@ -30,7 +33,7 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
|
|
30
33
|
* For all other props, the last prop object overrides all previous ones.
|
|
31
34
|
* @param args - Multiple sets of props to merge together.
|
|
32
35
|
*/
|
|
33
|
-
export function mergeProps<T extends
|
|
36
|
+
export function mergeProps<T extends PropsArg[]>(...args: T): UnionToIntersection<TupleTypes<T>> {
|
|
34
37
|
// Start with a base clone of the first argument. This is a lot faster than starting
|
|
35
38
|
// with an empty object and adding properties as we go.
|
|
36
39
|
let result: Props = {...args[0]};
|
package/src/mergeRefs.ts
CHANGED
|
@@ -16,6 +16,10 @@ import {ForwardedRef} from 'react';
|
|
|
16
16
|
* Merges multiple refs into one. Works with either callback or object refs.
|
|
17
17
|
*/
|
|
18
18
|
export function mergeRefs<T>(...refs: ForwardedRef<T>[]): ForwardedRef<T> {
|
|
19
|
+
if (refs.length === 1) {
|
|
20
|
+
return refs[0];
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
return (value: T) => {
|
|
20
24
|
for (let ref of refs) {
|
|
21
25
|
if (typeof ref === 'function') {
|
package/src/scrollIntoView.ts
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import {getScrollParent} from './';
|
|
14
|
+
|
|
15
|
+
interface ScrollIntoViewportOpts {
|
|
16
|
+
/** The optional containing element of the target to be centered in the viewport. */
|
|
17
|
+
containingElement?: Element
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
/**
|
|
14
21
|
* Scrolls `scrollView` so that `element` is visible.
|
|
15
22
|
* Similar to `element.scrollIntoView({block: 'nearest'})` (not supported in Edge),
|
|
@@ -22,20 +29,25 @@ export function scrollIntoView(scrollView: HTMLElement, element: HTMLElement) {
|
|
|
22
29
|
let height = element.offsetHeight;
|
|
23
30
|
let x = scrollView.scrollLeft;
|
|
24
31
|
let y = scrollView.scrollTop;
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
|
|
33
|
+
// Account for top/left border offsetting the scroll top/Left
|
|
34
|
+
let {borderTopWidth, borderLeftWidth} = getComputedStyle(scrollView);
|
|
35
|
+
let borderAdjustedX = scrollView.scrollLeft + parseInt(borderLeftWidth, 10);
|
|
36
|
+
let borderAdjustedY = scrollView.scrollTop + parseInt(borderTopWidth, 10);
|
|
37
|
+
// Ignore end/bottom border via clientHeight/Width instead of offsetHeight/Width
|
|
38
|
+
let maxX = borderAdjustedX + scrollView.clientWidth;
|
|
39
|
+
let maxY = borderAdjustedY + scrollView.clientHeight;
|
|
27
40
|
|
|
28
41
|
if (offsetX <= x) {
|
|
29
|
-
x = offsetX;
|
|
42
|
+
x = offsetX - parseInt(borderLeftWidth, 10);
|
|
30
43
|
} else if (offsetX + width > maxX) {
|
|
31
44
|
x += offsetX + width - maxX;
|
|
32
45
|
}
|
|
33
|
-
if (offsetY <=
|
|
34
|
-
y = offsetY;
|
|
46
|
+
if (offsetY <= borderAdjustedY) {
|
|
47
|
+
y = offsetY - parseInt(borderTopWidth, 10);
|
|
35
48
|
} else if (offsetY + height > maxY) {
|
|
36
49
|
y += offsetY + height - maxY;
|
|
37
50
|
}
|
|
38
|
-
|
|
39
51
|
scrollView.scrollLeft = x;
|
|
40
52
|
scrollView.scrollTop = y;
|
|
41
53
|
}
|
|
@@ -63,3 +75,37 @@ function relativeOffset(ancestor: HTMLElement, child: HTMLElement, axis: 'left'|
|
|
|
63
75
|
}
|
|
64
76
|
return sum;
|
|
65
77
|
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scrolls the `targetElement` so it is visible in the viewport. Accepts an optional `opts.containingElement`
|
|
81
|
+
* that will be centered in the viewport prior to scrolling the targetElement into view. If scrolling is prevented on
|
|
82
|
+
* 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.
|
|
83
|
+
*/
|
|
84
|
+
export function scrollIntoViewport(targetElement: Element, opts?: ScrollIntoViewportOpts) {
|
|
85
|
+
if (document.contains(targetElement)) {
|
|
86
|
+
let root = document.scrollingElement || document.documentElement;
|
|
87
|
+
let isScrollPrevented = window.getComputedStyle(root).overflow === 'hidden';
|
|
88
|
+
// 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
|
|
89
|
+
if (!isScrollPrevented) {
|
|
90
|
+
let {left: originalLeft, top: originalTop} = targetElement.getBoundingClientRect();
|
|
91
|
+
|
|
92
|
+
// use scrollIntoView({block: 'nearest'}) instead of .focus to check if the element is fully in view or not since .focus()
|
|
93
|
+
// 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
|
|
94
|
+
targetElement?.scrollIntoView?.({block: 'nearest'});
|
|
95
|
+
let {left: newLeft, top: newTop} = targetElement.getBoundingClientRect();
|
|
96
|
+
// Account for sub pixel differences from rounding
|
|
97
|
+
if ((Math.abs(originalLeft - newLeft) > 1) || (Math.abs(originalTop - newTop) > 1)) {
|
|
98
|
+
opts?.containingElement?.scrollIntoView?.({block: 'center', inline: 'center'});
|
|
99
|
+
targetElement.scrollIntoView?.({block: 'nearest'});
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
let scrollParent = getScrollParent(targetElement);
|
|
103
|
+
// 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.
|
|
104
|
+
while (targetElement && scrollParent && targetElement !== root && scrollParent !== root) {
|
|
105
|
+
scrollIntoView(scrollParent as HTMLElement, targetElement as HTMLElement);
|
|
106
|
+
targetElement = scrollParent;
|
|
107
|
+
scrollParent = getScrollParent(targetElement);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/useObjectRef.ts
CHANGED
|
@@ -40,6 +40,14 @@ export function useObjectRef<T>(forwardedRef?: ((instance: T | null) => void) |
|
|
|
40
40
|
} else {
|
|
41
41
|
forwardedRef.current = objRef.current;
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
if (typeof forwardedRef === 'function') {
|
|
46
|
+
forwardedRef(null);
|
|
47
|
+
} else {
|
|
48
|
+
forwardedRef.current = null;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
43
51
|
}, [forwardedRef]);
|
|
44
52
|
|
|
45
53
|
return objRef;
|