-
Notifications
You must be signed in to change notification settings - Fork 130
/
Copy pathindex.ts
108 lines (95 loc) · 3.45 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { createEventListener } from "@solid-primitives/event-listener";
import { createHydratableSingletonRoot } from "@solid-primitives/rootless";
import { createDerivedStaticStore } from "@solid-primitives/static-store";
import { Accessor, createSignal, onMount, sharedConfig } from "solid-js";
import { isServer } from "solid-js/web";
export function getScrollParent(node: Element | null): Element {
if (isServer) {
return {} as Element;
}
while (node && !isScrollable(node)) {
node = node.parentElement;
}
return node || document.scrollingElement || document.documentElement;
}
export function isScrollable(node: Element): boolean {
if (isServer) {
return false;
}
const style = window.getComputedStyle(node);
return /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY);
}
export type Position = {
x: number;
y: number;
};
const FALLBACK_SCROLL_POSITION = { x: 0, y: 0 } as const satisfies Position;
/**
* Get an `{ x: number, y: number }` object of element/window scroll position.
*/
export function getScrollPosition(target: Element | Window | undefined): Position {
if (isServer || !target) {
return { ...FALLBACK_SCROLL_POSITION };
}
if (target instanceof Window)
return {
x: target.scrollX,
y: target.scrollY,
};
return {
x: target.scrollLeft,
y: target.scrollTop,
};
}
/**
* Reactive primitive providing a store-like object with current scroll position of specified target.
* @param target element/window to listen to scroll events. can be a reactive singal.
* @returns a store-like reactive object `{ x: number, y: number }` of current scroll position of {@link target}
* @example
* // target will be window by default
* const windowScroll = createScrollPosition();
*
* createEffect(() => {
* // returned object is a reactive store-like structure
* windowScroll.x; // => number
* windowScroll.y; // => number
* });
*/
export function createScrollPosition(
target?: Accessor<Element | Window | undefined> | Element | Window,
): Readonly<Position> {
if (isServer) {
return FALLBACK_SCROLL_POSITION;
}
target = target || window;
const isFn = typeof target === "function",
isHydrating = sharedConfig.context,
getTargetPos = isFn
? () => getScrollPosition((target as Extract<typeof target, Function>)())
: () => getScrollPosition(target as Element | Window),
// changing the calc signal will trigger the derived store to update
[calc, setCalc] = createSignal(isHydrating ? () => FALLBACK_SCROLL_POSITION : getTargetPos, {
equals: false,
}),
trigger = () => setCalc(() => getTargetPos),
pos = createDerivedStaticStore(() => calc()());
// update the position on mount if we are hydrating (initial pos is null)
// or if target is a function (which means it could be a ref that will be populated onMount)
if (isHydrating || isFn) onMount(trigger);
createEventListener(target, "scroll", trigger, { passive: true });
return pos;
}
/**
* Returns a reactive object with current window scroll position.
*
* This is a [singleton root](https://github.com/solidjs-community/solid-primitives/tree/main/packages/rootless#createSingletonRoot) primitive.
*
* @example
* const scroll = useWindowScrollPosition();
* createEffect(() => {
* console.log(scroll.x, scroll.y)
* })
*/
export const useWindowScrollPosition = /*#__PURE__*/ createHydratableSingletonRoot(() =>
createScrollPosition(isServer ? () => undefined : window),
);