110
await act(() => root.render(<Test />));
111
});
112
113
+ // @gate enableFragmentRefs && enableFragmentRefsInstanceHandles
114
+ it('attaches fragment handles to nodes', async () => {
115
+ const fragmentParentRef = React.createRef();
116
+ const fragmentRef = React.createRef();
117
+
118
+ function Test({show}) {
119
+ return (
120
+ <Fragment ref={fragmentParentRef}>
121
+ <Fragment ref={fragmentRef}>
122
+ <div id="childA">A</div>
123
+ <div id="childB">B</div>
124
+ </Fragment>
125
+ <div id="childC">C</div>
126
+ {show && <div id="childD">D</div>}
127
+ </Fragment>
128
+ );
129
+ }
130
+
131
+ const root = ReactDOMClient.createRoot(container);
132
+ await act(() => root.render(<Test show={false} />));
133
+
134
+ const childA = document.querySelector('#childA');
135
+ const childB = document.querySelector('#childB');
136
+ const childC = document.querySelector('#childC');
137
+
138
+ expect(childA.unstable_reactFragments.has(fragmentRef.current)).toBe(true);
139
+ expect(childB.unstable_reactFragments.has(fragmentRef.current)).toBe(true);
140
+ expect(childC.unstable_reactFragments.has(fragmentRef.current)).toBe(false);
141
+ expect(childA.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
142
+ true,
143
+ );
144
+ expect(childB.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
145
+ true,
146
+ );
147
+ expect(childC.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
148
+ true,
149
+ );
150
+
151
+ await act(() => root.render(<Test show={true} />));
152
+
153
+ const childD = document.querySelector('#childD');
154
+ expect(childD.unstable_reactFragments.has(fragmentRef.current)).toBe(false);
155
+ expect(childD.unstable_reactFragments.has(fragmentParentRef.current)).toBe(
156
+ true,
157
+ );
158
+ });
159
+
160
describe('focus methods', () => {
161
describe('focus()', () => {
162
// @gate enableFragmentRefs
1092
{withoutStack: true},
1093
);
1094
});
1095
+
1096
+ // @gate enableFragmentRefs && enableFragmentRefsInstanceHandles
1097
+ it('attaches handles to observed elements to allow caching of observers', async () => {
1098
+ const targetToCallbackMap = new WeakMap();
1099
+ let cachedObserver = null;
1100
+ function createObserverIfNeeded(fragmentInstance, onIntersection) {
1101
+ const callbacks = targetToCallbackMap.get(fragmentInstance);
1102
+ targetToCallbackMap.set(
1103
+ fragmentInstance,
1104
+ callbacks ? [...callbacks, onIntersection] : [onIntersection],
1105
+ );
1106
+ if (cachedObserver !== null) {
1107
+ return cachedObserver;
1108
+ }
1109
+ const observer = new IntersectionObserver(entries => {
1110
+ entries.forEach(entry => {
1111
+ const fragmentInstances = entry.target.unstable_reactFragments;
1112
+ if (fragmentInstances) {
1113
+ Array.from(fragmentInstances).forEach(fInstance => {
1114
+ const cbs = targetToCallbackMap.get(fInstance) || [];
1115
+ cbs.forEach(callback => {
1116
+ callback(entry);
1117
+ });
1118
+ });
1119
+ }
1120
+
1121
+ targetToCallbackMap.get(entry.target)?.forEach(callback => {
1122
+ callback(entry);
1123
+ });
1124
+ });
1125
+ });
1126
+ cachedObserver = observer;
1127
+ return observer;
1128
+ }
1129
+
1130
+ function IntersectionObserverFragment({onIntersection, children}) {
1131
+ const fragmentRef = React.useRef(null);
1132
+ React.useLayoutEffect(() => {
1133
+ const observer = createObserverIfNeeded(
1134
+ fragmentRef.current,
1135
+ onIntersection,
1136
+ );
1137
+ fragmentRef.current.observeUsing(observer);
1138
+ const lastRefValue = fragmentRef.current;
1139
+ return () => {
1140
+ lastRefValue.unobserveUsing(observer);
1141
+ };
1142
+ }, []);
1143
+ return <React.Fragment ref={fragmentRef}>{children}</React.Fragment>;
1144
+ }
1145
+
1146
+ let logs = [];
1147
+ function logIntersection(id) {
1148
+ logs.push(`observe: ${id}`);
1149
+ }
1150
+
1151
+ function ChildWithManualIO({id}) {
1152
+ const divRef = React.useRef(null);
1153
+ React.useLayoutEffect(() => {
1154
+ const observer = createObserverIfNeeded(divRef.current, entry => {
1155
+ logIntersection(id);
1156
+ });
1157
+ observer.observe(divRef.current);
1158
+ return () => {
1159
+ observer.unobserve(divRef.current);
1160
+ };
1161
+ }, []);
1162
+ return (
1163
+ <div id={id} ref={divRef}>
1164
+ {id}
1165
+ </div>
1166
+ );
1167
+ }
1168
+
1169
+ function Test() {
1170
+ return (
1171
+ <>
1172
+ <IntersectionObserverFragment
1173
+ onIntersection={() => logIntersection('grandparent')}>
1174
+ <IntersectionObserverFragment
1175
+ onIntersection={() => logIntersection('parentA')}>
1176
+ <div id="childA">A</div>
1177
+ </IntersectionObserverFragment>
1178
+ </IntersectionObserverFragment>
1179
+ <IntersectionObserverFragment
1180
+ onIntersection={() => logIntersection('parentB')}>
1181
+ <div id="childB">B</div>
1182
+ <ChildWithManualIO id="childC" />
1183
+ </IntersectionObserverFragment>
1184
+ </>
1185
+ );
1186
+ }
1187
+
1188
+ const root = ReactDOMClient.createRoot(container);
1189
+ await act(() => root.render(<Test />));
1190
+
1191
+ simulateIntersection([
1192
+ container.querySelector('#childA'),
1193
+ {y: 0, x: 0, width: 1, height: 1},
1194
+ 1,
1195
+ ]);
1196
+ expect(logs).toEqual(['observe: grandparent', 'observe: parentA']);
1197
+
1198
+ logs = [];
1199
+
1200
+ simulateIntersection([
1201
+ container.querySelector('#childB'),
1202
+ {y: 0, x: 0, width: 1, height: 1},
1203
+ 1,
1204
+ ]);
1205
+ expect(logs).toEqual(['observe: parentB']);
1206
+
1207
+ logs = [];
1208
+ simulateIntersection([
1209
+ container.querySelector('#childC'),
1210
+ {y: 0, x: 0, width: 1, height: 1},
1211
+ 1,
1212
+ ]);
1213
+ expect(logs).toEqual(['observe: parentB', 'observe: childC']);
1214
+ });
1215
});
1216
1217
describe('getClientRects', () => {