Skip to content

Commit

Permalink
fix: rewrite interpolations to always be in frame sync
Browse files Browse the repository at this point in the history
  • Loading branch information
stipsan committed Dec 5, 2020
1 parent d5485ac commit cc43467
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 96 deletions.
39 changes: 39 additions & 0 deletions docs/Footer.tsx
@@ -0,0 +1,39 @@
function Badge({
name,
version,
}: {
name: React.ReactNode
version: React.ReactNode
}) {
return (
<a
className="flex text-sm"
href={`https://www.npmjs.com/package/${name}/v/${version}`}
>
<span className="block pl-3 pr-2 py-1 text-white bg-gray-800 rounded-l-md">
{name}
</span>
<span className="block pr-3 pl-2 py-1 bg-hero-lighter text-black rounded-r-md">
v{version}
</span>
</a>
)
}

export default function Footer({
version,
reactSpringVersion,
reactUseGestureVersion,
}: {
version: string
reactSpringVersion: string
reactUseGestureVersion: string
}) {
return (
<footer className="px-10 py-32 grid md:grid-flow-col md:place-items-center place-content-center gap-8 bg-gray-900">
<Badge name="react-spring-bottom-sheet" version={version} />
<Badge name="react-spring" version={reactSpringVersion} />
<Badge name="react-use-gesture" version={reactUseGestureVersion} />
</footer>
)
}
9 changes: 9 additions & 0 deletions docs/style.css
Expand Up @@ -29,7 +29,16 @@
.is-iframe {
--rsbs-ml: 0px;
--rsbs-mr: 0px;

/* the bottom sheet doesn't need display cutout paddings when in the iframe */
& [data-rsbs-has-footer='false'] [data-rsbs-content-padding] {
padding-bottom: 0px;
}
& [data-rsbs-footer-padding] {
padding-bottom: 16px;
}
}
/* Used by things like the "Close example" links that are only needed when not in the iframe */
.is-iframe .only-window {
display: none;
}
Expand Down
24 changes: 20 additions & 4 deletions pages/_app.tsx
Expand Up @@ -7,14 +7,30 @@ import '../docs/style.css'
import '../src/style.css'

export async function getStaticProps() {
const { version, description, homepage, name, meta = {} } = await import(
'../package.json'
)
const [
{ version, description, homepage, name, meta = {} },
{ version: reactSpringVersion },
{ version: reactUseGestureVersion },
] = await Promise.all([
import('../package.json'),
import('react-spring/package.json'),
import('react-use-gesture/package.json'),
])
if (!meta['og:site_name']) {
meta['og:site_name'] = capitalize(name)
}

return { props: { version, description, homepage, name, meta } }
return {
props: {
version,
description,
homepage,
name,
meta,
reactSpringVersion,
reactUseGestureVersion,
},
}
}

export type GetStaticProps = InferGetStaticPropsType<typeof getStaticProps>
Expand Down
12 changes: 10 additions & 2 deletions pages/index.tsx
@@ -1,9 +1,10 @@
import type { NextPage } from 'next'
import { aside, scrollable, simple, sticky } from '../docs/headings'
import Footer from '../docs/Footer'
import Hero from '../docs/Hero'
import MetaTags from '../docs/MetaTags'
import Nugget from '../docs/Nugget'
import StickyNugget from '../docs/StickyNugget'
import MetaTags from '../docs/MetaTags'
import type { NextPage } from 'next'
import type { GetStaticProps } from './_app'

export { getStaticProps } from './_app'
Expand All @@ -14,6 +15,8 @@ const IndexPage: NextPage<GetStaticProps> = ({
description,
homepage,
meta,
reactSpringVersion,
reactUseGestureVersion,
}) => (
<>
<MetaTags
Expand Down Expand Up @@ -57,6 +60,11 @@ const IndexPage: NextPage<GetStaticProps> = ({
/>
</div>
</main>
<Footer
version={version}
reactSpringVersion={reactSpringVersion}
reactUseGestureVersion={reactUseGestureVersion}
/>
</>
)

Expand Down
116 changes: 70 additions & 46 deletions src/BottomSheet.tsx
Expand Up @@ -126,36 +126,34 @@ export const BottomSheet = React.forwardRef<
headerRef,
})

// Setup refs that are used in cases where full control is needed over when a side effect is executed
const maxHeightRef = useRef(maxHeight)
const minSnapRef = useRef(minSnap)
const maxSnapRef = useRef(maxSnap)
const findSnapRef = useRef(findSnap)
// Sync the refs with current state, giving the spring full control over when to respond to changes
useEffect(() => {
maxHeightRef.current = maxHeight
maxSnapRef.current = maxSnap
minSnapRef.current = minSnap
findSnapRef.current = findSnap
}, [findSnap, maxHeight, maxSnap, minSnap])

const defaultSnapRef = useRef(0)
useEffect(() => {
// Wait with selectin default snap until element dimensions are measured
if (!ready) return
console.count('selecting default snap')

defaultSnapRef.current = findSnap(getDefaultSnap)
}, [findSnap, getDefaultSnap, ready])
defaultSnapRef.current = findSnapRef.current(getDefaultSnap)
}, [getDefaultSnap, ready])

// @TODO can be renamed or deleted
// Wether to interpolate refs or states, useful when needing to transition between changed snapshot bounds
const shouldInterpolateRefs = useRef(false)
const minSnapRef = useRef(minSnap)
const maxSnapRef = useRef(maxSnap)

// Adjust the height whenever the snap points are changed due to resize events
useEffect(() => {
// If we're not gonna interpolate the refs we'll just quietly update them
if (!shouldInterpolateRefs.current) {
maxSnapRef.current = maxSnap
minSnapRef.current = minSnap

console.log(
'Resizing due to',
'maxSnap:',
maxSnapRef.current !== maxSnap,
'minSnap:',
minSnapRef.current !== minSnap
)

return
}

if (shouldInterpolateRefs.current) {
set({
// @ts-expect-error
Expand All @@ -164,7 +162,7 @@ export const BottomSheet = React.forwardRef<

await onSpringStartRef.current?.({ type: 'RESIZE' })

const snap = findSnap(heightRef.current)
const snap = findSnapRef.current(heightRef.current)

console.log('animate resize')

Expand All @@ -177,8 +175,8 @@ export const BottomSheet = React.forwardRef<
minSnapRef.current !== minSnap
)
// Adjust bounds to have enough room for the transition
maxSnapRef.current = Math.max(snap, heightRef.current)
minSnapRef.current = Math.min(snap, heightRef.current)
// maxSnapRef.current = Math.max(snap, heightRef.current)
//minSnapRef.current = Math.min(snap, heightRef.current)
console.log('new maxSnapRef', maxSnapRef.current)

heightRef.current = snap
Expand All @@ -187,19 +185,22 @@ export const BottomSheet = React.forwardRef<
await next({
y: snap,
backdrop: 1,
maxHeight,
maxSnap,
minSnap,
immediate: prefersReducedMotion.current,
})

maxSnapRef.current = maxSnap
minSnapRef.current = minSnap
//maxSnapRef.current = maxSnap
//minSnapRef.current = minSnap

onSpringEndRef.current?.({ type: 'RESIZE' })

console.groupEnd()
},
})
}
}, [findSnap, lastSnapRef, maxSnap, minSnap, prefersReducedMotion, set])
}, [lastSnapRef, maxHeight, maxSnap, minSnap, prefersReducedMotion, set])
useImperativeHandle(
forwardRef,
() => ({
Expand Down Expand Up @@ -261,7 +262,11 @@ export const BottomSheet = React.forwardRef<
await next({
y: defaultSnapRef.current,
backdrop: 1,
opacity: 1,
ready: 1,
maxHeight: maxHeightRef.current,
maxSnap: maxSnapRef.current,
// Using defaultSnapRef instead of minSnapRef to avoid animating `height` on open
minSnap: defaultSnapRef.current,
immediate: true,
})

Expand All @@ -281,7 +286,11 @@ export const BottomSheet = React.forwardRef<
await next({
y: defaultSnapRef.current,
backdrop: 0,
opacity: 0,
ready: 0,
maxHeight: maxHeightRef.current,
maxSnap: maxSnapRef.current,
// Using defaultSnapRef instead of minSnapRef to avoid animating `height` on open
minSnap: defaultSnapRef.current,
immediate: true,
})

Expand All @@ -298,7 +307,11 @@ export const BottomSheet = React.forwardRef<
await next({
y: 0,
backdrop: 0,
opacity: 1,
ready: 1,
maxHeight: maxHeightRef.current,
maxSnap: maxSnapRef.current,
// Using defaultSnapRef instead of minSnapRef to avoid animating `height` on open
minSnap: defaultSnapRef.current,
immediate: true,
})

Expand All @@ -309,7 +322,11 @@ export const BottomSheet = React.forwardRef<
await next({
y: defaultSnapRef.current,
backdrop: 1,
opacity: 1,
ready: 1,
maxHeight: maxHeightRef.current,
maxSnap: maxSnapRef.current,
// Using defaultSnapRef instead of minSnapRef to avoid animating `height` on open
minSnap: defaultSnapRef.current,
immediate: prefersReducedMotion.current,
})
}
Expand Down Expand Up @@ -368,18 +385,30 @@ export const BottomSheet = React.forwardRef<

if (maybeCancel()) return

heightRef.current = 0
// Edge case for already closed
if (heightRef.current === 0) {
onSpringEndRef.current?.({ type: 'CLOSE' })
return
}

console.log('animate close')
// Avoid animating the height property on close and stay within FLIP bounds by upping the minSnap
next({
minSnap: heightRef.current,
immediate: true,
})

heightRef.current = 0

await next({
y: 0,
backdrop: 0,
maxHeight: maxHeightRef.current,
maxSnap: maxSnapRef.current,
immediate: prefersReducedMotion.current,
})
if (maybeCancel()) return

await next({ opacity: 0, immediate: true })
await next({ ready: 0, immediate: true })

if (maybeCancel()) return

Expand Down Expand Up @@ -496,7 +525,10 @@ export const BottomSheet = React.forwardRef<
set({
y: newY,
backdrop: clamp(newY / minSnapRef.current, 0, 1),
opacity: 1,
ready: 1,
maxHeight: maxHeightRef.current,
maxSnap: maxSnapRef.current,
minSnap: minSnapRef.current,
immediate: prefersReducedMotion.current || down,
config: {
mass: relativeVelocity,
Expand Down Expand Up @@ -535,17 +567,7 @@ export const BottomSheet = React.forwardRef<
throw new TypeError('minSnapRef is NaN!!')
}

const interpolations = useSpringInterpolations({
spring,
maxHeight,
// Select which values to use in the interpolation based on wether it's safe to trust the height
maxSnapRef: shouldInterpolateRefs.current
? maxSnapRef
: { current: maxSnap },
minSnapRef: shouldInterpolateRefs.current
? minSnapRef
: { current: minSnap },
})
const interpolations = useSpringInterpolations({ spring })

return (
<animated.div
Expand All @@ -563,7 +585,9 @@ export const BottomSheet = React.forwardRef<
// but allow overriding them/disabling them
...style,
// Not overridable as the "focus lock with opacity 0" trick rely on it
opacity: spring.opacity,
// @TODO the line below only fails on TS <4
// @ts-ignore
opacity: spring.ready,
// Allows interactions on the rest of the page before the close transition is finished
pointerEvents: !ready || off ? 'none' : undefined,
}}
Expand Down
9 changes: 8 additions & 1 deletion src/hooks/useSpring.tsx
Expand Up @@ -4,7 +4,14 @@ import { useSpring as useReactSpring } from 'react-spring'
// Put in this file befause it makes it easier to type and I'm lazy! :D

export function useSpring() {
return useReactSpring(() => ({ y: 0, opacity: 0, backdrop: 0 }))
return useReactSpring(() => ({
y: 0,
ready: 0,
backdrop: 0,
maxHeight: 0,
minSnap: 0,
maxSnap: 0,
}))
}

export type Spring = ReturnType<typeof useSpring>[0]
Expand Down

0 comments on commit cc43467

Please sign in to comment.