fix: use ref flag to prevent feedback loop in reactive sliders
Track when applying our own changes via isApplyingRef to prevent the color update effect from resetting sliders when we trigger the change ourselves. This properly breaks the infinite loop while maintaining reactive behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -19,8 +19,6 @@ interface ManipulationPanelProps {
|
||||
}
|
||||
|
||||
export function ManipulationPanel({ color, onColorChange }: ManipulationPanelProps) {
|
||||
// Track base color and reset sliders when color changes externally
|
||||
const [baseColor, setBaseColor] = useState(color);
|
||||
const [lightenAmount, setLightenAmount] = useState(0);
|
||||
const [darkenAmount, setDarkenAmount] = useState(0);
|
||||
const [saturateAmount, setSaturateAmount] = useState(0);
|
||||
@@ -34,100 +32,101 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro
|
||||
const rotateMutation = useRotate();
|
||||
const complementMutation = useComplement();
|
||||
|
||||
// Debounce timer
|
||||
const debounceTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
// Track if we're applying our own changes to prevent feedback loop
|
||||
const isApplyingRef = useRef(false);
|
||||
const baseColorRef = useRef(color);
|
||||
|
||||
// Reset sliders when color changes from outside (e.g., color picker)
|
||||
// Update base color only when not applying our own changes
|
||||
useEffect(() => {
|
||||
setBaseColor(color);
|
||||
setLightenAmount(0);
|
||||
setDarkenAmount(0);
|
||||
setSaturateAmount(0);
|
||||
setDesaturateAmount(0);
|
||||
setRotateAmount(0);
|
||||
}, [color]);
|
||||
|
||||
// Apply all manipulations to base color
|
||||
const applyManipulations = useCallback(async () => {
|
||||
let currentColor = baseColor;
|
||||
|
||||
try {
|
||||
// Apply lighten
|
||||
if (lightenAmount > 0) {
|
||||
const result = await lightenMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: lightenAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply darken
|
||||
if (darkenAmount > 0) {
|
||||
const result = await darkenMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: darkenAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply saturate
|
||||
if (saturateAmount > 0) {
|
||||
const result = await saturateMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: saturateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply desaturate
|
||||
if (desaturateAmount > 0) {
|
||||
const result = await desaturateMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: desaturateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply rotate
|
||||
if (rotateAmount !== 0) {
|
||||
const result = await rotateMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: rotateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Only update if color changed
|
||||
if (currentColor !== baseColor) {
|
||||
onColorChange(currentColor);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silent error during manipulation
|
||||
if (!isApplyingRef.current) {
|
||||
baseColorRef.current = color;
|
||||
// Reset sliders when color changes externally
|
||||
setLightenAmount(0);
|
||||
setDarkenAmount(0);
|
||||
setSaturateAmount(0);
|
||||
setDesaturateAmount(0);
|
||||
setRotateAmount(0);
|
||||
}
|
||||
}, [baseColor, lightenAmount, darkenAmount, saturateAmount, desaturateAmount, rotateAmount, lightenMutation, darkenMutation, saturateMutation, desaturateMutation, rotateMutation, onColorChange]);
|
||||
}, [color]);
|
||||
|
||||
// Debounced effect to apply manipulations
|
||||
useEffect(() => {
|
||||
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
||||
const timer = setTimeout(async () => {
|
||||
// Skip if all sliders are at neutral position
|
||||
if (lightenAmount === 0 && darkenAmount === 0 && saturateAmount === 0 &&
|
||||
desaturateAmount === 0 && rotateAmount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
debounceTimer.current = setTimeout(() => {
|
||||
applyManipulations();
|
||||
isApplyingRef.current = true;
|
||||
let currentColor = baseColorRef.current;
|
||||
|
||||
try {
|
||||
// Apply lighten
|
||||
if (lightenAmount > 0) {
|
||||
const result = await lightenMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: lightenAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply darken
|
||||
if (darkenAmount > 0) {
|
||||
const result = await darkenMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: darkenAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply saturate
|
||||
if (saturateAmount > 0) {
|
||||
const result = await saturateMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: saturateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply desaturate
|
||||
if (desaturateAmount > 0) {
|
||||
const result = await desaturateMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: desaturateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply rotate
|
||||
if (rotateAmount !== 0) {
|
||||
const result = await rotateMutation.mutateAsync({
|
||||
colors: [currentColor],
|
||||
amount: rotateAmount,
|
||||
});
|
||||
if (result.colors[0]) {
|
||||
currentColor = result.colors[0].output;
|
||||
}
|
||||
}
|
||||
|
||||
onColorChange(currentColor);
|
||||
} catch (error) {
|
||||
// Silent error during manipulation
|
||||
} finally {
|
||||
isApplyingRef.current = false;
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
||||
};
|
||||
}, [applyManipulations]);
|
||||
return () => clearTimeout(timer);
|
||||
}, [lightenAmount, darkenAmount, saturateAmount, desaturateAmount, rotateAmount]);
|
||||
|
||||
const handleComplement = async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user