Skip to content

Commit 77612b5

Browse files
committed
feat: Added use-resize-observer.
1 parent 25576d0 commit 77612b5

File tree

4 files changed

+106
-3
lines changed

4 files changed

+106
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Based on the [@mantine/hooks](https://github.com/mantinedev/mantine/tree/master/
9999
- [ ] use-previous
100100
- [ ] use-queue
101101
- [ ] use-reduced-motion
102-
- [ ] use-resize-observer
102+
- [x] use-resize-observer
103103
- [ ] use-scroll-into-view
104104
- [ ] use-session-storage
105105
- [ ] use-set-state

dev/App.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
import type { Component } from 'solid-js';
1+
import { createEffect, type Component } from 'solid-js';
22
import './app.css';
33

44
// Hooks
55
import {
66
useClickOutside,
7+
useElementSize,
78
useHotkeys,
89
useHover,
910
useIdle,
1011
useNetwork,
1112
useOs,
13+
useResizeObserver,
1214
useToggle,
1315
} from '../src';
16+
import { unwrap } from 'solid-js/store';
1417

1518
const App: Component = () => {
1619
// let ref = useClickOutside(() => {
@@ -30,8 +33,10 @@ const App: Component = () => {
3033

3134
const [currentOption, toggle] = useToggle(['light', 'dark', 'system']);
3235

36+
const { ref: elementSizeRef, width, height } = useElementSize();
37+
3338
return (
34-
<div class="">
39+
<div class="flex flex-col gap-y-5 items-start">
3540
{/* <p class="bg-green-500" ref={ref}>
3641
Hello world!
3742
</p> */}
@@ -45,6 +50,10 @@ const App: Component = () => {
4550
<p>networkStatus: {JSON.stringify(networkStatus())}</p>
4651

4752
<button onClick={() => toggle()}>Current Toggled: {JSON.stringify(currentOption())}</button>
53+
54+
<textarea ref={elementSizeRef} class="resize">
55+
{width()} | {height()}
56+
</textarea>
4857
</div>
4958
);
5059
};

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export * from './use-idle/use-idle';
55
export * from './use-mounted/use-mounted';
66
export * from './use-network/use-network';
77
export * from './use-os/use-os';
8+
export * from './use-resize-observer/use-resize-observer';
89
export * from './use-toggle/use-toggle';
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
2+
import { createStore, reconcile } from 'solid-js/store';
3+
4+
type ObserverRect = Omit<DOMRectReadOnly, 'toJSON'>;
5+
6+
const defaultState: ObserverRect = {
7+
x: 0,
8+
y: 0,
9+
width: 0,
10+
height: 0,
11+
top: 0,
12+
left: 0,
13+
bottom: 0,
14+
right: 0,
15+
};
16+
17+
/**
18+
* A hook that returns a resize observer.
19+
*
20+
* @example
21+
* ```ts
22+
* const [ref, rectStore] = useResizeObserver();
23+
*
24+
* return (<div ref={ref}>{rectStore.width}</div>);
25+
*/
26+
export function useResizeObserver<T extends HTMLElement = any>(options?: ResizeObserverOptions) {
27+
let frameID = 0;
28+
const [ref, setRef] = createSignal<T | null>(null);
29+
30+
/**
31+
* SolidJS stores always need a deeper object for the 'setter' and 'reconcile' to work,
32+
* hence why I wrapped it in { rect: ObserverRect }. When originally, it sohuld just be `ObserverRect`.
33+
*/
34+
const [rectStore, setRectStore] = createStore<{ rect: ObserverRect }>({ rect: defaultState });
35+
36+
const observer = createMemo(() => {
37+
if (typeof window === 'undefined') {
38+
return null;
39+
}
40+
41+
return new ResizeObserver((entries: any) => {
42+
const entry = entries[0];
43+
44+
if (entry) {
45+
cancelAnimationFrame(frameID);
46+
47+
frameID = requestAnimationFrame(() => {
48+
if (ref()) {
49+
setRectStore('rect', reconcile(entry.contentRect));
50+
}
51+
});
52+
}
53+
});
54+
});
55+
56+
createEffect(() => {
57+
if (ref()) {
58+
observer()?.observe(ref()!, options);
59+
}
60+
61+
onCleanup(() => {
62+
observer()?.disconnect();
63+
64+
if (frameID) {
65+
cancelAnimationFrame(frameID);
66+
}
67+
});
68+
});
69+
70+
return [setRef, rectStore] as const;
71+
}
72+
73+
/**
74+
* A hook that returns the current size of an element.
75+
*
76+
* Plus points on SolidJS for this: `width()` and `height()` only emit a change when it actually changed its value.
77+
* This is thanks to useResizeObserver using a `store` with `reconcile` under the hood.
78+
*
79+
* @example
80+
* ```ts
81+
* const { ref, width, height } = useElementSize();
82+
*
83+
* return (<div ref={ref}>{width()} | {height()}</div>);
84+
*/
85+
export function useElementSize<T extends HTMLElement = any>(options?: ResizeObserverOptions) {
86+
const [ref, rectStore] = useResizeObserver<T>(options);
87+
88+
return {
89+
ref,
90+
width: createMemo(() => rectStore.rect.width),
91+
height: createMemo(() => rectStore.rect.height),
92+
};
93+
}

0 commit comments

Comments
 (0)