Added color reducer. Performed state management refactor to prevent circular behavior.

This commit is contained in:
Jay
2025-08-06 14:26:55 -04:00
parent c27a5258d3
commit e011bd0763
21 changed files with 2592 additions and 799 deletions
+86 -39
View File
@@ -1,12 +1,15 @@
import { useCallback, useEffect, useRef, useState } from "react";
import type { Dispatch, SetStateAction } from "react";
import type { CartesianSpace } from "@/types";
import type { CartesianSpace, Range, Setter } from "@/types";
import { Direction } from "@/types";
import {
chooseValueByDirection,
extractEventCoordinates,
isLeftMouseButton,
isTouchEvent,
minmax,
positionToValue,
valueToPosition,
} from "@/util";
import { useScroll } from "./scroll";
@@ -16,19 +19,6 @@ if (typeof TouchEvent === "undefined") {
window.TouchEvent = window.MouseEvent;
}
export enum Direction {
HORIZONTAL = "horizontal",
VERTICAL = "vertical",
}
function chooseValueByDirection(
direction: Direction,
xValue: number,
yValue: number,
) {
return direction === Direction.HORIZONTAL ? xValue : yValue;
}
function extractEventCoordinateByDirection(
event: MouseEvent | TouchEvent,
direction: Direction,
@@ -41,43 +31,74 @@ export function useSlider({
direction,
origin,
dimensions,
setPosition,
valueRange,
value,
setValue,
}: {
direction: Direction;
origin: CartesianSpace;
dimensions: CartesianSpace;
setPosition: Dispatch<SetStateAction<number>>;
valueRange: Range;
value: number;
setValue: Setter<number>;
}) {
const [isDragging, setIsDragging] = useState(false);
const sliderRef = useRef<HTMLDivElement>(null);
// Slider UI refs
const directionRef = useRef(direction);
const originRef = useRef(origin);
const dimensionsRef = useRef(dimensions);
// Slider value refs
const setValueRef = useRef(setValue);
const valueRangeRef = useRef(valueRange);
const maxPosition = useRef(0);
// Internal position management
const [position, setPosition] = useState(0);
const positionRef = useRef(0);
useEffect(() => {
directionRef.current = direction;
originRef.current = origin;
dimensionsRef.current = dimensions;
}, [direction, origin, dimensions]);
maxPosition.current = chooseValueByDirection(
direction,
dimensions.x,
dimensions.y,
);
valueRangeRef.current = valueRange;
}, [direction, origin, dimensions, valueRangeRef]);
useEffect(() => {
setValueRef.current = setValue;
}, [setValue]);
useEffect(() => {
positionRef.current = position;
}, [position]);
// Setup drag handlers
const calculatePosition = useCallback(
(event: MouseEvent | TouchEvent) => {
const dir = directionRef.current;
const orig = originRef.current;
const dims = dimensionsRef.current;
const calculatePosition = useCallback((event: MouseEvent | TouchEvent) => {
const dir = directionRef.current;
const orig = originRef.current;
const dims = dimensionsRef.current;
const clientCoord = extractEventCoordinateByDirection(event, dir);
const positionValue = minmax(
clientCoord - chooseValueByDirection(dir, orig.x, orig.y),
0,
chooseValueByDirection(dir, dims.x, dims.y),
);
setPosition(positionValue);
},
[setPosition],
);
const clientCoord = extractEventCoordinateByDirection(event, dir);
const newPosition = minmax(
clientCoord - chooseValueByDirection(dir, orig.x, orig.y),
0,
chooseValueByDirection(dir, dims.x, dims.y),
);
const newValue = positionToValue(
newPosition,
maxPosition.current,
valueRangeRef.current,
);
setValueRef.current(newValue);
}, []);
const handleMove = useCallback(
(event: MouseEvent | TouchEvent) => {
@@ -120,20 +141,37 @@ export function useSlider({
const dims = dimensionsRef.current;
const inc = chooseValueByDirection(dir, 1, -1);
setPosition((prev: number) =>
minmax(prev + inc, 0, chooseValueByDirection(dir, dims.x, dims.y)),
const newPosition = minmax(
positionRef.current + inc,
0,
chooseValueByDirection(dir, dims.x, dims.y),
);
}, [setPosition]);
const newValue = positionToValue(
newPosition,
maxPosition.current,
valueRangeRef.current,
);
setValueRef.current(newValue);
}, []);
const handleScrollDown = useCallback(() => {
const dir = directionRef.current;
const dims = dimensionsRef.current;
const inc = chooseValueByDirection(dir, -1, 1);
setPosition((prev: number) =>
minmax(prev + inc, 0, chooseValueByDirection(dir, dims.x, dims.y)),
const newPosition = minmax(
positionRef.current + inc,
0,
chooseValueByDirection(dir, dims.x, dims.y),
);
}, [setPosition]);
const newValue = positionToValue(
newPosition,
maxPosition.current,
valueRangeRef.current,
);
setValueRef.current(newValue);
}, []);
const { addScrollListener, removeScrollListener } = useScroll({
targetRef: sliderRef,
@@ -141,6 +179,15 @@ export function useSlider({
onScrollDown: handleScrollDown,
});
useEffect(() => {
const newPosition = valueToPosition(
value,
maxPosition.current,
valueRangeRef.current,
);
setPosition(newPosition);
}, [value, setPosition]);
// Set up entry listeners
useEffect(() => {
const currentRef = sliderRef.current;