-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
index.ts
161 lines (141 loc) Β· 4.88 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import {capitalize} from "@nextui-org/shared-utils";
import {useEffect, useRef} from "react";
export type ScrollOverflowVisibility =
| "auto"
| "top"
| "bottom"
| "left"
| "right"
| "both"
| "none";
export type ScrollOverflowEdgeCheck = "all" | "top" | "bottom" | "left" | "right";
export type ScrollOverflowOrientation = "horizontal" | "vertical";
export type ScrollOverflowCheck = ScrollOverflowOrientation | "both";
export interface UseDataScrollOverflowProps {
/**
* The reference to the DOM element on which we're checking overflow.
*/
domRef?: React.RefObject<HTMLElement>;
/**
* Determines the direction of overflow to check.
* - "horizontal" will check for overflow on the x-axis.
* - "vertical" will check for overflow on the y-axis.
* - "both" (default) will check for overflow on both axes.
*
* @default "both"
*/
overflowCheck?: ScrollOverflowCheck;
/**
* Controlled visible state. Passing "auto" will make the shadow visible only when the scroll reaches the edge.
* use "left" / "right" for horizontal scroll and "top" / "bottom" for vertical scroll.
* @default "auto"
*/
visibility?: ScrollOverflowVisibility;
/**
* Enables or disables the overflow checking mechanism.
* @default true
*/
isEnabled?: boolean;
/**
* Defines a buffer or margin within which we won't treat the scroll as reaching the edge.
*
* @default 0 - meaning the check will behave exactly at the edge.
*/
offset?: number;
/**
* List of dependencies to update the overflow check.
*/
updateDeps?: any[];
/**
* Callback to be called when the overflow state changes.
*
* @param visibility ScrollOverflowVisibility
*/
onVisibilityChange?: (overflow: ScrollOverflowVisibility) => void;
}
export function useDataScrollOverflow(props: UseDataScrollOverflowProps = {}) {
const {
domRef,
isEnabled = true,
overflowCheck = "vertical",
visibility = "auto",
offset = 0,
onVisibilityChange,
updateDeps = [],
} = props;
const visibleRef = useRef<ScrollOverflowVisibility>(visibility);
useEffect(() => {
const el = domRef?.current;
if (!el || !isEnabled) return;
const setAttributes = (
direction: string,
hasBefore: boolean,
hasAfter: boolean,
prefix: string,
suffix: string,
) => {
if (visibility === "auto") {
const both = `${prefix}${capitalize(suffix)}Scroll`;
if (hasBefore && hasAfter) {
el.dataset[both] = "true";
el.removeAttribute(`data-${prefix}-scroll`);
el.removeAttribute(`data-${suffix}-scroll`);
} else {
el.dataset[`${prefix}Scroll`] = hasBefore.toString();
el.dataset[`${suffix}Scroll`] = hasAfter.toString();
el.removeAttribute(`data-${prefix}-${suffix}-scroll`);
}
} else {
const next =
hasBefore && hasAfter ? "both" : hasBefore ? prefix : hasAfter ? suffix : "none";
if (next !== visibleRef.current) {
onVisibilityChange?.(next as ScrollOverflowVisibility);
visibleRef.current = next as ScrollOverflowVisibility;
}
}
};
const checkOverflow = () => {
const directions = [
{type: "vertical", prefix: "top", suffix: "bottom"},
{type: "horizontal", prefix: "left", suffix: "right"},
];
for (const {type, prefix, suffix} of directions) {
if (overflowCheck === type || overflowCheck === "both") {
const hasBefore = type === "vertical" ? el.scrollTop > offset : el.scrollLeft > offset;
const hasAfter =
type === "vertical"
? el.scrollTop + el.clientHeight + offset < el.scrollHeight
: el.scrollLeft + el.clientWidth + offset < el.scrollWidth;
setAttributes(type, hasBefore, hasAfter, prefix, suffix);
}
}
};
const clearOverflow = () => {
["top", "bottom", "top-bottom", "left", "right", "left-right"].forEach((attr) => {
el.removeAttribute(`data-${attr}-scroll`);
});
};
// auto
checkOverflow();
el.addEventListener("scroll", checkOverflow);
// controlled
if (visibility !== "auto") {
clearOverflow();
if (visibility === "both") {
el.dataset.topBottomScroll = String(overflowCheck === "vertical");
el.dataset.leftRightScroll = String(overflowCheck === "horizontal");
} else {
el.dataset.topBottomScroll = "false";
el.dataset.leftRightScroll = "false";
["top", "bottom", "left", "right"].forEach((attr) => {
el.dataset[`${attr}Scroll`] = String(visibility === attr);
});
}
}
return () => {
el.removeEventListener("scroll", checkOverflow);
clearOverflow();
};
}, [...updateDeps, isEnabled, visibility, overflowCheck, onVisibilityChange, domRef]);
}
export type UseDataScrollOverflowReturn = ReturnType<typeof useDataScrollOverflow>;