Skip to content

Commit 383e206

Browse files
committed
feat: add source to RESIZE events
Fixes stipsan#53
1 parent 51c8510 commit 383e206

File tree

4 files changed

+63
-11
lines changed

4 files changed

+63
-11
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,10 @@ If the user reopens the sheet before it's done animating it'll trigger this even
191191
192192
#### RESIZE
193193
194-
Fires whenever there's been a window resize event, or if the header, footer or content have changed its height in such a way that the valid snap points have changed. In the future (#53) you'll be able to differentiate between what triggered the resize.
194+
Type: `{ source: 'window' | 'maxheightprop' | 'element }`
195+
196+
Fires whenever there's been a window resize event, or if the header, footer or content have changed its height in such a way that the valid snap points have changed.
197+
`source` tells you what caused the resize. If the resize comes from a `window.onresize` event it's set to `'window'`. `'maxheightprop'` is if the `maxHeight` prop is used, and is fired whenever it changes. And `'element'` is whenever the header, footer or content resize observers detect a change.
195198
196199
#### SNAP
197200

src/BottomSheet.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
defaultSnapProps,
3030
Props,
3131
RefHandles,
32+
ResizeSource,
3233
SnapPointProps,
3334
} from './types'
3435
import { debugging } from './utils'
@@ -99,6 +100,7 @@ export const BottomSheet = React.forwardRef<
99100

100101
// Keeps track of the current height, or the height transitioning to
101102
const heightRef = useRef(0)
103+
const resizeSourceRef = useRef<ResizeSource>()
102104

103105
const prefersReducedMotion = useReducedMotion()
104106

@@ -131,6 +133,7 @@ export const BottomSheet = React.forwardRef<
131133
lastSnapRef,
132134
ready,
133135
registerReady,
136+
resizeSourceRef,
134137
})
135138

136139
// Setup refs that are used in cases where full control is needed over when a side effect is executed
@@ -196,7 +199,11 @@ export const BottomSheet = React.forwardRef<
196199
[]
197200
),
198201
onResizeCancel: useCallback(
199-
() => onSpringCancelRef.current?.({ type: 'RESIZE' }),
202+
() =>
203+
onSpringCancelRef.current?.({
204+
type: 'RESIZE',
205+
source: resizeSourceRef.current,
206+
}),
200207
[]
201208
),
202209
onOpenEnd: useCallback(
@@ -212,7 +219,11 @@ export const BottomSheet = React.forwardRef<
212219
[]
213220
),
214221
onResizeEnd: useCallback(
215-
() => onSpringEndRef.current?.({ type: 'RESIZE' }),
222+
() =>
223+
onSpringEndRef.current?.({
224+
type: 'RESIZE',
225+
source: resizeSourceRef.current,
226+
}),
216227
[]
217228
),
218229
},
@@ -235,7 +246,11 @@ export const BottomSheet = React.forwardRef<
235246
[]
236247
),
237248
onResizeStart: useCallback(
238-
async () => onSpringStartRef.current?.({ type: 'RESIZE' }),
249+
async () =>
250+
onSpringStartRef.current?.({
251+
type: 'RESIZE',
252+
source: resizeSourceRef.current,
253+
}),
239254
[]
240255
),
241256
onSnapEnd: useCallback(
@@ -255,7 +270,11 @@ export const BottomSheet = React.forwardRef<
255270
[]
256271
),
257272
onResizeEnd: useCallback(
258-
async () => onSpringEndRef.current?.({ type: 'RESIZE' }),
273+
async () =>
274+
onSpringEndRef.current?.({
275+
type: 'RESIZE',
276+
source: resizeSourceRef.current,
277+
}),
259278
[]
260279
),
261280
renderVisuallyHidden: useCallback(

src/hooks/useSnapPoints.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import React, {
77
useState,
88
} from 'react'
99
import { ResizeObserver, ResizeObserverEntry } from '@juggle/resize-observer'
10-
import type { defaultSnapProps, snapPoints } from '../types'
10+
import type { defaultSnapProps, ResizeSource, snapPoints } from '../types'
1111
import { processSnapPoints, roundAndCheckForNaN } from '../utils'
1212
import { useReady } from './useReady'
1313
import { ResizeObserverOptions } from '@juggle/resize-observer/lib/ResizeObserverOptions'
@@ -24,6 +24,7 @@ export function useSnapPoints({
2424
lastSnapRef,
2525
ready,
2626
registerReady,
27+
resizeSourceRef,
2728
}: {
2829
contentRef: React.RefObject<Element>
2930
controlledMaxHeight?: number
@@ -36,6 +37,7 @@ export function useSnapPoints({
3637
lastSnapRef: React.RefObject<number>
3738
ready: boolean
3839
registerReady: ReturnType<typeof useReady>['registerReady']
40+
resizeSourceRef: React.MutableRefObject<ResizeSource>
3941
}) {
4042
const { maxHeight, minHeight, headerHeight, footerHeight } = useDimensions({
4143
contentRef: contentRef,
@@ -45,6 +47,7 @@ export function useSnapPoints({
4547
headerEnabled,
4648
headerRef,
4749
registerReady,
50+
resizeSourceRef,
4851
})
4952

5053
const { snapPoints, minSnap, maxSnap } = processSnapPoints(
@@ -100,6 +103,7 @@ function useDimensions({
100103
headerEnabled,
101104
headerRef,
102105
registerReady,
106+
resizeSourceRef,
103107
}: {
104108
contentRef: React.RefObject<Element>
105109
controlledMaxHeight?: number
@@ -108,24 +112,32 @@ function useDimensions({
108112
headerEnabled: boolean
109113
headerRef: React.RefObject<Element>
110114
registerReady: ReturnType<typeof useReady>['registerReady']
115+
resizeSourceRef: React.MutableRefObject<ResizeSource>
111116
}) {
112117
const setReady = useMemo(() => registerReady('contentHeight'), [
113118
registerReady,
114119
])
115-
const maxHeight = useMaxHeight(controlledMaxHeight, registerReady)
120+
const maxHeight = useMaxHeight(
121+
controlledMaxHeight,
122+
registerReady,
123+
resizeSourceRef
124+
)
116125

117126
// @TODO probably better to forward props instead of checking refs to decide if it's enabled
118127
const headerHeight = useElementSizeObserver(headerRef, {
119128
label: 'headerHeight',
120129
enabled: headerEnabled,
130+
resizeSourceRef,
121131
})
122132
const contentHeight = useElementSizeObserver(contentRef, {
123133
label: 'contentHeight',
124134
enabled: true,
135+
resizeSourceRef,
125136
})
126137
const footerHeight = useElementSizeObserver(footerRef, {
127138
label: 'footerHeight',
128139
enabled: footerEnabled,
140+
resizeSourceRef,
129141
})
130142
const minHeight =
131143
Math.min(maxHeight - headerHeight - footerHeight, contentHeight) +
@@ -161,7 +173,15 @@ const observerOptions: ResizeObserverOptions = {
161173
*/
162174
function useElementSizeObserver(
163175
ref: React.RefObject<Element>,
164-
{ label, enabled }: { label: string; enabled: boolean }
176+
{
177+
label,
178+
enabled,
179+
resizeSourceRef,
180+
}: {
181+
label: string
182+
enabled: boolean
183+
resizeSourceRef: React.MutableRefObject<ResizeSource>
184+
}
165185
): number {
166186
let [size, setSize] = useState(0)
167187

@@ -170,6 +190,7 @@ function useElementSizeObserver(
170190
const handleResize = useCallback((entries: ResizeObserverEntry[]) => {
171191
// we only observe one element, so accessing the first entry here is fine
172192
setSize(entries[0].borderBoxSize[0].blockSize)
193+
resizeSourceRef.current = 'element'
173194
}, [])
174195

175196
useEffect(() => {
@@ -191,7 +212,8 @@ function useElementSizeObserver(
191212
// Blazingly keep track of the current viewport height without blocking the thread, keeping that sweet 60fps on smartphones
192213
function useMaxHeight(
193214
controlledMaxHeight,
194-
registerReady: ReturnType<typeof useReady>['registerReady']
215+
registerReady: ReturnType<typeof useReady>['registerReady'],
216+
resizeSourceRef: React.MutableRefObject<ResizeSource>
195217
) {
196218
const setReady = useMemo(() => registerReady('maxHeight'), [registerReady])
197219
const [maxHeight, setMaxHeight] = useState(() =>
@@ -214,6 +236,7 @@ function useMaxHeight(
214236
// Bail if the max height is a controlled prop
215237
if (controlledMaxHeight) {
216238
setMaxHeight(roundAndCheckForNaN(controlledMaxHeight))
239+
resizeSourceRef.current = 'maxheightprop'
217240

218241
return
219242
}
@@ -227,19 +250,21 @@ function useMaxHeight(
227250
// throttle state changes using rAF
228251
raf.current = requestAnimationFrame(() => {
229252
setMaxHeight(window.innerHeight)
253+
resizeSourceRef.current = 'window'
230254

231255
raf.current = 0
232256
})
233257
}
234258
window.addEventListener('resize', handleResize)
235259
setMaxHeight(window.innerHeight)
260+
resizeSourceRef.current = 'window'
236261
setReady()
237262

238263
return () => {
239264
window.removeEventListener('resize', handleResize)
240265
cancelAnimationFrame(raf.current)
241266
}
242-
}, [controlledMaxHeight, setReady])
267+
}, [controlledMaxHeight, setReady, resizeSourceRef])
243268

244269
return maxHeight
245270
}

src/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export type SnapPointProps = {
2323

2424
export type snapPoints = (props: SnapPointProps) => number[] | number
2525

26+
/**
27+
* `window` comes from window.onresize, maxheightprop is if the `maxHeight` prop is used, and `element` comes from the resize observers that listens to header, footer and the content area
28+
*/
29+
export type ResizeSource = 'window' | 'maxheightprop' | 'element'
30+
2631
export type defaultSnapProps = {
2732
/** The snap points currently in use, this can be controlled by providing a `snapPoints` function on the bottom sheet. */
2833
snapPoints: number[]
@@ -34,7 +39,7 @@ export type defaultSnapProps = {
3439
export type SpringEvent =
3540
| { type: 'OPEN' }
3641
| { type: 'CLOSE' }
37-
| { type: 'RESIZE' }
42+
| { type: 'RESIZE'; source: ResizeSource }
3843
| { type: 'SNAP'; source: 'dragging' | 'custom' | string }
3944

4045
export type Props = {

0 commit comments

Comments
 (0)