"use client"; import { type PropsWithChildren, useEffect, useLayoutEffect, useRef, useState, } from "react"; type HandlebarsProps = PropsWithChildren; export function Handlebars({ children }: HandlebarsProps) { const containerRef = useRef(null); const leftHandleRef = useRef(null); const rightHandleRef = useRef(null); const [width, setWidth] = useState(0); const [leftHandle, setLeftHandle] = useState(0); const [rightHandle, setRightHandle] = useState(0); const widthRef = useRef(0); const leftHandlePositionRef = useRef(0); const rightHandlePositionRef = useRef(0); const dragRef = useRef<{ isDragging: boolean; side: "left" | "right" | null; pointerId: number | null; startX: number; initialPosition: number; }>({ isDragging: false, side: null, pointerId: null, startX: 0, initialPosition: 0, }); useLayoutEffect(() => { const el = containerRef.current; if (!el) return; const updateWidth = () => { const newWidth = el.offsetWidth; setWidth(newWidth); setRightHandle(newWidth); }; const observer = new ResizeObserver(updateWidth); observer.observe(el); updateWidth(); return () => observer.disconnect(); }, []); useEffect(() => { widthRef.current = width; leftHandlePositionRef.current = leftHandle; rightHandlePositionRef.current = rightHandle; }, [leftHandle, rightHandle, width]); useEffect(() => { const handlePointerMove = (event: PointerEvent) => { const { isDragging, side, pointerId, startX, initialPosition } = dragRef.current; if (!isDragging) return; if (pointerId !== null && event.pointerId !== pointerId) return; if (!side) return; const deltaX = event.clientX - startX; if (side === "left") { const maxLeft = Math.max(0, rightHandlePositionRef.current - 60); const nextLeftHandle = Math.max( 0, Math.min(maxLeft, initialPosition + deltaX), ); setLeftHandle(nextLeftHandle); return; } const minRight = Math.min( widthRef.current, leftHandlePositionRef.current + 60, ); const nextRightHandle = Math.max( minRight, Math.min(widthRef.current, initialPosition + deltaX), ); setRightHandle(nextRightHandle); }; const handlePointerEnd = (event: PointerEvent) => { const { pointerId } = dragRef.current; if (pointerId !== null && event.pointerId !== pointerId) return; dragRef.current.isDragging = false; dragRef.current.side = null; dragRef.current.pointerId = null; }; window.addEventListener("pointermove", handlePointerMove); window.addEventListener("pointerup", handlePointerEnd); window.addEventListener("pointercancel", handlePointerEnd); return () => { window.removeEventListener("pointermove", handlePointerMove); window.removeEventListener("pointerup", handlePointerEnd); window.removeEventListener("pointercancel", handlePointerEnd); }; }, []); const hasMeasuredWidth = width > 0; const leftGradientPercent = hasMeasuredWidth ? (leftHandle / (width - 10)) * 100 : 0; const rightGradientPercent = hasMeasuredWidth ? (rightHandle / (width + 10)) * 100 : 100; const textMask = hasMeasuredWidth ? `linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) ${leftGradientPercent}%, rgba(0, 0, 0) ${leftGradientPercent}%, rgba(0, 0, 0) ${rightGradientPercent}%, rgba(255, 255, 255, 0) ${rightGradientPercent}%, rgba(255, 255, 255, 0) 100%)` : undefined; return (
{ event.preventDefault(); leftHandleRef.current?.setPointerCapture(event.pointerId); dragRef.current.isDragging = true; dragRef.current.side = "left"; dragRef.current.pointerId = event.pointerId; dragRef.current.startX = event.clientX; dragRef.current.initialPosition = leftHandlePositionRef.current; }} >
{ event.preventDefault(); rightHandleRef.current?.setPointerCapture(event.pointerId); dragRef.current.isDragging = true; dragRef.current.side = "right"; dragRef.current.pointerId = event.pointerId; dragRef.current.startX = event.clientX; dragRef.current.initialPosition = rightHandlePositionRef.current; }} >
{children}
); }