{"version":3,"file":"auto-scroll.mjs","sources":["../../../../../src/components/Reorder/utils/auto-scroll.ts"],"sourcesContent":["import { frameData } from \"motion-dom\"\n\nconst threshold = 50\n// Pixels per millisecond (25px per frame at 60fps)\nconst maxSpeed = 1.5\n\nconst overflowStyles = new Set([\"auto\", \"scroll\"])\n\ntype ActiveEdge = \"start\" | \"end\" | null\n\ninterface ScrollState {\n    edge: ActiveEdge\n    limit: number\n}\n\n/**\n * Per axis and scrollable element, for the current drag: the edge it's\n * auto-scrolling towards, \"start\" (top/left) or \"end\" (bottom/right), and\n * its scroll limit when that began (Bug 1 fix). A drag in a Reorder.Board\n * scrolls on both axes, often of one element (like the page), and can\n * scroll several, so they're kept per axis and all cleared when it ends.\n */\nconst scrollStates = {\n    x: new Map<HTMLElement, ScrollState>(),\n    y: new Map<HTMLElement, ScrollState>(),\n}\n\nexport function resetAutoScrollState(): void {\n    scrollStates.x.clear()\n    scrollStates.y.clear()\n}\n\nfunction isScrollableElement(element: Element, axis: \"x\" | \"y\"): boolean {\n    const style = getComputedStyle(element)\n    const overflow = axis === \"x\" ? style.overflowX : style.overflowY\n\n    const isDocumentScroll =\n        element === document.body ||\n        element === document.documentElement\n\n    /**\n     * An element that scrolls on one axis computes overflow auto on both\n     * (e.g. a column of a board that scrolls the other way), so it must also\n     * have content to scroll on this one.\n     */\n    return (\n        isDocumentScroll ||\n        (overflowStyles.has(overflow) &&\n            (axis === \"x\"\n                ? element.scrollWidth > element.clientWidth\n                : element.scrollHeight > element.clientHeight))\n    )\n}\n\nfunction findScrollableAncestor(\n    element: Element | null,\n    axis: \"x\" | \"y\"\n): HTMLElement | null {\n    let current = element?.parentElement\n    while (current) {\n        if (isScrollableElement(current, axis)) {\n            return current\n        }\n        current = current.parentElement\n    }\n    return null\n}\n\n/**\n * Speed ramps up towards the edge and is capped at maxSpeed once the\n * pointer reaches or passes it. This runs from onDrag, within a frame, so\n * scale by the frame's elapsed time to scroll at the same speed whatever\n * the display's refresh rate.\n */\nfunction scrollSpeed(distanceFromEdge: number): number {\n    const intensity = Math.min(1, 1 - distanceFromEdge / threshold)\n    return maxSpeed * intensity * intensity * frameData.delta\n}\n\nfunction getScrollAmount(\n    pointerPosition: number,\n    scrollElement: HTMLElement,\n    axis: \"x\" | \"y\"\n): { amount: number; edge: ActiveEdge } {\n    const rect = scrollElement.getBoundingClientRect()\n\n    const start = axis === \"x\" ? Math.max(0, rect.left) : Math.max(0, rect.top)\n    const end = axis === \"x\" ? Math.min(window.innerWidth, rect.right) : Math.min(window.innerHeight, rect.bottom)\n\n    const distanceFromStart = pointerPosition - start\n    const distanceFromEnd = end - pointerPosition\n\n    if (distanceFromStart < threshold) {\n        return { amount: -scrollSpeed(distanceFromStart), edge: \"start\" }\n    } else if (distanceFromEnd < threshold) {\n        return { amount: scrollSpeed(distanceFromEnd), edge: \"end\" }\n    }\n\n    return { amount: 0, edge: null }\n}\n\nexport function autoScrollIfNeeded(\n    groupElement: Element | null,\n    pointerPosition: number,\n    axis: \"x\" | \"y\",\n    velocity: number\n): void {\n    if (!groupElement) return\n\n    const scrollableAncestor = findScrollableAncestor(groupElement, axis)\n    if (!scrollableAncestor) return\n\n    // Convert pointer position from page coordinates to viewport coordinates.\n    // The gesture system uses pageX/pageY but getBoundingClientRect() returns\n    // viewport-relative coordinates, so we need to account for page scroll.\n    const viewportPointerPosition =\n        pointerPosition - (axis === \"x\" ? window.scrollX : window.scrollY)\n\n    const { amount: scrollAmount, edge } = getScrollAmount(\n        viewportPointerPosition,\n        scrollableAncestor,\n        axis\n    )\n\n    const states = scrollStates[axis]\n\n    // If not in any threshold zone, clear this axis' state\n    if (edge === null) {\n        states.delete(scrollableAncestor)\n        return\n    }\n\n    let state = states.get(scrollableAncestor)\n\n    const isDocumentScroll =\n        scrollableAncestor === document.body ||\n        scrollableAncestor === document.documentElement\n\n    // If not currently scrolling this edge, check velocity to see if we should start\n    if (state?.edge !== edge) {\n        // Only start scrolling if velocity is towards the edge\n        const shouldStart =\n            (edge === \"start\" && velocity < 0) ||\n            (edge === \"end\" && velocity > 0)\n        if (!shouldStart) return\n\n        // Activate this edge, and record the initial scroll limit\n        // (prevents infinite scroll)\n        state = {\n            edge,\n            limit:\n                axis === \"x\"\n                    ? scrollableAncestor.scrollWidth - (isDocumentScroll ? window.innerWidth : scrollableAncestor.clientWidth)\n                    : scrollableAncestor.scrollHeight - (isDocumentScroll ? window.innerHeight : scrollableAncestor.clientHeight),\n        }\n        states.set(scrollableAncestor, state)\n    }\n\n    // Cap scrolling at initial limit (prevents infinite scroll)\n    if (scrollAmount > 0) {\n        const currentScroll =\n            axis === \"x\"\n                ? (isDocumentScroll ? window.scrollX : scrollableAncestor.scrollLeft)\n                : (isDocumentScroll ? window.scrollY : scrollableAncestor.scrollTop)\n        if (currentScroll >= state.limit) return\n    }\n\n    // Apply scroll\n    if (axis === \"x\") {\n        if (isDocumentScroll) {\n            window.scrollBy({ left: scrollAmount })\n        } else {\n            scrollableAncestor.scrollLeft += scrollAmount\n        }\n    } else {\n        if (isDocumentScroll) {\n            window.scrollBy({ top: scrollAmount })\n        } else {\n            scrollableAncestor.scrollTop += scrollAmount\n        }\n    }\n}\n"],"names":[],"mappings":"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"}