-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.tsx
295 lines (272 loc) · 10 KB
/
index.tsx
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/**
* @module react-observer-hook
*/
/**
*
*/
import * as React from 'react'
import { ResizeObserverEntry, ResizeObserver } from 'resize-observer-browser' // @types/resize-observer-browser
// https://github.com/Microsoft/TypeScript/issues/16255#issuecomment-436935103
/**
* @hidden
*/
declare global {
interface Window {
IntersectionObserver: typeof IntersectionObserver
}
}
/**
* An Observer represents a generic Observer API,
* such as an [[IntersectionObserver]], [[MutationObserver]]
* or [[ResizeObserver]].
*
* though there is a clear common interface between
* Observer APIs, there's actually no formal definition
* I can find anywhere of what an Observer API is
* and actually conforms to, so I made this up...
* @hidden
*/
export interface Observer<EntriesType extends ReadonlyArray<EntryType>, EntryType extends { target: Node }, OptionsType> {
new(
callback: (entries: EntriesType, originator?: ObserverImpl<EntriesType>) => void,
options?: OptionsType
) : ObserverImpl<EntriesType>
}
/**
* @hidden
*/
export interface ObserverImpl<EntriesType extends ReadonlyArray<{ target: Node }>> {
/**
* Clears all observed targets of this [[Observer]].
* Effectively a destructor.
*/
disconnect: () => void,
/**
* Starts observing an element.
*/
observe: (e: Node, options: any) => void,
/**
* Stops observing an Node.
* [[MutationObserver]], specifically does not have an `unobserve`
* function.
*/
unobserve?: (e: Node) => void,
/**
* the function of `takeRecords()` is a little confusing,
* and tied to the implementation of an [[Observer]].
* Internally, an [[Observer]] maintains a list of observation
* events, but may not fire them immediately.
*
* When `takeRecords()` is called, it returns all observation
* events which have not yet fired a callback. Records
* received by this method do *not* propagate to their normal
* callbacks.
*
* I see this as being useful for two things: (1) forcing, at
* some point, the observer to tell you the current state of the
* world and (2) using an IntersectionObserver in a non-callback
* focused way.
*
* The [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/takeRecord) note that if you use this method you "don't need"
* to set a callback, which would imply it's intended as an alternate,
* polling based mode for the Observer API.
*/
takeRecords?: () => EntriesType
}
/**
* @hidden
*/
type useObserveRef<EntryType extends { target: Node }> = (Default?: EntryType, options?: any) => [EntryType | undefined, callbackRef]
/**
* @see https://reactjs.org/docs/refs-and-the-dom.html#callback-refs
* @hidden
*/
type callbackRef = (element: Node) => void
/**
* useObserver is a low-level API for using
* an API conforming to the [[Observer]] interface.
*
* It wraps a generic [[Observer]], producing a [[useObserveRef]]
* which can bind React refs to the [[Observer]].
*
* In virtually all cases, you should use [[useMutationObserver]],
* [[useIntersectionObserver]] or [[useResizeObserver]] instead.
* @hidden
*/
export const useObserver =
<EntriesType extends ReadonlyArray<EntryType>, EntryType extends { target : Node }, OptionsType>(
o: Observer<EntriesType, EntryType, OptionsType>,
options?: OptionsType):
useObserveRef<EntriesType[0]> => {
// oh boy. the weak map.
// in essence, as well as being reversible,
// a weak map allows its keys to be GC'd
// this should mean that callbacks we add
// to this callback mapping get automatically
// gc'd along with the element when the element
// is destroyed by react.
//
// otherwise, we'd annoyingly have to keep tabs
// on when elements go in and out of existence.
const elementToCallback:
WeakMap<Node, (setState: EntriesType[0]|undefined) => void> =
React.useMemo(() => new WeakMap(), []);
const callback = React.useCallback((entries: EntriesType) =>
entries.forEach((entry: EntriesType[0]) =>
(elementToCallback.get(entry.target)
|| (()=>{}))(entry)
)
, []);
const observer = React.useMemo(() => new o(callback, options), [])
// calling this essentially
// creates a new ref and binds it
return (Default?: EntriesType[0], options?: any) => {
const [entry, setEntry] = React.useState<EntriesType[0] | undefined>(Default);
return [entry, React.useCallback((element: Node) => {
// here we could potentially use the element going null
// to unobserve the previously observed ref. however,
// i dont see any documentation indicating that
// an observer observing an element that no longer exists
// is any kind of issue, so we might as well just leave it
if (element === null) return;
elementToCallback.set(element, setEntry)
observer.observe(element, options)
}, [])]
}
}
/**
* [[useResizeObserver]] is a React hook exposing the functionality of
* the [ResizeObserver][mdn: ResizeObserver] API, which is an efficient
* way to tell when an element changes size.
*
* The function returns two values in an array.
*
* 1. a [ResizeObserverEntry][mdn: ResizeObserverEntry] or `undefined`,
* representing the current known size.
*
* 2.a React ref you can pass in the `ref={}` parameter to any
* elements you want to track the size of.
*
*
* @example [useResizeObserver](example/src/useResizeObserver.js)
*
*/
export const useResizeObserver = (
Default?: ResizeObserverEntry
): [ResizeObserverEntry | undefined, callbackRef] => useObserver(ResizeObserver)(Default)
/**
* [[useMutationObserver]] is a React hook exposing the functionality of the
* [MutationObserver][mdn: MutationObserver] API, which is an efficient way to
* tell when the children of an element changes.
*
* This function returns two values in an array:
*
* 1. A [MutationRecord][mdn: MutationRecord], or `undefined` representing
* details on how the children were modified.
*
* 2. A [React ref][react docs: react ref] that you can pass in the `ref={}`
* parameter to any elements you want to track child changes for.
*
* [mdn: MutationObserver]: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver "MDN docs: MutationObserver"
* [mdn: MutationRecord]: https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord "MDN docs: MutationRecord"
* [react docs: react ref]: https://reactjs.org/docs/refs-and-the-dom.html "React Docs: Refs and the DOM"
*
* @example [useMutationObserver](example/src/useMutationObserver.js)
*
* ```javascript
* import React from 'react'
* import { useMutationObserver } from 'react-observer-hook'
*
* export default () => {
* const [mutation, ref] = useMutationObserver()
* const childRef = React.useRef()
* const [text, setText] = React.useState("example")
* React.useEffect(() => {
* window.setTimeout(() => setText("text2"), 1000)
* }, [])
*
* return <div>
*
* <div ref={ref}>
* <div ref={childRef}>{text}</div>
* </div>
*
* changes to the DOM:
* {JSON.stringify(mutation)}
*
* </div>
* }
*
* ```
*/
export const useMutationObserver = (
Default?: MutationRecord,
/**
* [Initialization options](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit)
* for the [[MutationObserver]], used to filter the kinds of mutation events observed.
*/
options?: MutationObserverInit
): [MutationRecord | undefined, callbackRef] => useObserver(MutationObserver)(Default, options)
/**
* [[useIntersectionObserver]] is a React hook exposing the functionality of the
* [IntersectionObserver][mdn: IntersectionObserver] API, which is an efficient way to
* tell when an element becomes visible within some viewport, which could be
* the parent window, or the containing element.
*
* This function returns a factory function, which when called returns two values in an array:
*
* 1. An [IntersectionObserverEntry][mdn: IntersectionObserverEntry], or `undefined` representing
* details on how the children were modified.
*
* 2. A [React ref][react docs: react ref] that you can pass in the `ref={}`
* parameter to any elements you want to track child changes for.
*
* This factory type construction allows you to pass your useIntersectionObserver value
* any number to children that you want to intersect with you.
*
* [mdn: IntersectionObserver]: https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver "MDN docs: IntersectionObserver"
* [mdn: IntersectionObserverEntry]: https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry "MDN docs: IntersectionObserverEntry"
* [react docs: react ref]: https://reactjs.org/docs/refs-and-the-dom.html "React Docs: Refs and the DOM"
*
* @example [useIntersectionObserver](example/src/useIntersectionObserver.js)
*
* ```javascript
* import React from 'react'
* import { useIntersectionObserver } from 'react-observer-hook'
*
* export default () => {
* const [{
* bindingClientRect: { width, height },
* intersectionRatio,
* intersectionRect,
* isIntersecting,
* rootBounds,
* target,
* time
* }, ref] = useResizeObserver()
*
* return <React.Fragment>
* <textarea ref={ref}>
* Resize me!!
* </textarea>
* <table> <thead><tr><td>param,</td><td>value</td></tr></thead>
* <tbody>
* {Object.entries({
* width, height, intersectionRatio, intersectionRect,
* isIntersecting, rootBounds, target, time
* }).map(([k,v]) => <tr><td>{k}</td><td>{v}</td></tr>)}
* </tbody></table>
* </React.Fragment>
*
* }
*
* ```
*/
export const useIntersectionObserver = (
/**
* [Initialization options](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#Parameters)
* for the [[IntersectionObserver]]. The important ones are `root`, specifying the root element, and `threshold`, specifying the
* different amounts of intersection which should generate an event.
*/
options?: IntersectionObserverInit
) => useObserver(IntersectionObserver, options)