-
-
{children}
+
{footer && (
)}
-
)
})
diff --git a/src/hooks/useSnapPoints.tsx b/src/hooks/useSnapPoints.tsx
index 695edcfa..cac45767 100644
--- a/src/hooks/useSnapPoints.tsx
+++ b/src/hooks/useSnapPoints.tsx
@@ -6,13 +6,14 @@ import React, {
useRef,
useState,
} from 'react'
-import ResizeObserver from 'resize-observer-polyfill'
+import { ResizeObserver, ResizeObserverEntry } from '@juggle/resize-observer'
import type { defaultSnapProps, snapPoints } from '../types'
import { processSnapPoints, roundAndCheckForNaN } from '../utils'
import { useReady } from './useReady'
+import { ResizeObserverOptions } from '@juggle/resize-observer/lib/ResizeObserverOptions'
export function useSnapPoints({
- contentContainerRef,
+ contentRef,
controlledMaxHeight,
footerEnabled,
footerRef,
@@ -24,7 +25,7 @@ export function useSnapPoints({
ready,
registerReady,
}: {
- contentContainerRef: React.RefObject
+ contentRef: React.RefObject
controlledMaxHeight?: number
footerEnabled: boolean
footerRef: React.RefObject
@@ -37,7 +38,7 @@ export function useSnapPoints({
registerReady: ReturnType['registerReady']
}) {
const { maxHeight, minHeight, headerHeight, footerHeight } = useDimensions({
- contentContainerRef,
+ contentRef: contentRef,
controlledMaxHeight,
footerEnabled,
footerRef,
@@ -58,7 +59,7 @@ export function useSnapPoints({
: [0],
maxHeight
)
- console.log({ snapPoints, minSnap, maxSnap })
+ //console.log({ snapPoints, minSnap, maxSnap })
// @TODO investigate the gains from memoizing this
function findSnap(
@@ -92,7 +93,7 @@ export function useSnapPoints({
}
function useDimensions({
- contentContainerRef,
+ contentRef,
controlledMaxHeight,
footerEnabled,
footerRef,
@@ -100,7 +101,7 @@ function useDimensions({
headerRef,
registerReady,
}: {
- contentContainerRef: React.RefObject
+ contentRef: React.RefObject
controlledMaxHeight?: number
footerEnabled: boolean
footerRef: React.RefObject
@@ -114,15 +115,15 @@ function useDimensions({
const maxHeight = useMaxHeight(controlledMaxHeight, registerReady)
// @TODO probably better to forward props instead of checking refs to decide if it's enabled
- const { height: headerHeight } = useElementSizeObserver(headerRef, {
+ const headerHeight = useElementSizeObserver(headerRef, {
label: 'headerHeight',
enabled: headerEnabled,
})
- const { height: contentHeight } = useElementSizeObserver(
- contentContainerRef,
- { label: 'contentHeight', enabled: true }
- )
- const { height: footerHeight } = useElementSizeObserver(footerRef, {
+ const contentHeight = useElementSizeObserver(contentRef, {
+ label: 'contentHeight',
+ enabled: true,
+ })
+ const footerHeight = useElementSizeObserver(footerRef, {
label: 'footerHeight',
enabled: footerEnabled,
})
@@ -148,26 +149,27 @@ function useDimensions({
}
}
+const observerOptions: ResizeObserverOptions = {
+ // Respond to changes to padding, happens often on iOS when using env(safe-area-inset-bottom)
+ // And the user hides or shows the Safari browser toolbar
+ box: 'border-box',
+}
/**
* Hook for determining the size of an element using the Resize Observer API.
*
* @param ref - A React ref to an element
*/
-const defaultSize = { width: 0, height: 0 }
function useElementSizeObserver(
ref: React.RefObject,
{ label, enabled }: { label: string; enabled: boolean }
-): { width: number; height: number } {
- let [size, setSize] = useState(defaultSize)
+): number {
+ let [size, setSize] = useState(0)
- useDebugValue(`${label}: ${size.height}`)
+ useDebugValue(`${label}: ${size}`)
const handleResize = useCallback((entries: ResizeObserverEntry[]) => {
- setSize({
- // we only observe one element, so accessing the first entry here is fine
- width: entries[0].contentRect.width,
- height: entries[0].contentRect.height,
- })
+ // we only observe one element, so accessing the first entry here is fine
+ setSize(entries[0].borderBoxSize[0].blockSize)
}, [])
useEffect(() => {
@@ -175,19 +177,15 @@ function useElementSizeObserver(
return
}
- // Set initial size here, as the one from the observer fires too late on iOS safari
- const { width, height } = ref.current.getBoundingClientRect()
- setSize({ width, height })
-
const resizeObserver = new ResizeObserver(handleResize)
- resizeObserver.observe(ref.current)
+ resizeObserver.observe(ref.current, observerOptions)
return () => {
resizeObserver.disconnect()
}
}, [ref, handleResize, enabled])
- return enabled ? size : defaultSize
+ return enabled ? size : 0
}
// Blazingly keep track of the current viewport height without blocking the thread, keeping that sweet 60fps on smartphones
diff --git a/src/index.tsx b/src/index.tsx
index b55ca742..bf861667 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -65,18 +65,21 @@ export const BottomSheet = forwardRef(function BottomSheet(
[onSpringEnd]
)
+ // This isn't just a performance optimization, it's also to avoid issues when running a non-browser env like SSR
+ if (!mounted) {
+ return null
+ }
+
return (
- {mounted && (
- <_BottomSheet
- {...props}
- lastSnapRef={lastSnapRef}
- ref={ref}
- initialState={initialStateRef.current}
- onSpringStart={handleSpringStart}
- onSpringEnd={handleSpringEnd}
- />
- )}
+ <_BottomSheet
+ {...props}
+ lastSnapRef={lastSnapRef}
+ ref={ref}
+ initialState={initialStateRef.current}
+ onSpringStart={handleSpringStart}
+ onSpringEnd={handleSpringEnd}
+ />
)
})
diff --git a/src/style.css b/src/style.css
index bca5726a..58fc6ac1 100644
--- a/src/style.css
+++ b/src/style.css
@@ -16,24 +16,21 @@
0 -1px 0 rgba(38, 89, 115, 0.05);
}
[data-rsbs-overlay],
-[data-rsbs-antigap] {
+[data-rsbs-root]:after {
max-width: var(--rsbs-max-w);
margin-left: var(--rsbs-ml);
margin-right: var(--rsbs-mr);
}
[data-rsbs-overlay],
[data-rsbs-backdrop],
-[data-rsbs-antigap] {
+[data-rsbs-root]:after {
z-index: 3;
- -ms-scroll-chaining: none;
overscroll-behavior: none;
touch-action: none;
position: fixed;
right: 0;
bottom: 0;
left: 0;
- -webkit-user-select: none;
- -ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
@@ -50,7 +47,8 @@
cursor: ns-resize;
}
-[data-rsbs-antigap] {
+[data-rsbs-root]:after {
+ content: '';
pointer-events: none;
background: var(--rsbs-bg);
height: 1px;
@@ -62,21 +60,14 @@
[data-rsbs-header] {
flex-shrink: 0;
cursor: ns-resize;
-}
-[data-rsbs-footer-padding],
-[data-rsbs-header-padding] {
padding: 16px;
}
[data-rsbs-header] {
text-align: center;
- -webkit-user-select: none;
- -ms-user-select: none;
user-select: none;
box-shadow: 0 1px 0
rgba(46, 59, 66, calc(var(--rsbs-content-opacity) * 0.125));
z-index: 1;
-}
-[data-rsbs-header-padding] {
padding-top: calc(20px + env(safe-area-inset-top));
padding-bottom: 8px;
}
@@ -99,11 +90,9 @@
}
[data-rsbs-has-header='false'] [data-rsbs-header] {
box-shadow: none;
-}
-[data-rsbs-has-header='false'] [data-rsbs-header-padding] {
padding-top: calc(12px + env(safe-area-inset-top));
}
-[data-rsbs-content] {
+[data-rsbs-scroll] {
flex-shrink: 1;
flex-grow: 1;
-webkit-tap-highlight-color: revert;
@@ -112,30 +101,30 @@
-ms-user-select: auto;
user-select: auto;
overflow: auto;
- -ms-scroll-chaining: none;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
-[data-rsbs-content]:focus {
+[data-rsbs-scroll]:focus {
outline: none;
}
-[data-rsbs-has-footer='false'] [data-rsbs-content-padding] {
+[data-rsbs-has-footer='false'] [data-rsbs-scroll] {
padding-bottom: env(safe-area-inset-bottom);
}
+[data-rsbs-content] {
+ /* The overflow hidden is to ensure any margin on child nodes are included when the resize observer is measuring the height */
+ overflow: hidden;
+}
[data-rsbs-footer] {
box-shadow: 0 -1px 0 rgba(46, 59, 66, calc(var(--rsbs-content-opacity) * 0.125)),
0 2px 0 var(--rsbs-bg);
overflow: hidden;
z-index: 1;
-}
-[data-rsbs-footer-padding] {
padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
[data-rsbs-is-dismissable='true'],
[data-rsbs-is-dismissable='false']:matches([data-rsbs-state='opening'], [data-rsbs-state='closing']) {
- &
- :matches([data-rsbs-header-padding], [data-rsbs-content-padding], [data-rsbs-footer-padding]) {
+ & :matches([data-rsbs-header], [data-rsbs-scroll], [data-rsbs-footer]) > * {
opacity: var(--rsbs-content-opacity);
}
& [data-rsbs-backdrop] {