Skip to content

Commit e18b9de

Browse files
committed
tune(parameter-slider): remove drag lag and stabilize center fill
1 parent 1b19bcf commit e18b9de

File tree

3 files changed

+27
-33
lines changed

3 files changed

+27
-33
lines changed

components/tool-ui/parameter-slider/parameter-slider.tsx

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -414,32 +414,26 @@ function SliderRow({
414414
const toClipFromLeftInset = (percent: number) =>
415415
toRadixThumbPosition(percent);
416416
const TERMINAL_EPSILON = 1e-6;
417+
const snapLeftInset = (percent: number) => {
418+
if (percent <= TERMINAL_EPSILON) return "0";
419+
if (percent >= 100 - TERMINAL_EPSILON) return "100%";
420+
return toClipFromLeftInset(percent);
421+
};
422+
const snapRightInset = (percent: number) => {
423+
if (percent <= TERMINAL_EPSILON) return "100%";
424+
if (percent >= 100 - TERMINAL_EPSILON) return "0";
425+
return toClipFromRightInset(percent);
426+
};
417427

418428
if (crossesZero) {
419-
// Keep interior fill aligned to Radix thumb math, but snap to exact
420-
// track borders at terminal values to avoid edge gaps.
421-
if (valuePercent <= TERMINAL_EPSILON) {
422-
return `inset(0 ${toClipFromRightInset(zeroPercent)} 0 0)`;
423-
}
424-
if (valuePercent >= 100 - TERMINAL_EPSILON) {
425-
return `inset(0 0 0 ${toClipFromLeftInset(zeroPercent)})`;
426-
}
427-
if (valuePercent >= zeroPercent) {
428-
// Positive: clip from zero on left, value on right
429-
return `inset(0 ${toClipFromRightInset(valuePercent)} 0 ${toClipFromLeftInset(zeroPercent)})`;
430-
} else {
431-
// Negative: clip from value on left, zero on right
432-
return `inset(0 ${toClipFromRightInset(zeroPercent)} 0 ${toClipFromLeftInset(valuePercent)})`;
433-
}
434-
}
435-
// Non-crossing: keep Radix alignment internally, but snap to exact borders at terminals.
436-
if (valuePercent <= TERMINAL_EPSILON) {
437-
return "inset(0 100% 0 0)";
438-
}
439-
if (valuePercent >= 100 - TERMINAL_EPSILON) {
440-
return "inset(0 0 0 0)";
429+
// Keep center anchor stable by always clipping the low/high pair,
430+
// independent of sign branch, then snapping at terminal edges.
431+
const lowPercent = Math.min(valuePercent, zeroPercent);
432+
const highPercent = Math.max(valuePercent, zeroPercent);
433+
return `inset(0 ${snapRightInset(highPercent)} 0 ${snapLeftInset(lowPercent)})`;
441434
}
442-
return `inset(0 ${toClipFromRightInset(valuePercent)} 0 0)`;
435+
// Non-crossing: fill starts at left edge; snap right inset at terminals.
436+
return `inset(0 ${snapRightInset(valuePercent)} 0 0)`;
443437
}, [crossesZero, zeroPercent, valuePercent]);
444438

445439
const fillMaskImage = crossesZero
@@ -505,10 +499,9 @@ function SliderRow({
505499
className={cn(
506500
"group/slider relative flex w-full touch-none items-center select-none",
507501
"isolate h-12",
508-
"[&>span]:transition-[left,transform]",
509502
isDragging
510-
? "[&>span]:duration-75 [&>span]:ease-linear"
511-
: "[&>span]:duration-180 [&>span]:ease-[cubic-bezier(0.22,1,0.36,1)]",
503+
? "[&>span]:transition-none"
504+
: "[&>span]:transition-[left,transform] [&>span]:duration-120 [&>span]:ease-[cubic-bezier(0.22,1,0.36,1)]",
512505
"[&>span]:will-change-[left,transform]",
513506
"motion-reduce:[&>span]:transition-none",
514507
disabled && "pointer-events-none opacity-50",
@@ -538,8 +531,8 @@ function SliderRow({
538531
className={cn(
539532
"absolute inset-0 will-change-[clip-path]",
540533
isDragging
541-
? "transition-[clip-path] duration-75 ease-linear"
542-
: "transition-[clip-path] duration-180 ease-[cubic-bezier(0.22,1,0.36,1)]",
534+
? "transition-none"
535+
: "transition-[clip-path] duration-120 ease-[cubic-bezier(0.22,1,0.36,1)]",
543536
"motion-reduce:transition-none",
544537
resolvedFillClassName ?? "bg-primary/30 dark:bg-primary/40",
545538
)}
@@ -581,8 +574,8 @@ function SliderRow({
581574
className={cn(
582575
"squircle pointer-events-none absolute inset-0 rounded-sm",
583576
isDragging
584-
? "transition-[opacity,background] duration-75 ease-linear"
585-
: "transition-[opacity,background] duration-180 ease-[cubic-bezier(0.22,1,0.36,1)]",
577+
? "transition-none"
578+
: "transition-[opacity,background] duration-120 ease-[cubic-bezier(0.22,1,0.36,1)]",
586579
"motion-reduce:transition-none",
587580
)}
588581
style={{

lib/tests/tool-ui/parameter-slider/visual-alignment-contract.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,11 @@ describe("parameter-slider visual alignment contract", () => {
160160
}),
161161
);
162162

163-
expect(html).toContain("transition-[clip-path] duration-180");
163+
expect(html).toContain("transition-[clip-path] duration-120");
164164
expect(html).toContain(
165-
"[&amp;&gt;span]:transition-[left,transform] [&amp;&gt;span]:duration-180",
165+
"[&amp;&gt;span]:transition-[left,transform] [&amp;&gt;span]:duration-120",
166166
);
167167
expect(html).toContain("ease-[cubic-bezier(0.22,1,0.36,1)]");
168+
expect(html).not.toContain("duration-180");
168169
});
169170
});

0 commit comments

Comments
 (0)