huntabyte / vaul-svelte

An unstyled drawer component for Svelte.
https://vaul-svelte.com
MIT License
467 stars 19 forks source link

Event for animations and dragging #107

Open VityaSchel opened 6 days ago

VityaSchel commented 6 days ago

Describe the feature in detail (code, mocks, or screenshots encouraged)

The component already has onDrag callback which reports percentage of dragging, but I need to get currently visible portion of drawer on screen, including all animations and dragging

What type of pull request would this be?

New Feature

Provide relevant links or additional information.

No response

VityaSchel commented 6 days ago

Here is a temporary patch for 0.3.2 version. It requires bezier-easing package from npm in order to run CSS cubic-bezier transitions in js, so just install it on your project where you use vaul-svelte. You can apply the patch by placing it to patches/vaul-svelte@0.3.2.patch directory and running bun install. Then use onDrag prop on Drawer.Root with percentage argument on callback:

<Drawer.Root onDrag={(_, p) => console.log('progress in range [0-1]', p)} />
diff --git a/dist/internal/vaul.js b/dist/internal/vaul.js
index 6ced7405f010443705cbfef58060d050994842f6..584eb6161b4f95a5027d51acb015a4f119c17602 100644
--- a/dist/internal/vaul.js
+++ b/dist/internal/vaul.js
@@ -5,6 +5,24 @@ import { isIOS, preventScroll } from "./prevent-scroll.js";
 import { TRANSITIONS, VELOCITY_THRESHOLD } from "./constants.js";
 import { handleEscapeKeydown } from "./escape-keydown.js";
 import { handlePositionFixed } from "./position-fixed.js";
+import bezierEasing from 'bezier-easing'
+function animateTransition(callback, from, to) {
+    const startTime = performance.now()
+    const durationMs = TRANSITIONS.DURATION * 1000
+    const easing = bezierEasing(...TRANSITIONS.EASE)
+    function step(currentTime) {
+        const elapsed = currentTime - startTime
+        const progress = Math.min(elapsed / durationMs, 1)
+        const easedProgress = easing(progress)
+
+        callback(from + easedProgress * (to - from))
+
+        if (elapsed < durationMs) {
+            requestAnimationFrame(step)
+        }
+    }
+    requestAnimationFrame(step)
+}
 const CLOSE_THRESHOLD = 0.25;
 const SCROLL_LOCK_TIMEOUT = 100;
 const BORDER_RADIUS = 8;
@@ -419,6 +437,12 @@ export function createVaul(props) {
         if (!$drawerRef)
             return;
         const $direction = get(direction);
+        const drawerStyles = window.getComputedStyle($drawerRef)
+        const drawerOffsetFromSnapPoint = new DOMMatrix(drawerStyles.transform).m42
+        const drawerHeight = parseFloat(drawerStyles.height)
+        animateTransition(x => {
+            onDragProp?.(undefined, x / drawerHeight)
+        }, drawerOffsetFromSnapPoint, drawerHeight)
         onClose?.();
         set($drawerRef, {
             transform: isVertical($direction)
@@ -469,6 +493,12 @@ export function createVaul(props) {
             transform: "translate3d(0, 0, 0)",
             transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
         });
+        const drawerStyles = window.getComputedStyle($drawerRef)
+        const drawerOffsetFromSnapPoint = new DOMMatrix(drawerStyles.transform).m42
+        const drawerHeight = parseFloat(drawerStyles.height)
+        animateTransition(x => {
+            onDragProp?.(undefined, x / drawerHeight)
+        }, drawerOffsetFromSnapPoint, 0)
         set($overlayRef, {
             transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
             opacity: "1",
@@ -566,6 +596,14 @@ export function createVaul(props) {
         }
         openTime.set(new Date());
         scaleBackground(true, props.backgroundColor);
+        setTimeout(() => {
+            const $drawerRef = get(drawerRef);
+            const drawerStyles = window.getComputedStyle($drawerRef)
+            const drawerHeight = parseFloat(drawerStyles.height)
+            animateTransition(x => {
+                onDragProp?.(undefined, 1 - (x / drawerHeight))
+            }, 0, drawerHeight)
+        }, 0)
     });
     effect([visible], ([$visible]) => {
         if (!$visible)