-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.tsx
168 lines (129 loc) · 4.46 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
import React, {ReactInstance, RefObject} from "react";
import {BoundingClientRectObserver} from "@html-ng/bounding-client-rect-observer";
import PropTypes from "prop-types";
import ReactDOM from 'react-dom';
interface BoundsObserverProps {
readonly enabled: boolean;
readonly onBoundsChange: (bounds: DOMRect) => void;
readonly children: React.ReactElement;
}
export class BoundsObserver extends React.Component<BoundsObserverProps, {}> {
static propTypes = {
children: PropTypes.element.isRequired,
};
private _childRef = React.createRef<ReactInstance>();
private _childNode: Element | null = null;
private _observer: BoundingClientRectObserver | null = null;
constructor(props: BoundsObserverProps) {
super(props);
}
componentDidMount() {
const childRef = this._childRef.current;
if (!childRef) {
throw new Error("Reference should have been set by the time the component is mounted");
}
const childNode = ReactDOM.findDOMNode(childRef);
if (!(childNode instanceof Element)) {
throw new Error("Child's corresponding DOM node should be an Element");
}
this._childNode = childNode;
this._observer = this._observe({
root: childNode,
activate: this.props.enabled,
});
}
componentDidUpdate(
prevProps: Readonly<BoundsObserverProps>,
prevState: Readonly<{}>,
snapshot?: any,
) {
const oldObserver = this._observer;
if (!oldObserver) {
throw new Error(`Observer should have been installed by the time the component is updated`);
}
const childNode = this._childNode;
if (!childNode) {
throw new Error("Reference should have been set by the time the component is updated");
}
/**
* Handle the potential change of callbacks
*
* @returns The new current bounds observer
*/
const handleCallbackUpdate = (): BoundingClientRectObserver => {
if (
this.props.onBoundsChange != prevProps.onBoundsChange
) {
oldObserver.disconnect();
const newObserver = this._observe({
root: childNode,
// Ensure that the new observer is active, if the previous one was
activate: prevProps.enabled,
});
this._observer = newObserver;
return newObserver;
} else {
return oldObserver;
}
}
const currentObserver = handleCallbackUpdate();
if (
this.props.enabled != prevProps.enabled
) {
if (this.props.enabled) {
this._activate({observer: currentObserver, root: childNode});
} else {
this._deactivate({observer: currentObserver});
}
}
}
componentWillUnmount() {
const observer = this._observer;
if (!observer) {
throw new Error("Observer should have been installed by the time the component is unmounted");
}
this._deactivate({observer});
}
render() {
const child = React.Children.only(this.props.children);
return React.cloneElement(
child,
{
ref: this._childRef
},
);
}
_observe(args: {
root: Element,
activate: boolean,
}): BoundingClientRectObserver {
const {root, activate} = args;
const observer = new BoundingClientRectObserver((entries) => {
// In practice, in our case there should always be one entry, and the target should be root
entries.forEach((entry) => {
if (entry.target === root) {
this.props.onBoundsChange(entries[0].newBounds);
}
});
});
if (activate) {
this._activate({observer, root});
}
return observer;
}
_activate(args: {
observer: BoundingClientRectObserver,
root: Element,
}) {
const {observer, root} = args;
observer.observe(root);
const initialBounds = root.getBoundingClientRect();
this.props.onBoundsChange(initialBounds);
}
_deactivate(args: {
observer: BoundingClientRectObserver,
}) {
const {observer} = args;
observer.disconnect();
}
}