-
Notifications
You must be signed in to change notification settings - Fork 332
/
Copy pathlayouts.ts
98 lines (88 loc) · 2.8 KB
/
layouts.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
import { MouseEvent, FocusEvent, MutableRefObject, useState } from 'react'
export const getElementOffset = (el?: HTMLElement | null | undefined) => {
if (!el)
return {
top: 0,
left: 0,
}
const { top, left } = el.getBoundingClientRect()
return { top, left }
}
export interface ReactiveDomReact {
top: number
left: number
right: number
width: number
height: number
elementTop: number
}
const defaultRect: ReactiveDomReact = {
top: -1000,
left: -1000,
right: -1000,
width: 0,
height: 0,
elementTop: -1000,
}
const getRectFromDOMWithContainer = (
domRect?: DOMRect,
getContainer?: () => HTMLElement | null,
): ReactiveDomReact => {
if (!domRect) return defaultRect
const container = getContainer ? getContainer() : null
const scrollElement = container || document.documentElement
const { top: offsetTop, left: offsetLeft } = getElementOffset(container)
return {
...domRect,
width: domRect.width || domRect.right - domRect.left,
height: domRect.height || domRect.top - domRect.bottom,
top: domRect.bottom + scrollElement.scrollTop - offsetTop,
left: domRect.left + scrollElement.scrollLeft - offsetLeft,
elementTop: domRect.top + scrollElement.scrollTop - offsetTop,
}
}
export const isUnplacedRect = (rect?: ReactiveDomReact): boolean => {
if (!rect) return true
return rect.top === defaultRect.top && rect.left === defaultRect.left
}
export const getRefRect = (
ref?: MutableRefObject<HTMLElement | null>,
getContainer?: () => HTMLElement | null,
): ReactiveDomReact => {
if (!ref || !ref.current) return defaultRect
const rect = ref.current.getBoundingClientRect()
return getRectFromDOMWithContainer(rect, getContainer)
}
export const getEventRect = (
event?: MouseEvent<HTMLElement> | FocusEvent<HTMLElement>,
getContainer?: () => HTMLElement | null,
) => {
const rect = (event?.target as HTMLElement)?.getBoundingClientRect()
if (!rect) return defaultRect
return getRectFromDOMWithContainer(rect, getContainer)
}
const isRefTarget = (
eventOrRef:
| MouseEvent<HTMLElement>
| FocusEvent<HTMLElement>
| MutableRefObject<HTMLElement | null>,
): eventOrRef is MutableRefObject<HTMLElement | null> => {
return typeof (eventOrRef as any)?.target === 'undefined'
}
export const useRect = (initialState?: ReactiveDomReact | (() => ReactiveDomReact)) => {
const [rect, setRect] = useState<ReactiveDomReact>(initialState || defaultRect)
const updateRect = (
eventOrRef:
| MouseEvent<HTMLElement>
| FocusEvent<HTMLElement>
| MutableRefObject<HTMLElement | null>,
getContainer?: () => HTMLElement | null,
) => {
if (isRefTarget(eventOrRef)) return setRect(getRefRect(eventOrRef, getContainer))
setRect(getEventRect(eventOrRef, getContainer))
}
return {
rect,
setRect: updateRect,
}
}