@@ -21,7 +21,6 @@ import { useSignatureReset } from "../shared/use-signature-reset";
2121
2222import { cn } from "./_adapter" ;
2323import {
24- advanceEasedSliderPercent ,
2524 createSliderSignature ,
2625 createSliderValueSnapshot ,
2726 sliderRangeToPercent ,
@@ -406,39 +405,6 @@ function SliderRow({
406405 ? sliderRangeToPercent ( { value : 0 , min, max } )
407406 : 0 ;
408407 const valuePercent = sliderRangeToPercent ( { value, min, max } ) ;
409- const [ easedValuePercent , setEasedValuePercent ] = useState ( valuePercent ) ;
410- const easedValuePercentRef = useRef ( valuePercent ) ;
411-
412- useEffect ( ( ) => {
413- let animationFrameId = 0 ;
414-
415- const tick = ( ) => {
416- const next = advanceEasedSliderPercent ( {
417- current : easedValuePercentRef . current ,
418- target : valuePercent ,
419- isDragging,
420- } ) ;
421-
422- easedValuePercentRef . current = next ;
423- setEasedValuePercent ( next ) ;
424-
425- if ( next !== valuePercent ) {
426- animationFrameId = window . requestAnimationFrame ( tick ) ;
427- }
428- } ;
429-
430- animationFrameId = window . requestAnimationFrame ( tick ) ;
431-
432- return ( ) => {
433- window . cancelAnimationFrame ( animationFrameId ) ;
434- } ;
435- } , [ valuePercent , isDragging ] ) ;
436-
437- const handleAlignmentOffset = useMemo (
438- ( ) =>
439- `calc(${ toRadixThumbPosition ( easedValuePercent ) } - ${ toRadixThumbPosition ( valuePercent ) } )` ,
440- [ easedValuePercent , valuePercent ] ,
441- ) ;
442408
443409 // Fill clip-path uses the same inset coordinate system as the handle.
444410 // This keeps the collapsed stroke aligned with the fill edge near extremes.
@@ -448,34 +414,33 @@ function SliderRow({
448414 const toClipFromLeftInset = ( percent : number ) =>
449415 toRadixThumbPosition ( percent ) ;
450416 const TERMINAL_EPSILON = 1e-6 ;
451- const fillPercent = easedValuePercent ;
452417
453418 if ( crossesZero ) {
454419 // Keep interior fill aligned to Radix thumb math, but snap to exact
455420 // track borders at terminal values to avoid edge gaps.
456- if ( fillPercent <= TERMINAL_EPSILON ) {
421+ if ( valuePercent <= TERMINAL_EPSILON ) {
457422 return `inset(0 ${ toClipFromRightInset ( zeroPercent ) } 0 0)` ;
458423 }
459- if ( fillPercent >= 100 - TERMINAL_EPSILON ) {
424+ if ( valuePercent >= 100 - TERMINAL_EPSILON ) {
460425 return `inset(0 0 0 ${ toClipFromLeftInset ( zeroPercent ) } )` ;
461426 }
462- if ( fillPercent >= zeroPercent ) {
427+ if ( valuePercent >= zeroPercent ) {
463428 // Positive: clip from zero on left, value on right
464- return `inset(0 ${ toClipFromRightInset ( fillPercent ) } 0 ${ toClipFromLeftInset ( zeroPercent ) } )` ;
429+ return `inset(0 ${ toClipFromRightInset ( valuePercent ) } 0 ${ toClipFromLeftInset ( zeroPercent ) } )` ;
465430 } else {
466431 // Negative: clip from value on left, zero on right
467- return `inset(0 ${ toClipFromRightInset ( zeroPercent ) } 0 ${ toClipFromLeftInset ( fillPercent ) } )` ;
432+ return `inset(0 ${ toClipFromRightInset ( zeroPercent ) } 0 ${ toClipFromLeftInset ( valuePercent ) } )` ;
468433 }
469434 }
470435 // Non-crossing: keep Radix alignment internally, but snap to exact borders at terminals.
471- if ( fillPercent <= TERMINAL_EPSILON ) {
436+ if ( valuePercent <= TERMINAL_EPSILON ) {
472437 return "inset(0 100% 0 0)" ;
473438 }
474- if ( fillPercent >= 100 - TERMINAL_EPSILON ) {
439+ if ( valuePercent >= 100 - TERMINAL_EPSILON ) {
475440 return "inset(0 0 0 0)" ;
476441 }
477- return `inset(0 ${ toClipFromRightInset ( fillPercent ) } 0 0)` ;
478- } , [ crossesZero , easedValuePercent , zeroPercent ] ) ;
442+ return `inset(0 ${ toClipFromRightInset ( valuePercent ) } 0 0)` ;
443+ } , [ crossesZero , zeroPercent , valuePercent ] ) ;
479444
480445 const fillMaskImage = crossesZero
481446 ? "linear-gradient(to right, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.35) 50%, rgba(0,0,0,0.7) 100%)"
@@ -486,12 +451,11 @@ function SliderRow({
486451 const reflectionStyle = useMemo ( ( ) => {
487452 const edgeThreshold = 3 ;
488453 const nearEdge =
489- easedValuePercent <= edgeThreshold ||
490- easedValuePercent >= 100 - edgeThreshold ;
454+ valuePercent <= edgeThreshold || valuePercent >= 100 - edgeThreshold ;
491455
492456 // Narrower spread when stationary at edges (~35% narrower)
493457 const spreadPercent = nearEdge && ! isDragging ? 6.5 : 10 ;
494- const handlePos = toRadixThumbPosition ( easedValuePercent ) ;
458+ const handlePos = toRadixThumbPosition ( valuePercent ) ;
495459 const start = `clamp(0%, calc(${ handlePos } - ${ spreadPercent } %), 100%)` ;
496460 const end = `clamp(0%, calc(${ handlePos } + ${ spreadPercent } %), 100%)` ;
497461
@@ -508,14 +472,13 @@ function SliderRow({
508472 maskComposite : "exclude" ,
509473 padding : "1px" ,
510474 } ;
511- } , [ easedValuePercent , isDragging ] ) ;
475+ } , [ valuePercent , isDragging ] ) ;
512476
513477 // Opacity scales with handle size: rest → hover → drag
514478 const reflectionOpacity = useMemo ( ( ) => {
515479 const edgeThreshold = 3 ;
516480 const atEdge =
517- easedValuePercent <= edgeThreshold ||
518- easedValuePercent >= 100 - edgeThreshold ;
481+ valuePercent <= edgeThreshold || valuePercent >= 100 - edgeThreshold ;
519482
520483 if ( isDragging || atEdge ) {
521484 return 1 ;
@@ -524,7 +487,7 @@ function SliderRow({
524487 return 0.6 ;
525488 }
526489 return 0 ;
527- } , [ easedValuePercent , isDragging , isHovered ] ) ;
490+ } , [ valuePercent , isDragging , isHovered ] ) ;
528491
529492 const handleValueChange = useCallback (
530493 ( values : number [ ] ) => {
@@ -542,6 +505,8 @@ function SliderRow({
542505 className = { cn (
543506 "group/slider relative flex w-full touch-none items-center select-none" ,
544507 "isolate h-12" ,
508+ "[&>span]:transition-[left,transform] [&>span]:duration-150 [&>span]:ease-[var(--cubic-ease-in-out)]" ,
509+ "[&>span]:will-change-[left,transform]" ,
545510 disabled && "pointer-events-none opacity-50" ,
546511 ) }
547512 value = { [ value ] }
@@ -567,7 +532,7 @@ function SliderRow({
567532 >
568533 < div
569534 className = { cn (
570- "absolute inset-0" ,
535+ "absolute inset-0 transition-[clip-path] duration-150 ease-[var(--cubic-ease-in-out)] will-change-[clip-path] " ,
571536 resolvedFillClassName ?? "bg-primary/30 dark:bg-primary/40" ,
572537 ) }
573538 style = { {
@@ -605,7 +570,7 @@ function SliderRow({
605570
606571 { /* Metallic reflection overlay - follows handle, brightness scales with interaction */ }
607572 < div
608- className = "squircle pointer-events-none absolute inset-0 rounded-sm transition-[opacity] duration-200 ease-[var(--cubic-ease-in-out)]"
573+ className = "squircle pointer-events-none absolute inset-0 rounded-sm transition-[opacity,background ] duration-150 ease-[var(--cubic-ease-in-out)]"
609574 style = { {
610575 ...reflectionStyle ,
611576 opacity : reflectionOpacity ,
@@ -630,15 +595,15 @@ function SliderRow({
630595 // Calculate morph state
631596 const isActive = isHovered || isDragging ;
632597
633- // Move the visual handle toward the eased position so handle and fill
634- // animate together while preserving Radix hit-target behavior .
635- const fillEdgeOffset = handleAlignmentOffset ;
598+ // Indicator stays centered on the real thumb while CSS transitions
599+ // smooth thumb wrapper and fill movement together .
600+ const fillEdgeOffset = 0 ;
636601
637602 // Hide rest-state indicator at edges (0% or 100%) - the reflection gradient handles this
638603 const edgeThreshold = 3 ;
639604 const atEdge =
640- easedValuePercent <= edgeThreshold ||
641- easedValuePercent >= 100 - edgeThreshold ;
605+ valuePercent <= edgeThreshold ||
606+ valuePercent >= 100 - edgeThreshold ;
642607 const restOpacity = atEdge ? 0 : 0.25 ;
643608
644609 // Asymmetric segment heights: gap is shifted up to match raised text position
@@ -667,7 +632,7 @@ function SliderRow({
667632 resolvedHandleClassName ?? "bg-primary" ,
668633 ) }
669634 style = { {
670- transform : `translateX(calc(-50% + ${ fillEdgeOffset } ))` ,
635+ transform : `translateX(calc(-50% + ${ fillEdgeOffset } px ))` ,
671636 height : topHeight ,
672637 opacity : isActive ? 1 : restOpacity ,
673638 } }
@@ -685,7 +650,7 @@ function SliderRow({
685650 resolvedHandleClassName ?? "bg-primary" ,
686651 ) }
687652 style = { {
688- transform : `translateX(calc(-50% + ${ fillEdgeOffset } ))` ,
653+ transform : `translateX(calc(-50% + ${ fillEdgeOffset } px ))` ,
689654 height : bottomHeight ,
690655 opacity : isActive ? 1 : restOpacity ,
691656 } }
0 commit comments