From b1f473315d45fbc0218970804bf498b01d09b986 Mon Sep 17 00:00:00 2001 From: Emil Kowalski Date: Fri, 13 Sep 2024 21:56:29 +0200 Subject: [PATCH] Refactor background scaling --- src/context.ts | 8 +++-- src/helpers.ts | 27 ++++++++++++++- src/index.tsx | 68 ++++--------------------------------- src/types.ts | 2 ++ src/use-scale-background.ts | 51 ++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 65 deletions(-) create mode 100644 src/use-scale-background.ts diff --git a/src/context.ts b/src/context.ts index a2b13968..f1b0d9af 100644 --- a/src/context.ts +++ b/src/context.ts @@ -4,7 +4,6 @@ import { DrawerDirection } from './types'; interface DrawerContextValue { drawerRef: React.RefObject; overlayRef: React.RefObject; - scaleBackground: (open: boolean) => void; onPress: (event: React.PointerEvent) => void; onRelease: (event: React.PointerEvent) => void; onDrag: (event: React.PointerEvent) => void; @@ -25,12 +24,14 @@ interface DrawerContextValue { openProp?: boolean; onOpenChange?: (o: boolean) => void; direction?: DrawerDirection; + shouldScaleBackground: boolean; + setBackgroundColorOnScale: boolean; + noBodyStyles: boolean; } export const DrawerContext = React.createContext({ drawerRef: { current: null }, overlayRef: { current: null }, - scaleBackground: () => {}, onPress: () => {}, onRelease: () => {}, onDrag: () => {}, @@ -51,6 +52,9 @@ export const DrawerContext = React.createContext({ setActiveSnapPoint: () => {}, closeDrawer: () => {}, direction: 'bottom', + shouldScaleBackground: false, + setBackgroundColorOnScale: true, + noBodyStyles: false, }); export const useDrawerContext = () => { diff --git a/src/helpers.ts b/src/helpers.ts index 8c2fa3b7..db17154e 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,4 +1,4 @@ -import { DrawerDirection } from './types'; +import { AnyFunction, DrawerDirection } from './types'; interface Style { [key: string]: string; @@ -90,3 +90,28 @@ export function getTranslate(element: HTMLElement, direction: DrawerDirection): export function dampenValue(v: number) { return 8 * (Math.log(v + 1) - 2); } + +export function assignStyle(element: HTMLElement | null | undefined, style: Partial) { + if (!element) return () => {}; + + const prevStyle = element.style.cssText; + Object.assign(element.style, style); + + return () => { + element.style.cssText = prevStyle; + }; +} + +/** + * Receives functions as arguments and returns a new function that calls all. + */ +export function chain(...fns: T[]) { + return (...args: T extends AnyFunction ? Parameters : never) => { + for (const fn of fns) { + if (typeof fn === 'function') { + // @ts-ignore + fn(...args); + } + } + }; +} diff --git a/src/index.tsx b/src/index.tsx index 070b248e..8a05d569 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -20,6 +20,7 @@ import { } from './constants'; import { DrawerDirection } from './types'; import { useControllableState } from './use-controllable-state'; +import { useScaleBackground } from './use-scale-background'; export interface WithFadeFromProps { snapPoints: (number | string)[]; @@ -330,12 +331,6 @@ export function Root({ } } - React.useEffect(() => { - return () => { - scaleBackground(false); - }; - }, []); - React.useEffect(() => { function onVisualViewportChange() { if (!drawerRef.current) return; @@ -397,7 +392,6 @@ export function Root({ cancelDrag(); onClose?.(); - scaleBackground(false); setIsOpen(false); setTimeout(() => { @@ -538,7 +532,6 @@ export function Root({ }); openTime.current = new Date(); - scaleBackground(true); } }, [isOpen]); @@ -555,58 +548,6 @@ export function Root({ } }, [isOpen]); - function scaleBackground(open: boolean) { - const wrapper = document.querySelector('[data-vaul-drawer-wrapper]'); - - if (!wrapper || !shouldScaleBackground) return; - - if (open) { - if (setBackgroundColorOnScale) { - if (!noBodyStyles) { - // setting original styles initially - set(document.body, { - background: document.body.style.backgroundColor || document.body.style.background, - }); - // setting body styles, with cache ignored, so that we can get correct original styles in reset - set( - document.body, - { - background: 'black', - }, - true, - ); - } - } - - set(wrapper, { - borderRadius: `${BORDER_RADIUS}px`, - overflow: 'hidden', - ...(isVertical(direction) - ? { - transform: `scale(${getScale()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)`, - transformOrigin: 'top', - } - : { - transform: `scale(${getScale()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)`, - transformOrigin: 'left', - }), - transitionProperty: 'transform, border-radius', - transitionDuration: `${TRANSITIONS.DURATION}s`, - transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(',')})`, - }); - } else { - // Exit - reset(wrapper, 'overflow'); - reset(wrapper, 'transform'); - reset(wrapper, 'borderRadius'); - set(wrapper, { - transitionProperty: 'transform, border-radius', - transitionDuration: `${TRANSITIONS.DURATION}s`, - transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(',')})`, - }); - } - } - function onNestedOpenChange(o: boolean) { const scale = o ? (window.innerWidth - NESTED_DISPLACEMENT) / window.innerWidth : 1; const y = o ? -NESTED_DISPLACEMENT : 0; @@ -683,7 +624,6 @@ export function Root({ setActiveSnapPoint, drawerRef, overlayRef, - scaleBackground, onOpenChange, onPress, onRelease, @@ -700,6 +640,9 @@ export function Root({ modal, snapPointsOffset, direction, + shouldScaleBackground, + setBackgroundColorOnScale, + noBodyStyles, }} > {children} @@ -755,7 +698,8 @@ export const Content = React.forwardRef(function ( const pointerStartRef = React.useRef<{ x: number; y: number } | null>(null); const wasBeyondThePointRef = React.useRef(false); const hasSnapPoints = snapPoints && snapPoints.length > 0; - + useScaleBackground(); + const isDeltaInDirection = (delta: { x: number; y: number }, direction: DrawerDirection, threshold = 0) => { if (wasBeyondThePointRef.current) return true; diff --git a/src/types.ts b/src/types.ts index c30cd688..929805b2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,3 +3,5 @@ export interface SnapPoint { fraction: number; height: number; } + +export type AnyFunction = (...args: any) => any; diff --git a/src/use-scale-background.ts b/src/use-scale-background.ts new file mode 100644 index 00000000..2c360e38 --- /dev/null +++ b/src/use-scale-background.ts @@ -0,0 +1,51 @@ +import React from 'react'; +import { useDrawerContext } from './context'; +import { assignStyle, chain, isVertical } from './helpers'; +import { BORDER_RADIUS, TRANSITIONS, WINDOW_TOP_OFFSET } from './constants'; + +const noop = () => () => {}; + +export function useScaleBackground() { + const { direction, isOpen, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles } = useDrawerContext(); + const timeoutIdRef = React.useRef(null); + + function getScale() { + return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth; + } + + React.useEffect(() => { + if (isOpen && shouldScaleBackground) { + if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current); + const wrapper = document.querySelector('[data-vaul-drawer-wrapper]') as HTMLElement; + + const bodyAndWrapperCleanup = chain( + setBackgroundColorOnScale && !noBodyStyles ? assignStyle(document.body, { background: 'black' }) : noop, + assignStyle(wrapper, { + transformOrigin: isVertical(direction) ? 'top' : 'left', + transitionProperty: 'transform, border-radius', + transitionDuration: `${TRANSITIONS.DURATION}s`, + transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(',')})`, + }), + ); + + const wrapperStylesCleanup = assignStyle(wrapper, { + borderRadius: `${BORDER_RADIUS}px`, + overflow: 'hidden', + ...(isVertical(direction) + ? { + transform: `scale(${getScale()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)`, + } + : { + transform: `scale(${getScale()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)`, + }), + }); + + return () => { + wrapperStylesCleanup(); + timeoutIdRef.current = window.setTimeout(() => { + bodyAndWrapperCleanup(); + }, TRANSITIONS.DURATION * 1000); + }; + } + }, [isOpen, shouldScaleBackground]); +}