airyhooks 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -112,6 +112,110 @@ describe("useBoolean", () => {
|
|
|
112
112
|
expect(handlers1.setFalse).toBe(handlers2.setFalse);
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
|
+
`,
|
|
116
|
+
useClickAway: `import { useEffect } from "react";
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Detects clicks outside of a target element.
|
|
120
|
+
*
|
|
121
|
+
* @param ref - React ref to the target element
|
|
122
|
+
* @param callback - Function to call when click outside is detected
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* const ref = useRef<HTMLDivElement>(null);
|
|
126
|
+
*
|
|
127
|
+
* useClickAway(ref, () => {
|
|
128
|
+
* setIsOpen(false);
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* return <div ref={ref}>Content</div>;
|
|
132
|
+
*/
|
|
133
|
+
export function useClickAway<T extends HTMLElement>(
|
|
134
|
+
ref: React.RefObject<null | T>,
|
|
135
|
+
callback: () => void,
|
|
136
|
+
): void {
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
139
|
+
const element = ref.current;
|
|
140
|
+
if (element && !element.contains(event.target as Node)) {
|
|
141
|
+
callback();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
146
|
+
return () => {
|
|
147
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
148
|
+
};
|
|
149
|
+
}, [ref, callback]);
|
|
150
|
+
}
|
|
151
|
+
`, useClickAway_test: `import { cleanup, fireEvent, render } from "@testing-library/react";
|
|
152
|
+
import React from "react";
|
|
153
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
154
|
+
|
|
155
|
+
import { useClickAway } from "./useClickAway.js";
|
|
156
|
+
|
|
157
|
+
describe("useClickAway", () => {
|
|
158
|
+
afterEach(() => {
|
|
159
|
+
cleanup();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should call callback when clicking outside element", () => {
|
|
163
|
+
const callback = vi.fn();
|
|
164
|
+
const Component = () => {
|
|
165
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
166
|
+
useClickAway(ref, callback);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div>
|
|
170
|
+
<div data-testid="target" ref={ref}>
|
|
171
|
+
Target
|
|
172
|
+
</div>
|
|
173
|
+
<div data-testid="outside">Outside</div>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const { getByTestId } = render(<Component />);
|
|
179
|
+
|
|
180
|
+
fireEvent.mouseDown(getByTestId("outside"));
|
|
181
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should not call callback when clicking inside element", () => {
|
|
185
|
+
const callback = vi.fn();
|
|
186
|
+
const Component = () => {
|
|
187
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
188
|
+
useClickAway(ref, callback);
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div data-testid="target" ref={ref}>
|
|
192
|
+
Target
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const { getByTestId } = render(<Component />);
|
|
198
|
+
|
|
199
|
+
fireEvent.mouseDown(getByTestId("target"));
|
|
200
|
+
expect(callback).not.toHaveBeenCalled();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should cleanup listener on unmount", () => {
|
|
204
|
+
const callback = vi.fn();
|
|
205
|
+
const removeSpy = vi.spyOn(document, "removeEventListener");
|
|
206
|
+
|
|
207
|
+
const Component = () => {
|
|
208
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
209
|
+
useClickAway(ref, callback);
|
|
210
|
+
return <div ref={ref} />;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const { unmount } = render(<Component />);
|
|
214
|
+
unmount();
|
|
215
|
+
|
|
216
|
+
expect(removeSpy).toHaveBeenCalledWith("mousedown", expect.any(Function));
|
|
217
|
+
});
|
|
218
|
+
});
|
|
115
219
|
`,
|
|
116
220
|
useCopyToClipboard: `import { useCallback, useState } from "react";
|
|
117
221
|
|
|
@@ -927,6 +1031,274 @@ describe("useDocumentTitle", () => {
|
|
|
927
1031
|
expect(document.title).toBe(originalTitle);
|
|
928
1032
|
});
|
|
929
1033
|
});
|
|
1034
|
+
`,
|
|
1035
|
+
useEventListener: `import { useEffect, useRef } from "react";
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Attaches an event listener to a target element or window with automatic cleanup.
|
|
1039
|
+
*
|
|
1040
|
+
* @param eventName - The event type to listen for (e.g., 'click', 'scroll', 'keydown')
|
|
1041
|
+
* @param handler - The event handler function
|
|
1042
|
+
* @param element - The target element or window (default: window)
|
|
1043
|
+
* @param options - Event listener options (capture, passive, once)
|
|
1044
|
+
*
|
|
1045
|
+
* @example
|
|
1046
|
+
* // Listen for clicks on window
|
|
1047
|
+
* useEventListener('click', (e) => console.log('Clicked!'));
|
|
1048
|
+
*
|
|
1049
|
+
* @example
|
|
1050
|
+
* // Listen for clicks on a specific element
|
|
1051
|
+
* const buttonRef = useRef<HTMLButtonElement>(null);
|
|
1052
|
+
* useEventListener('click', handleClick, buttonRef);
|
|
1053
|
+
*
|
|
1054
|
+
* @example
|
|
1055
|
+
* // With options
|
|
1056
|
+
* useEventListener('scroll', handleScroll, window, { passive: true });
|
|
1057
|
+
*/
|
|
1058
|
+
export function useEventListener<K extends keyof WindowEventMap>(
|
|
1059
|
+
eventName: K,
|
|
1060
|
+
handler: (event: WindowEventMap[K]) => void,
|
|
1061
|
+
element?: Window,
|
|
1062
|
+
options?: AddEventListenerOptions | boolean,
|
|
1063
|
+
): void;
|
|
1064
|
+
export function useEventListener<
|
|
1065
|
+
K extends keyof HTMLElementEventMap,
|
|
1066
|
+
T extends HTMLElement = HTMLDivElement,
|
|
1067
|
+
>(
|
|
1068
|
+
eventName: K,
|
|
1069
|
+
handler: (event: HTMLElementEventMap[K]) => void,
|
|
1070
|
+
element: React.RefObject<null | T>,
|
|
1071
|
+
options?: AddEventListenerOptions | boolean,
|
|
1072
|
+
): void;
|
|
1073
|
+
export function useEventListener<K extends keyof DocumentEventMap>(
|
|
1074
|
+
eventName: K,
|
|
1075
|
+
handler: (event: DocumentEventMap[K]) => void,
|
|
1076
|
+
element: Document,
|
|
1077
|
+
options?: AddEventListenerOptions | boolean,
|
|
1078
|
+
): void;
|
|
1079
|
+
export function useEventListener<
|
|
1080
|
+
KW extends keyof WindowEventMap,
|
|
1081
|
+
KH extends keyof HTMLElementEventMap,
|
|
1082
|
+
KD extends keyof DocumentEventMap,
|
|
1083
|
+
T extends HTMLElement = HTMLElement,
|
|
1084
|
+
>(
|
|
1085
|
+
eventName: KD | KH | KW,
|
|
1086
|
+
handler: (
|
|
1087
|
+
event:
|
|
1088
|
+
| DocumentEventMap[KD]
|
|
1089
|
+
| Event
|
|
1090
|
+
| HTMLElementEventMap[KH]
|
|
1091
|
+
| WindowEventMap[KW],
|
|
1092
|
+
) => void,
|
|
1093
|
+
element?: Document | React.RefObject<null | T> | Window,
|
|
1094
|
+
options?: AddEventListenerOptions | boolean,
|
|
1095
|
+
): void {
|
|
1096
|
+
const savedHandler = useRef(handler);
|
|
1097
|
+
|
|
1098
|
+
useEffect(() => {
|
|
1099
|
+
savedHandler.current = handler;
|
|
1100
|
+
}, [handler]);
|
|
1101
|
+
|
|
1102
|
+
useEffect(() => {
|
|
1103
|
+
let targetElement: Document | Element | null | Window;
|
|
1104
|
+
|
|
1105
|
+
if (element === undefined) {
|
|
1106
|
+
targetElement = window;
|
|
1107
|
+
} else if (element instanceof Document || element instanceof Window) {
|
|
1108
|
+
targetElement = element;
|
|
1109
|
+
} else {
|
|
1110
|
+
targetElement = element.current;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (!targetElement?.addEventListener) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const eventListener: typeof handler = (event) => {
|
|
1118
|
+
savedHandler.current(event);
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
targetElement.addEventListener(eventName, eventListener, options);
|
|
1122
|
+
|
|
1123
|
+
return () => {
|
|
1124
|
+
targetElement.removeEventListener(eventName, eventListener, options);
|
|
1125
|
+
};
|
|
1126
|
+
}, [eventName, element, options]);
|
|
1127
|
+
}
|
|
1128
|
+
`, useEventListener_test: `import { cleanup, fireEvent, render } from "@testing-library/react";
|
|
1129
|
+
import React from "react";
|
|
1130
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
1131
|
+
|
|
1132
|
+
import { useEventListener } from "./useEventListener.js";
|
|
1133
|
+
|
|
1134
|
+
describe("useEventListener", () => {
|
|
1135
|
+
afterEach(() => {
|
|
1136
|
+
cleanup();
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
it("should add event listener to window by default", () => {
|
|
1140
|
+
const handler = vi.fn();
|
|
1141
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
1142
|
+
|
|
1143
|
+
const Component = () => {
|
|
1144
|
+
useEventListener("click", handler);
|
|
1145
|
+
return null;
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
render(<Component />);
|
|
1149
|
+
|
|
1150
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
1151
|
+
"click",
|
|
1152
|
+
expect.any(Function),
|
|
1153
|
+
undefined,
|
|
1154
|
+
);
|
|
1155
|
+
addSpy.mockRestore();
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
it("should call handler when event is fired on window", () => {
|
|
1159
|
+
const handler = vi.fn();
|
|
1160
|
+
|
|
1161
|
+
const Component = () => {
|
|
1162
|
+
useEventListener("click", handler);
|
|
1163
|
+
return null;
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
render(<Component />);
|
|
1167
|
+
|
|
1168
|
+
fireEvent.click(window);
|
|
1169
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
it("should add event listener to ref element", () => {
|
|
1173
|
+
const handler = vi.fn();
|
|
1174
|
+
|
|
1175
|
+
const Component = () => {
|
|
1176
|
+
const ref = React.useRef<HTMLButtonElement>(null);
|
|
1177
|
+
useEventListener("click", handler, ref);
|
|
1178
|
+
return <button ref={ref}>Click me</button>;
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
const { getByText } = render(<Component />);
|
|
1182
|
+
|
|
1183
|
+
fireEvent.click(getByText("Click me"));
|
|
1184
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
it("should not fire handler when clicking outside ref element", () => {
|
|
1188
|
+
const handler = vi.fn();
|
|
1189
|
+
|
|
1190
|
+
const Component = () => {
|
|
1191
|
+
const ref = React.useRef<HTMLButtonElement>(null);
|
|
1192
|
+
useEventListener("click", handler, ref);
|
|
1193
|
+
return (
|
|
1194
|
+
<div>
|
|
1195
|
+
<button ref={ref}>Target</button>
|
|
1196
|
+
<button>Other</button>
|
|
1197
|
+
</div>
|
|
1198
|
+
);
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
const { getByText } = render(<Component />);
|
|
1202
|
+
|
|
1203
|
+
fireEvent.click(getByText("Other"));
|
|
1204
|
+
expect(handler).not.toHaveBeenCalled();
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
it("should add event listener to document", () => {
|
|
1208
|
+
const handler = vi.fn();
|
|
1209
|
+
const addSpy = vi.spyOn(document, "addEventListener");
|
|
1210
|
+
|
|
1211
|
+
const Component = () => {
|
|
1212
|
+
useEventListener("click", handler, document);
|
|
1213
|
+
return null;
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
render(<Component />);
|
|
1217
|
+
|
|
1218
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
1219
|
+
"click",
|
|
1220
|
+
expect.any(Function),
|
|
1221
|
+
undefined,
|
|
1222
|
+
);
|
|
1223
|
+
addSpy.mockRestore();
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
it("should remove event listener on unmount", () => {
|
|
1227
|
+
const handler = vi.fn();
|
|
1228
|
+
const removeSpy = vi.spyOn(window, "removeEventListener");
|
|
1229
|
+
|
|
1230
|
+
const Component = () => {
|
|
1231
|
+
useEventListener("click", handler);
|
|
1232
|
+
return null;
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
const { unmount } = render(<Component />);
|
|
1236
|
+
unmount();
|
|
1237
|
+
|
|
1238
|
+
expect(removeSpy).toHaveBeenCalledWith(
|
|
1239
|
+
"click",
|
|
1240
|
+
expect.any(Function),
|
|
1241
|
+
undefined,
|
|
1242
|
+
);
|
|
1243
|
+
removeSpy.mockRestore();
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
it("should pass options to event listener", () => {
|
|
1247
|
+
const handler = vi.fn();
|
|
1248
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
1249
|
+
const options = { capture: true, passive: true };
|
|
1250
|
+
|
|
1251
|
+
const Component = () => {
|
|
1252
|
+
useEventListener("scroll", handler, undefined, options);
|
|
1253
|
+
return null;
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
render(<Component />);
|
|
1257
|
+
|
|
1258
|
+
expect(addSpy).toHaveBeenCalledWith(
|
|
1259
|
+
"scroll",
|
|
1260
|
+
expect.any(Function),
|
|
1261
|
+
options,
|
|
1262
|
+
);
|
|
1263
|
+
addSpy.mockRestore();
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
it("should update handler without re-adding listener", () => {
|
|
1267
|
+
const handler1 = vi.fn();
|
|
1268
|
+
const handler2 = vi.fn();
|
|
1269
|
+
const addSpy = vi.spyOn(window, "addEventListener");
|
|
1270
|
+
|
|
1271
|
+
const Component = ({ handler }: { handler: () => void }) => {
|
|
1272
|
+
useEventListener("click", handler);
|
|
1273
|
+
return null;
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
const { rerender } = render(<Component handler={handler1} />);
|
|
1277
|
+
expect(addSpy).toHaveBeenCalledTimes(1);
|
|
1278
|
+
|
|
1279
|
+
rerender(<Component handler={handler2} />);
|
|
1280
|
+
|
|
1281
|
+
fireEvent.click(window);
|
|
1282
|
+
expect(handler1).not.toHaveBeenCalled();
|
|
1283
|
+
expect(handler2).toHaveBeenCalledTimes(1);
|
|
1284
|
+
|
|
1285
|
+
addSpy.mockRestore();
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
it("should handle keydown events", () => {
|
|
1289
|
+
const handler = vi.fn();
|
|
1290
|
+
|
|
1291
|
+
const Component = () => {
|
|
1292
|
+
useEventListener("keydown", handler);
|
|
1293
|
+
return null;
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
render(<Component />);
|
|
1297
|
+
|
|
1298
|
+
fireEvent.keyDown(window, { key: "Enter" });
|
|
1299
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
1300
|
+
});
|
|
1301
|
+
});
|
|
930
1302
|
`,
|
|
931
1303
|
useFetch: `import { useCallback, useEffect, useRef, useState } from "react";
|
|
932
1304
|
|
|
@@ -1172,6 +1544,450 @@ describe("useFetch", () => {
|
|
|
1172
1544
|
expect(result.current.data).toEqual(mockData);
|
|
1173
1545
|
});
|
|
1174
1546
|
});
|
|
1547
|
+
`,
|
|
1548
|
+
useHover: `import { useCallback, useRef, useState } from "react";
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* Tracks mouse hover state on a DOM element via ref.
|
|
1552
|
+
*
|
|
1553
|
+
* @returns Tuple of [isHovered, ref]
|
|
1554
|
+
*
|
|
1555
|
+
* @example
|
|
1556
|
+
* const [isHovered, ref] = useHover();
|
|
1557
|
+
*
|
|
1558
|
+
* return (
|
|
1559
|
+
* <div
|
|
1560
|
+
* ref={ref}
|
|
1561
|
+
* style={{
|
|
1562
|
+
* backgroundColor: isHovered ? "blue" : "gray",
|
|
1563
|
+
* }}
|
|
1564
|
+
* >
|
|
1565
|
+
* Hover me!
|
|
1566
|
+
* </div>
|
|
1567
|
+
* );
|
|
1568
|
+
*/
|
|
1569
|
+
export function useHover<T extends HTMLElement = HTMLElement>(): [
|
|
1570
|
+
boolean,
|
|
1571
|
+
React.RefObject<T>,
|
|
1572
|
+
] {
|
|
1573
|
+
const ref = useRef<T>(null);
|
|
1574
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
1575
|
+
|
|
1576
|
+
const handleMouseEnter = useCallback(() => {
|
|
1577
|
+
setIsHovered(true);
|
|
1578
|
+
}, []);
|
|
1579
|
+
|
|
1580
|
+
const handleMouseLeave = useCallback(() => {
|
|
1581
|
+
setIsHovered(false);
|
|
1582
|
+
}, []);
|
|
1583
|
+
|
|
1584
|
+
// Attach event listeners to the ref
|
|
1585
|
+
const setRef = useCallback(
|
|
1586
|
+
(element: null | T) => {
|
|
1587
|
+
if (ref.current) {
|
|
1588
|
+
ref.current.removeEventListener("mouseenter", handleMouseEnter);
|
|
1589
|
+
ref.current.removeEventListener("mouseleave", handleMouseLeave);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
if (element) {
|
|
1593
|
+
element.addEventListener("mouseenter", handleMouseEnter);
|
|
1594
|
+
element.addEventListener("mouseleave", handleMouseLeave);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
ref.current = element;
|
|
1598
|
+
},
|
|
1599
|
+
[handleMouseEnter, handleMouseLeave],
|
|
1600
|
+
);
|
|
1601
|
+
|
|
1602
|
+
// Return a proxy ref that updates the internal ref
|
|
1603
|
+
return [
|
|
1604
|
+
isHovered,
|
|
1605
|
+
{
|
|
1606
|
+
get current() {
|
|
1607
|
+
return ref.current;
|
|
1608
|
+
},
|
|
1609
|
+
set current(element: null | T) {
|
|
1610
|
+
setRef(element);
|
|
1611
|
+
},
|
|
1612
|
+
} as React.RefObject<T>,
|
|
1613
|
+
];
|
|
1614
|
+
}
|
|
1615
|
+
`, useHover_test: `import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
1616
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
1617
|
+
|
|
1618
|
+
import { useHover } from "./useHover.js";
|
|
1619
|
+
|
|
1620
|
+
function TestComponent() {
|
|
1621
|
+
const [isHovered, ref] = useHover<HTMLDivElement>();
|
|
1622
|
+
|
|
1623
|
+
return (
|
|
1624
|
+
<div data-testid="hover-element" ref={ref}>
|
|
1625
|
+
{isHovered ? "Hovering" : "Not hovering"}
|
|
1626
|
+
</div>
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
describe("useHover", () => {
|
|
1631
|
+
afterEach(() => {
|
|
1632
|
+
cleanup();
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
it("should initialize with false", () => {
|
|
1636
|
+
render(<TestComponent />);
|
|
1637
|
+
const element = screen.getByTestId("hover-element");
|
|
1638
|
+
expect(element.textContent).toBe("Not hovering");
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
it("should set to true on mouseenter", () => {
|
|
1642
|
+
render(<TestComponent />);
|
|
1643
|
+
const element = screen.getByTestId("hover-element");
|
|
1644
|
+
|
|
1645
|
+
fireEvent.mouseEnter(element);
|
|
1646
|
+
expect(element.textContent).toBe("Hovering");
|
|
1647
|
+
});
|
|
1648
|
+
|
|
1649
|
+
it("should set to false on mouseleave", () => {
|
|
1650
|
+
render(<TestComponent />);
|
|
1651
|
+
const element = screen.getByTestId("hover-element");
|
|
1652
|
+
|
|
1653
|
+
fireEvent.mouseEnter(element);
|
|
1654
|
+
expect(element.textContent).toBe("Hovering");
|
|
1655
|
+
|
|
1656
|
+
fireEvent.mouseLeave(element);
|
|
1657
|
+
expect(element.textContent).toBe("Not hovering");
|
|
1658
|
+
});
|
|
1659
|
+
|
|
1660
|
+
it("should handle multiple enter/leave cycles", () => {
|
|
1661
|
+
render(<TestComponent />);
|
|
1662
|
+
const element = screen.getByTestId("hover-element");
|
|
1663
|
+
|
|
1664
|
+
fireEvent.mouseEnter(element);
|
|
1665
|
+
expect(element.textContent).toBe("Hovering");
|
|
1666
|
+
|
|
1667
|
+
fireEvent.mouseLeave(element);
|
|
1668
|
+
expect(element.textContent).toBe("Not hovering");
|
|
1669
|
+
|
|
1670
|
+
fireEvent.mouseEnter(element);
|
|
1671
|
+
expect(element.textContent).toBe("Hovering");
|
|
1672
|
+
});
|
|
1673
|
+
});
|
|
1674
|
+
`,
|
|
1675
|
+
useIntersectionObserver: `import { useEffect, useRef, useState } from "react";
|
|
1676
|
+
|
|
1677
|
+
export interface UseIntersectionObserverOptions {
|
|
1678
|
+
/** Whether to stop observing after the first intersection (default: false) */
|
|
1679
|
+
once?: boolean;
|
|
1680
|
+
/** The element used as the viewport for checking visibility (default: browser viewport) */
|
|
1681
|
+
root?: Element | null;
|
|
1682
|
+
/** Margin around the root element (e.g., "10px 20px 30px 40px") */
|
|
1683
|
+
rootMargin?: string;
|
|
1684
|
+
/** A number or array of numbers indicating at what percentage of visibility the callback should trigger */
|
|
1685
|
+
threshold?: number | number[];
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
export interface UseIntersectionObserverResult {
|
|
1689
|
+
/** The current intersection observer entry */
|
|
1690
|
+
entry: IntersectionObserverEntry | null;
|
|
1691
|
+
/** Whether the element is currently intersecting */
|
|
1692
|
+
isIntersecting: boolean;
|
|
1693
|
+
/** Ref to attach to the element to observe */
|
|
1694
|
+
ref: React.RefObject<HTMLElement | null>;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
/**
|
|
1698
|
+
* Track the visibility of a DOM element within the viewport using IntersectionObserver.
|
|
1699
|
+
*
|
|
1700
|
+
* @param options - IntersectionObserver configuration options
|
|
1701
|
+
* @returns Object containing ref, entry, and isIntersecting state
|
|
1702
|
+
*
|
|
1703
|
+
* @example
|
|
1704
|
+
* // Basic usage - lazy load an image
|
|
1705
|
+
* const { ref, isIntersecting } = useIntersectionObserver();
|
|
1706
|
+
*
|
|
1707
|
+
* return (
|
|
1708
|
+
* <div ref={ref}>
|
|
1709
|
+
* {isIntersecting && <img src="large-image.jpg" />}
|
|
1710
|
+
* </div>
|
|
1711
|
+
* );
|
|
1712
|
+
*
|
|
1713
|
+
* @example
|
|
1714
|
+
* // Infinite scroll with threshold
|
|
1715
|
+
* const { ref, isIntersecting } = useIntersectionObserver({
|
|
1716
|
+
* threshold: 0.5,
|
|
1717
|
+
* rootMargin: '100px',
|
|
1718
|
+
* });
|
|
1719
|
+
*
|
|
1720
|
+
* useEffect(() => {
|
|
1721
|
+
* if (isIntersecting) loadMoreItems();
|
|
1722
|
+
* }, [isIntersecting]);
|
|
1723
|
+
*/
|
|
1724
|
+
export function useIntersectionObserver(
|
|
1725
|
+
options: UseIntersectionObserverOptions = {},
|
|
1726
|
+
): UseIntersectionObserverResult {
|
|
1727
|
+
const {
|
|
1728
|
+
once = false,
|
|
1729
|
+
root = null,
|
|
1730
|
+
rootMargin = "0px",
|
|
1731
|
+
threshold = 0,
|
|
1732
|
+
} = options;
|
|
1733
|
+
|
|
1734
|
+
const ref = useRef<HTMLElement | null>(null);
|
|
1735
|
+
const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);
|
|
1736
|
+
const hasTriggered = useRef(false);
|
|
1737
|
+
|
|
1738
|
+
useEffect(() => {
|
|
1739
|
+
const element = ref.current;
|
|
1740
|
+
|
|
1741
|
+
if (!element || (once && hasTriggered.current)) {
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
const observer = new IntersectionObserver(
|
|
1750
|
+
([observerEntry]) => {
|
|
1751
|
+
if (!observerEntry) {
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
setEntry(observerEntry);
|
|
1756
|
+
|
|
1757
|
+
if (once && observerEntry.isIntersecting) {
|
|
1758
|
+
hasTriggered.current = true;
|
|
1759
|
+
observer.disconnect();
|
|
1760
|
+
}
|
|
1761
|
+
},
|
|
1762
|
+
{ root, rootMargin, threshold },
|
|
1763
|
+
);
|
|
1764
|
+
|
|
1765
|
+
observer.observe(element);
|
|
1766
|
+
|
|
1767
|
+
return () => {
|
|
1768
|
+
observer.disconnect();
|
|
1769
|
+
};
|
|
1770
|
+
}, [root, rootMargin, threshold, once]);
|
|
1771
|
+
|
|
1772
|
+
return {
|
|
1773
|
+
entry,
|
|
1774
|
+
isIntersecting: entry?.isIntersecting ?? false,
|
|
1775
|
+
ref,
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
`, useIntersectionObserver_test: `import { cleanup, render, waitFor } from "@testing-library/react";
|
|
1779
|
+
import React from "react";
|
|
1780
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
1781
|
+
|
|
1782
|
+
import { useIntersectionObserver } from "./useIntersectionObserver.js";
|
|
1783
|
+
|
|
1784
|
+
describe("useIntersectionObserver", () => {
|
|
1785
|
+
let mockObserve: ReturnType<typeof vi.fn>;
|
|
1786
|
+
let mockDisconnect: ReturnType<typeof vi.fn>;
|
|
1787
|
+
let mockCallback: (entries: IntersectionObserverEntry[]) => void;
|
|
1788
|
+
let mockConstructorOptions: IntersectionObserverInit | undefined;
|
|
1789
|
+
|
|
1790
|
+
beforeEach(() => {
|
|
1791
|
+
mockObserve = vi.fn();
|
|
1792
|
+
mockDisconnect = vi.fn();
|
|
1793
|
+
mockConstructorOptions = undefined;
|
|
1794
|
+
|
|
1795
|
+
vi.stubGlobal(
|
|
1796
|
+
"IntersectionObserver",
|
|
1797
|
+
class MockIntersectionObserver {
|
|
1798
|
+
disconnect = mockDisconnect;
|
|
1799
|
+
observe = mockObserve;
|
|
1800
|
+
unobserve = vi.fn();
|
|
1801
|
+
constructor(
|
|
1802
|
+
callback: (entries: IntersectionObserverEntry[]) => void,
|
|
1803
|
+
options?: IntersectionObserverInit,
|
|
1804
|
+
) {
|
|
1805
|
+
mockCallback = callback;
|
|
1806
|
+
mockConstructorOptions = options;
|
|
1807
|
+
}
|
|
1808
|
+
},
|
|
1809
|
+
);
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
afterEach(() => {
|
|
1813
|
+
cleanup();
|
|
1814
|
+
vi.unstubAllGlobals();
|
|
1815
|
+
});
|
|
1816
|
+
|
|
1817
|
+
it("should return ref, entry, and isIntersecting", () => {
|
|
1818
|
+
let hookResult: ReturnType<typeof useIntersectionObserver> | undefined;
|
|
1819
|
+
|
|
1820
|
+
const Component = () => {
|
|
1821
|
+
hookResult = useIntersectionObserver();
|
|
1822
|
+
return (
|
|
1823
|
+
<div ref={hookResult.ref as React.RefObject<HTMLDivElement>}>Test</div>
|
|
1824
|
+
);
|
|
1825
|
+
};
|
|
1826
|
+
|
|
1827
|
+
render(<Component />);
|
|
1828
|
+
|
|
1829
|
+
expect(hookResult?.ref).toBeDefined();
|
|
1830
|
+
expect(hookResult?.entry).toBeNull();
|
|
1831
|
+
expect(hookResult?.isIntersecting).toBe(false);
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
it("should observe the element", () => {
|
|
1835
|
+
const Component = () => {
|
|
1836
|
+
const { ref } = useIntersectionObserver();
|
|
1837
|
+
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>;
|
|
1838
|
+
};
|
|
1839
|
+
|
|
1840
|
+
render(<Component />);
|
|
1841
|
+
|
|
1842
|
+
expect(mockObserve).toHaveBeenCalled();
|
|
1843
|
+
});
|
|
1844
|
+
|
|
1845
|
+
it("should update isIntersecting when element becomes visible", async () => {
|
|
1846
|
+
let hookResult: ReturnType<typeof useIntersectionObserver>;
|
|
1847
|
+
|
|
1848
|
+
const Component = () => {
|
|
1849
|
+
hookResult = useIntersectionObserver();
|
|
1850
|
+
return (
|
|
1851
|
+
<div ref={hookResult.ref as React.RefObject<HTMLDivElement>}>Test</div>
|
|
1852
|
+
);
|
|
1853
|
+
};
|
|
1854
|
+
|
|
1855
|
+
render(<Component />);
|
|
1856
|
+
|
|
1857
|
+
const mockEntry = {
|
|
1858
|
+
boundingClientRect: {} as DOMRectReadOnly,
|
|
1859
|
+
intersectionRatio: 1,
|
|
1860
|
+
intersectionRect: {} as DOMRectReadOnly,
|
|
1861
|
+
isIntersecting: true,
|
|
1862
|
+
rootBounds: null,
|
|
1863
|
+
target: document.createElement("div"),
|
|
1864
|
+
time: Date.now(),
|
|
1865
|
+
} as IntersectionObserverEntry;
|
|
1866
|
+
|
|
1867
|
+
mockCallback([mockEntry]);
|
|
1868
|
+
|
|
1869
|
+
await waitFor(() => {
|
|
1870
|
+
expect(hookResult.isIntersecting).toBe(true);
|
|
1871
|
+
expect(hookResult.entry).toBe(mockEntry);
|
|
1872
|
+
});
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
it("should disconnect on unmount", () => {
|
|
1876
|
+
const Component = () => {
|
|
1877
|
+
const { ref } = useIntersectionObserver();
|
|
1878
|
+
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>;
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
const { unmount } = render(<Component />);
|
|
1882
|
+
unmount();
|
|
1883
|
+
|
|
1884
|
+
expect(mockDisconnect).toHaveBeenCalled();
|
|
1885
|
+
});
|
|
1886
|
+
|
|
1887
|
+
it("should pass options to IntersectionObserver", () => {
|
|
1888
|
+
const options = {
|
|
1889
|
+
root: null,
|
|
1890
|
+
rootMargin: "10px",
|
|
1891
|
+
threshold: 0.5,
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1894
|
+
const Component = () => {
|
|
1895
|
+
const { ref } = useIntersectionObserver(options);
|
|
1896
|
+
return <div ref={ref as React.RefObject<HTMLDivElement>}>Test</div>;
|
|
1897
|
+
};
|
|
1898
|
+
|
|
1899
|
+
render(<Component />);
|
|
1900
|
+
|
|
1901
|
+
expect(mockConstructorOptions).toEqual({
|
|
1902
|
+
root: null,
|
|
1903
|
+
rootMargin: "10px",
|
|
1904
|
+
threshold: 0.5,
|
|
1905
|
+
});
|
|
1906
|
+
});
|
|
1907
|
+
|
|
1908
|
+
it("should disconnect after first intersection when once is true", async () => {
|
|
1909
|
+
let hookResult: ReturnType<typeof useIntersectionObserver>;
|
|
1910
|
+
|
|
1911
|
+
const Component = () => {
|
|
1912
|
+
hookResult = useIntersectionObserver({ once: true });
|
|
1913
|
+
return (
|
|
1914
|
+
<div ref={hookResult.ref as React.RefObject<HTMLDivElement>}>Test</div>
|
|
1915
|
+
);
|
|
1916
|
+
};
|
|
1917
|
+
|
|
1918
|
+
render(<Component />);
|
|
1919
|
+
|
|
1920
|
+
const mockEntry = {
|
|
1921
|
+
isIntersecting: true,
|
|
1922
|
+
} as IntersectionObserverEntry;
|
|
1923
|
+
|
|
1924
|
+
mockCallback([mockEntry]);
|
|
1925
|
+
|
|
1926
|
+
await waitFor(() => {
|
|
1927
|
+
expect(hookResult.isIntersecting).toBe(true);
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
expect(mockDisconnect).toHaveBeenCalled();
|
|
1931
|
+
});
|
|
1932
|
+
|
|
1933
|
+
it("should not disconnect when once is true but not intersecting", async () => {
|
|
1934
|
+
let hookResult: ReturnType<typeof useIntersectionObserver>;
|
|
1935
|
+
|
|
1936
|
+
const Component = () => {
|
|
1937
|
+
hookResult = useIntersectionObserver({ once: true });
|
|
1938
|
+
return (
|
|
1939
|
+
<div ref={hookResult.ref as React.RefObject<HTMLDivElement>}>Test</div>
|
|
1940
|
+
);
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
render(<Component />);
|
|
1944
|
+
|
|
1945
|
+
const mockEntry = {
|
|
1946
|
+
isIntersecting: false,
|
|
1947
|
+
} as IntersectionObserverEntry;
|
|
1948
|
+
|
|
1949
|
+
mockCallback([mockEntry]);
|
|
1950
|
+
|
|
1951
|
+
await waitFor(() => {
|
|
1952
|
+
expect(hookResult.isIntersecting).toBe(false);
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
// Should not disconnect because it hasn't intersected yet
|
|
1956
|
+
expect(mockDisconnect).not.toHaveBeenCalled();
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
it("should handle no element ref gracefully", () => {
|
|
1960
|
+
const Component = () => {
|
|
1961
|
+
const { isIntersecting } = useIntersectionObserver();
|
|
1962
|
+
// Don't attach the ref to any element
|
|
1963
|
+
return (
|
|
1964
|
+
<div>No ref attached, isIntersecting: {String(isIntersecting)}</div>
|
|
1965
|
+
);
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
render(<Component />);
|
|
1969
|
+
|
|
1970
|
+
// Should not observe anything since ref is not attached
|
|
1971
|
+
expect(mockObserve).not.toHaveBeenCalled();
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1974
|
+
it("should handle IntersectionObserver being undefined (SSR)", () => {
|
|
1975
|
+
vi.stubGlobal("IntersectionObserver", undefined);
|
|
1976
|
+
|
|
1977
|
+
const Component = () => {
|
|
1978
|
+
const { isIntersecting, ref } = useIntersectionObserver();
|
|
1979
|
+
return (
|
|
1980
|
+
<div ref={ref as React.RefObject<HTMLDivElement>}>
|
|
1981
|
+
SSR: {String(isIntersecting)}
|
|
1982
|
+
</div>
|
|
1983
|
+
);
|
|
1984
|
+
};
|
|
1985
|
+
|
|
1986
|
+
// Should not throw
|
|
1987
|
+
render(<Component />);
|
|
1988
|
+
expect(mockObserve).not.toHaveBeenCalled();
|
|
1989
|
+
});
|
|
1990
|
+
});
|
|
1175
1991
|
`,
|
|
1176
1992
|
useInterval: `import { useEffect } from "react";
|
|
1177
1993
|
|
|
@@ -1928,6 +2744,325 @@ describe("useLockBodyScroll", () => {
|
|
|
1928
2744
|
expect(document.body.style.overflow).toBe("scroll");
|
|
1929
2745
|
});
|
|
1930
2746
|
});
|
|
2747
|
+
`,
|
|
2748
|
+
useMeasure: `import { useCallback, useEffect, useRef, useState } from "react";
|
|
2749
|
+
|
|
2750
|
+
export interface UseMeasureRect {
|
|
2751
|
+
/** Bottom position relative to viewport */
|
|
2752
|
+
bottom: number;
|
|
2753
|
+
/** Element height */
|
|
2754
|
+
height: number;
|
|
2755
|
+
/** Left position relative to viewport */
|
|
2756
|
+
left: number;
|
|
2757
|
+
/** Right position relative to viewport */
|
|
2758
|
+
right: number;
|
|
2759
|
+
/** Top position relative to viewport */
|
|
2760
|
+
top: number;
|
|
2761
|
+
/** Element width */
|
|
2762
|
+
width: number;
|
|
2763
|
+
/** X position (same as left) */
|
|
2764
|
+
x: number;
|
|
2765
|
+
/** Y position (same as top) */
|
|
2766
|
+
y: number;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
export interface UseMeasureResult<T extends Element> {
|
|
2770
|
+
/** The measured dimensions of the element */
|
|
2771
|
+
rect: UseMeasureRect;
|
|
2772
|
+
/** Ref to attach to the element to measure */
|
|
2773
|
+
ref: React.RefCallback<T>;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
const defaultRect: UseMeasureRect = {
|
|
2777
|
+
bottom: 0,
|
|
2778
|
+
height: 0,
|
|
2779
|
+
left: 0,
|
|
2780
|
+
right: 0,
|
|
2781
|
+
top: 0,
|
|
2782
|
+
width: 0,
|
|
2783
|
+
x: 0,
|
|
2784
|
+
y: 0,
|
|
2785
|
+
};
|
|
2786
|
+
|
|
2787
|
+
/**
|
|
2788
|
+
* Measure the dimensions of a DOM element using ResizeObserver.
|
|
2789
|
+
*
|
|
2790
|
+
* @returns Object containing ref callback and rect measurements
|
|
2791
|
+
*
|
|
2792
|
+
* @example
|
|
2793
|
+
* const { ref, rect } = useMeasure<HTMLDivElement>();
|
|
2794
|
+
*
|
|
2795
|
+
* return (
|
|
2796
|
+
* <div ref={ref}>
|
|
2797
|
+
* <p>Width: {rect.width}px</p>
|
|
2798
|
+
* <p>Height: {rect.height}px</p>
|
|
2799
|
+
* </div>
|
|
2800
|
+
* );
|
|
2801
|
+
*
|
|
2802
|
+
* @example
|
|
2803
|
+
* // Responsive component
|
|
2804
|
+
* const { ref, rect } = useMeasure<HTMLDivElement>();
|
|
2805
|
+
* const isCompact = rect.width < 400;
|
|
2806
|
+
*
|
|
2807
|
+
* return (
|
|
2808
|
+
* <nav ref={ref} className={isCompact ? 'compact' : 'full'}>
|
|
2809
|
+
* {isCompact ? <MobileMenu /> : <DesktopMenu />}
|
|
2810
|
+
* </nav>
|
|
2811
|
+
* );
|
|
2812
|
+
*/
|
|
2813
|
+
export function useMeasure<
|
|
2814
|
+
T extends Element = HTMLDivElement,
|
|
2815
|
+
>(): UseMeasureResult<T> {
|
|
2816
|
+
const [element, setElement] = useState<null | T>(null);
|
|
2817
|
+
const [rect, setRect] = useState<UseMeasureRect>(defaultRect);
|
|
2818
|
+
|
|
2819
|
+
const observerRef = useRef<null | ResizeObserver>(null);
|
|
2820
|
+
|
|
2821
|
+
const ref = useCallback((node: null | T) => {
|
|
2822
|
+
setElement(node);
|
|
2823
|
+
}, []);
|
|
2824
|
+
|
|
2825
|
+
useEffect(() => {
|
|
2826
|
+
if (!element) {
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
if (typeof ResizeObserver === "undefined") {
|
|
2831
|
+
// Fallback: get initial dimensions without observing changes
|
|
2832
|
+
const boundingRect = element.getBoundingClientRect();
|
|
2833
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
2834
|
+
setRect({
|
|
2835
|
+
bottom: boundingRect.bottom,
|
|
2836
|
+
height: boundingRect.height,
|
|
2837
|
+
left: boundingRect.left,
|
|
2838
|
+
right: boundingRect.right,
|
|
2839
|
+
top: boundingRect.top,
|
|
2840
|
+
width: boundingRect.width,
|
|
2841
|
+
x: boundingRect.x,
|
|
2842
|
+
y: boundingRect.y,
|
|
2843
|
+
});
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
observerRef.current = new ResizeObserver(([entry]) => {
|
|
2848
|
+
if (entry) {
|
|
2849
|
+
const boundingRect = entry.target.getBoundingClientRect();
|
|
2850
|
+
setRect({
|
|
2851
|
+
bottom: boundingRect.bottom,
|
|
2852
|
+
height: boundingRect.height,
|
|
2853
|
+
left: boundingRect.left,
|
|
2854
|
+
right: boundingRect.right,
|
|
2855
|
+
top: boundingRect.top,
|
|
2856
|
+
width: boundingRect.width,
|
|
2857
|
+
x: boundingRect.x,
|
|
2858
|
+
y: boundingRect.y,
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
});
|
|
2862
|
+
|
|
2863
|
+
observerRef.current.observe(element);
|
|
2864
|
+
|
|
2865
|
+
return () => {
|
|
2866
|
+
observerRef.current?.disconnect();
|
|
2867
|
+
};
|
|
2868
|
+
}, [element]);
|
|
2869
|
+
|
|
2870
|
+
return { rect, ref };
|
|
2871
|
+
}
|
|
2872
|
+
`, useMeasure_test: `import { cleanup, render, waitFor } from "@testing-library/react";
|
|
2873
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2874
|
+
|
|
2875
|
+
import { useMeasure } from "./useMeasure.js";
|
|
2876
|
+
|
|
2877
|
+
describe("useMeasure", () => {
|
|
2878
|
+
let mockObserve: ReturnType<typeof vi.fn>;
|
|
2879
|
+
let mockDisconnect: ReturnType<typeof vi.fn>;
|
|
2880
|
+
let mockCallback: (entries: ResizeObserverEntry[]) => void;
|
|
2881
|
+
|
|
2882
|
+
const mockBoundingRect = {
|
|
2883
|
+
bottom: 200,
|
|
2884
|
+
height: 100,
|
|
2885
|
+
left: 50,
|
|
2886
|
+
right: 250,
|
|
2887
|
+
top: 100,
|
|
2888
|
+
width: 200,
|
|
2889
|
+
x: 50,
|
|
2890
|
+
y: 100,
|
|
2891
|
+
};
|
|
2892
|
+
|
|
2893
|
+
beforeEach(() => {
|
|
2894
|
+
mockObserve = vi.fn();
|
|
2895
|
+
mockDisconnect = vi.fn();
|
|
2896
|
+
|
|
2897
|
+
vi.stubGlobal(
|
|
2898
|
+
"ResizeObserver",
|
|
2899
|
+
class MockResizeObserver {
|
|
2900
|
+
disconnect = mockDisconnect;
|
|
2901
|
+
observe = mockObserve;
|
|
2902
|
+
unobserve = vi.fn();
|
|
2903
|
+
constructor(callback: (entries: ResizeObserverEntry[]) => void) {
|
|
2904
|
+
mockCallback = callback;
|
|
2905
|
+
}
|
|
2906
|
+
},
|
|
2907
|
+
);
|
|
2908
|
+
});
|
|
2909
|
+
|
|
2910
|
+
afterEach(() => {
|
|
2911
|
+
cleanup();
|
|
2912
|
+
vi.unstubAllGlobals();
|
|
2913
|
+
});
|
|
2914
|
+
|
|
2915
|
+
it("should return ref and initial rect with zeros", () => {
|
|
2916
|
+
let result: ReturnType<typeof useMeasure> | undefined;
|
|
2917
|
+
|
|
2918
|
+
const Component = () => {
|
|
2919
|
+
result = useMeasure();
|
|
2920
|
+
return <div ref={result.ref}>Test</div>;
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
render(<Component />);
|
|
2924
|
+
|
|
2925
|
+
expect(typeof result?.ref).toBe("function");
|
|
2926
|
+
expect(result?.rect).toEqual({
|
|
2927
|
+
bottom: 0,
|
|
2928
|
+
height: 0,
|
|
2929
|
+
left: 0,
|
|
2930
|
+
right: 0,
|
|
2931
|
+
top: 0,
|
|
2932
|
+
width: 0,
|
|
2933
|
+
x: 0,
|
|
2934
|
+
y: 0,
|
|
2935
|
+
});
|
|
2936
|
+
});
|
|
2937
|
+
|
|
2938
|
+
it("should observe the element", () => {
|
|
2939
|
+
const Component = () => {
|
|
2940
|
+
const { ref } = useMeasure();
|
|
2941
|
+
return <div ref={ref}>Test</div>;
|
|
2942
|
+
};
|
|
2943
|
+
|
|
2944
|
+
render(<Component />);
|
|
2945
|
+
|
|
2946
|
+
expect(mockObserve).toHaveBeenCalled();
|
|
2947
|
+
});
|
|
2948
|
+
|
|
2949
|
+
it("should update rect when element is resized", async () => {
|
|
2950
|
+
let result: ReturnType<typeof useMeasure> | undefined;
|
|
2951
|
+
|
|
2952
|
+
const Component = () => {
|
|
2953
|
+
result = useMeasure();
|
|
2954
|
+
return <div ref={result.ref}>Test</div>;
|
|
2955
|
+
};
|
|
2956
|
+
|
|
2957
|
+
render(<Component />);
|
|
2958
|
+
|
|
2959
|
+
const mockEntry = {
|
|
2960
|
+
target: {
|
|
2961
|
+
getBoundingClientRect: () => mockBoundingRect,
|
|
2962
|
+
},
|
|
2963
|
+
} as unknown as ResizeObserverEntry;
|
|
2964
|
+
|
|
2965
|
+
mockCallback([mockEntry]);
|
|
2966
|
+
|
|
2967
|
+
await waitFor(() => {
|
|
2968
|
+
expect(result?.rect).toEqual(mockBoundingRect);
|
|
2969
|
+
});
|
|
2970
|
+
});
|
|
2971
|
+
|
|
2972
|
+
it("should disconnect on unmount", () => {
|
|
2973
|
+
const Component = () => {
|
|
2974
|
+
const { ref } = useMeasure();
|
|
2975
|
+
return <div ref={ref}>Test</div>;
|
|
2976
|
+
};
|
|
2977
|
+
|
|
2978
|
+
const { unmount } = render(<Component />);
|
|
2979
|
+
unmount();
|
|
2980
|
+
|
|
2981
|
+
expect(mockDisconnect).toHaveBeenCalled();
|
|
2982
|
+
});
|
|
2983
|
+
|
|
2984
|
+
it("should handle null ref", () => {
|
|
2985
|
+
let result: ReturnType<typeof useMeasure>;
|
|
2986
|
+
|
|
2987
|
+
const Component = ({ show }: { show: boolean }) => {
|
|
2988
|
+
result = useMeasure();
|
|
2989
|
+
return show ? <div ref={result.ref}>Test</div> : null;
|
|
2990
|
+
};
|
|
2991
|
+
|
|
2992
|
+
const { rerender } = render(<Component show={true} />);
|
|
2993
|
+
|
|
2994
|
+
expect(mockObserve).toHaveBeenCalledTimes(1);
|
|
2995
|
+
|
|
2996
|
+
rerender(<Component show={false} />);
|
|
2997
|
+
|
|
2998
|
+
// Observer should be disconnected when element is removed
|
|
2999
|
+
expect(mockDisconnect).toHaveBeenCalled();
|
|
3000
|
+
});
|
|
3001
|
+
|
|
3002
|
+
it("should re-observe when element changes", async () => {
|
|
3003
|
+
let result: ReturnType<typeof useMeasure>;
|
|
3004
|
+
|
|
3005
|
+
const Component = ({ id }: { id: string }) => {
|
|
3006
|
+
result = useMeasure();
|
|
3007
|
+
return (
|
|
3008
|
+
<div key={id} ref={result.ref}>
|
|
3009
|
+
{id}
|
|
3010
|
+
</div>
|
|
3011
|
+
);
|
|
3012
|
+
};
|
|
3013
|
+
|
|
3014
|
+
const { rerender } = render(<Component id="first" />);
|
|
3015
|
+
|
|
3016
|
+
expect(mockObserve).toHaveBeenCalledTimes(1);
|
|
3017
|
+
|
|
3018
|
+
rerender(<Component id="second" />);
|
|
3019
|
+
|
|
3020
|
+
await waitFor(() => {
|
|
3021
|
+
expect(mockObserve).toHaveBeenCalledTimes(2);
|
|
3022
|
+
});
|
|
3023
|
+
});
|
|
3024
|
+
|
|
3025
|
+
it("should fallback to getBoundingClientRect when ResizeObserver is undefined", () => {
|
|
3026
|
+
vi.stubGlobal("ResizeObserver", undefined);
|
|
3027
|
+
|
|
3028
|
+
const mockRect = {
|
|
3029
|
+
bottom: 100,
|
|
3030
|
+
height: 50,
|
|
3031
|
+
left: 10,
|
|
3032
|
+
right: 110,
|
|
3033
|
+
top: 50,
|
|
3034
|
+
width: 100,
|
|
3035
|
+
x: 10,
|
|
3036
|
+
y: 50,
|
|
3037
|
+
};
|
|
3038
|
+
|
|
3039
|
+
let result: ReturnType<typeof useMeasure> | undefined;
|
|
3040
|
+
|
|
3041
|
+
const Component = () => {
|
|
3042
|
+
result = useMeasure();
|
|
3043
|
+
return (
|
|
3044
|
+
<div
|
|
3045
|
+
ref={(node) => {
|
|
3046
|
+
if (node) {
|
|
3047
|
+
vi.spyOn(node, "getBoundingClientRect").mockReturnValue(
|
|
3048
|
+
mockRect as DOMRect,
|
|
3049
|
+
);
|
|
3050
|
+
}
|
|
3051
|
+
result?.ref(node);
|
|
3052
|
+
}}
|
|
3053
|
+
>
|
|
3054
|
+
Test
|
|
3055
|
+
</div>
|
|
3056
|
+
);
|
|
3057
|
+
};
|
|
3058
|
+
|
|
3059
|
+
render(<Component />);
|
|
3060
|
+
|
|
3061
|
+
// Should have initial rect from getBoundingClientRect fallback
|
|
3062
|
+
expect(result?.rect.width).toBe(100);
|
|
3063
|
+
expect(result?.rect.height).toBe(50);
|
|
3064
|
+
});
|
|
3065
|
+
});
|
|
1931
3066
|
`,
|
|
1932
3067
|
useMedia: `import { useEffect, useState } from "react";
|
|
1933
3068
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-templates.js","sourceRoot":"","sources":["../../src/utils/hook-templates.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hook-templates.js","sourceRoot":"","sources":["../../src/utils/hook-templates.ts"],"names":[],"mappings":"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"}
|