Skip to content

Commit

Permalink
feat: add source to RESIZE events
Browse files Browse the repository at this point in the history
Fixes #53
  • Loading branch information
stipsan committed Jan 21, 2021
1 parent 51c8510 commit 383e206
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 11 deletions.
5 changes: 4 additions & 1 deletion README.md
Expand Up @@ -191,7 +191,10 @@ If the user reopens the sheet before it's done animating it'll trigger this even
#### RESIZE
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.
Type: `{ source: 'window' | 'maxheightprop' | 'element }`
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.
`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.
#### SNAP
Expand Down
27 changes: 23 additions & 4 deletions src/BottomSheet.tsx
Expand Up @@ -29,6 +29,7 @@ import type {
defaultSnapProps,
Props,
RefHandles,
ResizeSource,
SnapPointProps,
} from './types'
import { debugging } from './utils'
Expand Down Expand Up @@ -99,6 +100,7 @@ export const BottomSheet = React.forwardRef<

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

const prefersReducedMotion = useReducedMotion()

Expand Down Expand Up @@ -131,6 +133,7 @@ export const BottomSheet = React.forwardRef<
lastSnapRef,
ready,
registerReady,
resizeSourceRef,
})

// Setup refs that are used in cases where full control is needed over when a side effect is executed
Expand Down Expand Up @@ -196,7 +199,11 @@ export const BottomSheet = React.forwardRef<
[]
),
onResizeCancel: useCallback(
() => onSpringCancelRef.current?.({ type: 'RESIZE' }),
() =>
onSpringCancelRef.current?.({
type: 'RESIZE',
source: resizeSourceRef.current,
}),
[]
),
onOpenEnd: useCallback(
Expand All @@ -212,7 +219,11 @@ export const BottomSheet = React.forwardRef<
[]
),
onResizeEnd: useCallback(
() => onSpringEndRef.current?.({ type: 'RESIZE' }),
() =>
onSpringEndRef.current?.({
type: 'RESIZE',
source: resizeSourceRef.current,
}),
[]
),
},
Expand All @@ -235,7 +246,11 @@ export const BottomSheet = React.forwardRef<
[]
),
onResizeStart: useCallback(
async () => onSpringStartRef.current?.({ type: 'RESIZE' }),
async () =>
onSpringStartRef.current?.({
type: 'RESIZE',
source: resizeSourceRef.current,
}),
[]
),
onSnapEnd: useCallback(
Expand All @@ -255,7 +270,11 @@ export const BottomSheet = React.forwardRef<
[]
),
onResizeEnd: useCallback(
async () => onSpringEndRef.current?.({ type: 'RESIZE' }),
async () =>
onSpringEndRef.current?.({
type: 'RESIZE',
source: resizeSourceRef.current,
}),
[]
),
renderVisuallyHidden: useCallback(
Expand Down
35 changes: 30 additions & 5 deletions src/hooks/useSnapPoints.tsx
Expand Up @@ -7,7 +7,7 @@ import React, {
useState,
} from 'react'
import { ResizeObserver, ResizeObserverEntry } from '@juggle/resize-observer'
import type { defaultSnapProps, snapPoints } from '../types'
import type { defaultSnapProps, ResizeSource, snapPoints } from '../types'
import { processSnapPoints, roundAndCheckForNaN } from '../utils'
import { useReady } from './useReady'
import { ResizeObserverOptions } from '@juggle/resize-observer/lib/ResizeObserverOptions'
Expand All @@ -24,6 +24,7 @@ export function useSnapPoints({
lastSnapRef,
ready,
registerReady,
resizeSourceRef,
}: {
contentRef: React.RefObject<Element>
controlledMaxHeight?: number
Expand All @@ -36,6 +37,7 @@ export function useSnapPoints({
lastSnapRef: React.RefObject<number>
ready: boolean
registerReady: ReturnType<typeof useReady>['registerReady']
resizeSourceRef: React.MutableRefObject<ResizeSource>
}) {
const { maxHeight, minHeight, headerHeight, footerHeight } = useDimensions({
contentRef: contentRef,
Expand All @@ -45,6 +47,7 @@ export function useSnapPoints({
headerEnabled,
headerRef,
registerReady,
resizeSourceRef,
})

const { snapPoints, minSnap, maxSnap } = processSnapPoints(
Expand Down Expand Up @@ -100,6 +103,7 @@ function useDimensions({
headerEnabled,
headerRef,
registerReady,
resizeSourceRef,
}: {
contentRef: React.RefObject<Element>
controlledMaxHeight?: number
Expand All @@ -108,24 +112,32 @@ function useDimensions({
headerEnabled: boolean
headerRef: React.RefObject<Element>
registerReady: ReturnType<typeof useReady>['registerReady']
resizeSourceRef: React.MutableRefObject<ResizeSource>
}) {
const setReady = useMemo(() => registerReady('contentHeight'), [
registerReady,
])
const maxHeight = useMaxHeight(controlledMaxHeight, registerReady)
const maxHeight = useMaxHeight(
controlledMaxHeight,
registerReady,
resizeSourceRef
)

// @TODO probably better to forward props instead of checking refs to decide if it's enabled
const headerHeight = useElementSizeObserver(headerRef, {
label: 'headerHeight',
enabled: headerEnabled,
resizeSourceRef,
})
const contentHeight = useElementSizeObserver(contentRef, {
label: 'contentHeight',
enabled: true,
resizeSourceRef,
})
const footerHeight = useElementSizeObserver(footerRef, {
label: 'footerHeight',
enabled: footerEnabled,
resizeSourceRef,
})
const minHeight =
Math.min(maxHeight - headerHeight - footerHeight, contentHeight) +
Expand Down Expand Up @@ -161,7 +173,15 @@ const observerOptions: ResizeObserverOptions = {
*/
function useElementSizeObserver(
ref: React.RefObject<Element>,
{ label, enabled }: { label: string; enabled: boolean }
{
label,
enabled,
resizeSourceRef,
}: {
label: string
enabled: boolean
resizeSourceRef: React.MutableRefObject<ResizeSource>
}
): number {
let [size, setSize] = useState(0)

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

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

return
}
Expand All @@ -227,19 +250,21 @@ function useMaxHeight(
// throttle state changes using rAF
raf.current = requestAnimationFrame(() => {
setMaxHeight(window.innerHeight)
resizeSourceRef.current = 'window'

raf.current = 0
})
}
window.addEventListener('resize', handleResize)
setMaxHeight(window.innerHeight)
resizeSourceRef.current = 'window'
setReady()

return () => {
window.removeEventListener('resize', handleResize)
cancelAnimationFrame(raf.current)
}
}, [controlledMaxHeight, setReady])
}, [controlledMaxHeight, setReady, resizeSourceRef])

return maxHeight
}
7 changes: 6 additions & 1 deletion src/types.ts
Expand Up @@ -23,6 +23,11 @@ export type SnapPointProps = {

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

/**
* `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
*/
export type ResizeSource = 'window' | 'maxheightprop' | 'element'

export type defaultSnapProps = {
/** The snap points currently in use, this can be controlled by providing a `snapPoints` function on the bottom sheet. */
snapPoints: number[]
Expand All @@ -34,7 +39,7 @@ export type defaultSnapProps = {
export type SpringEvent =
| { type: 'OPEN' }
| { type: 'CLOSE' }
| { type: 'RESIZE' }
| { type: 'RESIZE'; source: ResizeSource }
| { type: 'SNAP'; source: 'dragging' | 'custom' | string }

export type Props = {
Expand Down

0 comments on commit 383e206

Please sign in to comment.