'use client'; import type { ComponentProps, CSSProperties, FC } from 'react'; import { useEffect } from 'react'; import { EVENT_CAPTURE_PARAMS, EVENT_PASSIVE_PARAMS, useEventListener } from './internal-hooks'; import { useReactCompareSliderContext } from './provider'; import { ReactCompareSliderCssVars } from '../consts'; export type RootProps = ComponentProps<'div'>; const currentPositionCssValue = `clamp(var(${ReactCompareSliderCssVars.boundsPadding}), var(${ReactCompareSliderCssVars.rawPosition}) - var(${ReactCompareSliderCssVars.boundsPadding}) + var(${ReactCompareSliderCssVars.boundsPadding}), calc(100% - var(${ReactCompareSliderCssVars.boundsPadding})))`; /** * The root container of the slider. */ export const Root: FC = ({ style, ...props }) => { const { browsingContext, boundsPadding, changePositionOnHover, clip, isDragging, portrait, position, onTouchEnd, onPointerDown, onPointerMove, onPointerUp, interactiveTarget, rootRef, transition, canTransition, } = useReactCompareSliderContext(); const appliedStyle = { position: 'relative', boxSizing: 'border-box', display: 'grid', maxWidth: '100%', maxHeight: '100%', overflow: 'hidden', cursor: isDragging ? (portrait ? 'ns-resize' : 'ew-resize') : undefined, touchAction: 'pan-y', userSelect: 'none', KhtmlUserSelect: 'none', msUserSelect: 'none', MozUserSelect: 'none', WebkitUserSelect: 'none', /** * The transition is on the root because this is where `--rcs-raw-position` is set. * With the `@property` registration in the hook, the browser interpolates the variable itself, so children * that reference it via `var()` see smooth intermediate values without needing their own transitions. * This improves performance and prevents Safari from desyncing independent transitions on separate elements. */ transition: canTransition && transition ? `${ReactCompareSliderCssVars.rawPosition} ${transition}` : 'none', [ReactCompareSliderCssVars.rawPosition]: `${position.current}%`, [ReactCompareSliderCssVars.boundsPadding]: boundsPadding, [ReactCompareSliderCssVars.currentPosition]: currentPositionCssValue, ...style, } as CSSProperties; useEventListener('touchend', onTouchEnd, interactiveTarget, EVENT_CAPTURE_PARAMS); useEventListener('pointerdown', onPointerDown, interactiveTarget, EVENT_CAPTURE_PARAMS); // Handle hover events on the container. useEffect(() => { const containerRef = rootRef.current as HTMLDivElement; const handlePointerLeave = (ev: PointerEvent): void => { if (isDragging) return; onPointerUp(ev); }; if (changePositionOnHover) { containerRef.addEventListener('pointermove', onPointerMove, EVENT_PASSIVE_PARAMS); containerRef.addEventListener('pointerleave', handlePointerLeave, EVENT_PASSIVE_PARAMS); } return () => { containerRef.removeEventListener('pointermove', onPointerMove); containerRef.removeEventListener('pointerleave', handlePointerLeave); }; }, [changePositionOnHover, onPointerMove, onPointerUp, isDragging, rootRef]); // Allow drag outside of container while pointer is still down. useEffect(() => { if (!isDragging) return; browsingContext.addEventListener('pointermove', onPointerMove, EVENT_PASSIVE_PARAMS); browsingContext.addEventListener('pointerup', onPointerUp, EVENT_PASSIVE_PARAMS); return () => { browsingContext?.removeEventListener('pointermove', onPointerMove); browsingContext?.removeEventListener('pointerup', onPointerUp); }; }, [onPointerMove, onPointerUp, isDragging, browsingContext]); return
; };