Completed crosshair, slider, and scroll hooks.

This commit is contained in:
Jay
2025-07-23 11:05:47 -04:00
parent d224f335e9
commit 051c6602b5
7 changed files with 794 additions and 129 deletions
+167
View File
@@ -0,0 +1,167 @@
import { useEffect, useState, useRef } from "react";
import type { Dispatch, SetStateAction } from "react";
import type { CartesianSpace } from "../types";
import { minmax } from "../util";
if (typeof TouchEvent === "undefined") {
// @ts-ignore - intentionally creating global
window.TouchEvent = window.MouseEvent;
}
function isTouchEvent(event: Event): event is TouchEvent {
return "touches" in event;
}
export function useCrosshair({
origin,
dimensions,
setXPosition,
setYPosition,
}: {
origin: CartesianSpace;
dimensions: CartesianSpace;
setXPosition: Dispatch<SetStateAction<number>>;
setYPosition: Dispatch<SetStateAction<number>>;
}) {
const [isDragging, setIsDragging] = useState(false);
const crosshairRef = useRef<HTMLDivElement>(null);
// Construct event handler refs
// Prevents unnecessary function recreation
const calculatePositionsRef = useRef((_: MouseEvent | TouchEvent) => {});
const startCrosshairInteractionRef = useRef(
(_: MouseEvent | TouchEvent) => {},
);
const processCrosshairInteractionRef = useRef(
(_: MouseEvent | TouchEvent) => {},
);
const endCrosshairInteractionRef = useRef((_: MouseEvent | TouchEvent) => {});
// Store dependencies as refs
// Always use latest values
const originRef = useRef(origin);
const dimensionsRef = useRef(dimensions);
useEffect(() => {
originRef.current = origin;
dimensionsRef.current = dimensions;
}, [origin, dimensions]);
// Update handler functions when dependencies change via reference
useEffect(() => {
calculatePositionsRef.current = (event: MouseEvent | TouchEvent) => {
const orig = originRef.current;
const dims = dimensionsRef.current;
const clientX = isTouchEvent(event)
? event.touches[0].clientX
: event.clientX;
const clientY = isTouchEvent(event)
? event.touches[0].clientY
: event.clientY;
const xPos = minmax(clientX - orig.x, 0, dims.x - 1);
const yPos = minmax(clientY - orig.y, 0, dims.y - 1);
setXPosition(xPos);
setYPosition(yPos);
};
startCrosshairInteractionRef.current = (event: MouseEvent | TouchEvent) => {
event.preventDefault();
calculatePositionsRef.current(event);
setIsDragging(true);
if (!isTouchEvent(event)) {
document.addEventListener(
"mousemove",
processCrosshairInteractionRef.current,
);
document.addEventListener(
"mouseup",
endCrosshairInteractionRef.current,
{ passive: true },
);
} else {
document.addEventListener(
"touchmove",
processCrosshairInteractionRef.current,
);
document.addEventListener(
"touchend",
endCrosshairInteractionRef.current,
{ passive: true },
);
document.addEventListener(
"touchcancel",
endCrosshairInteractionRef.current,
{ passive: true },
);
}
};
processCrosshairInteractionRef.current = (
event: MouseEvent | TouchEvent,
) => {
event.preventDefault();
calculatePositionsRef.current(event);
};
endCrosshairInteractionRef.current = (event: MouseEvent | TouchEvent) => {
setIsDragging(false);
if (!isTouchEvent(event)) {
document.removeEventListener(
"mousemove",
processCrosshairInteractionRef.current,
);
document.removeEventListener(
"mouseup",
endCrosshairInteractionRef.current,
);
} else {
document.removeEventListener(
"touchmove",
processCrosshairInteractionRef.current,
);
document.removeEventListener(
"touchend",
endCrosshairInteractionRef.current,
);
document.removeEventListener(
"touchcancel",
endCrosshairInteractionRef.current,
);
}
};
}, []);
// Set up entry listeners
useEffect(() => {
const currentRef = crosshairRef.current;
if (currentRef) {
currentRef.addEventListener(
"mousedown",
startCrosshairInteractionRef.current,
);
currentRef.addEventListener(
"touchstart",
startCrosshairInteractionRef.current,
);
}
return () => {
if (currentRef) {
currentRef.removeEventListener(
"mousedown",
startCrosshairInteractionRef.current,
);
currentRef.removeEventListener(
"touchstart",
startCrosshairInteractionRef.current,
);
}
};
}, []);
return { crosshairRef, isDragging };
}
+71
View File
@@ -1,3 +1,6 @@
import { useState, useRef, useEffect } from "react";
import type { RefObject } from "react";
export function handleScroll(
prevLength: number,
scrollDelta: number,
@@ -18,3 +21,71 @@ export function handleScroll(
return newLength;
}
type ScrollHandler = () => void;
export function useScroll<T extends HTMLElement>({
targetRef,
onScrollUp,
onScrollDown,
deltaYMultiplier: deltaYMultiplier = 1,
}: {
targetRef: RefObject<T | null>;
onScrollUp: ScrollHandler;
onScrollDown: ScrollHandler;
deltaYMultiplier?: number;
}) {
const [_, setScrollLength] = useState(0);
const onScrollUpRef = useRef(onScrollUp);
const onScrollDownRef = useRef(onScrollDown);
const deltaYMultiplierRef = useRef(deltaYMultiplier);
useEffect(() => {
onScrollUpRef.current = onScrollUp;
onScrollDownRef.current = onScrollDown;
deltaYMultiplierRef.current = deltaYMultiplier;
}, [onScrollUp, onScrollDown, deltaYMultiplier]);
const handleWheelEventRef = useRef((_: WheelEvent) => {});
useEffect(() => {
handleWheelEventRef.current = (event: WheelEvent) => {
event.preventDefault();
setScrollLength((prev) =>
handleScroll(
prev,
event.deltaY * deltaYMultiplierRef.current,
onScrollDownRef.current,
onScrollUpRef.current,
),
);
};
}, []);
function addScrollListener() {
const currentRef = targetRef.current;
if (currentRef) {
currentRef.addEventListener("wheel", handleWheelEventRef.current);
}
}
function removeScrollListener() {
const currentRef = targetRef.current;
if (currentRef) {
currentRef.removeEventListener("wheel", handleWheelEventRef.current);
}
}
useEffect(() => {
return () => {
removeScrollListener();
};
}, []);
return {
addScrollListener,
removeScrollListener,
};
}
+171 -92
View File
@@ -2,13 +2,30 @@ import { useState, useRef, useEffect } from "react";
import type { Dispatch, SetStateAction, RefObject } from "react";
import { minmax } from "../util";
import type { CartesianSpace } from "../types";
import { handleScroll } from "./scroll";
import { useScroll } from "./scroll";
if (typeof TouchEvent === "undefined") {
// @ts-ignore - intentionally creating global
window.TouchEvent = window.MouseEvent;
}
function isTouchEvent(event: Event): event is TouchEvent {
return "touches" in event;
}
export enum Direction {
HORIZONTAL = "horizontal",
VERTICAL = "vertical",
}
function chooseValueByDirection(
direction: Direction,
xValue: number,
yValue: number,
) {
return direction === Direction.HORIZONTAL ? xValue : yValue;
}
export function useSlider({
direction,
origin,
@@ -22,107 +39,169 @@ export function useSlider({
}): { sliderRef: RefObject<HTMLDivElement | null>; isDragging: boolean } {
const [isDragging, setIsDragging] = useState(false);
const sliderRef = useRef<HTMLDivElement>(null);
const [_, setScrollLength] = useState(0);
function calculatePosition(event: MouseEvent | TouchEvent) {
const clientCoord =
event instanceof TouchEvent
? direction === Direction.HORIZONTAL
? event.touches[0].clientX
: event.touches[0].clientY
: direction === Direction.HORIZONTAL
? event.clientX
: event.clientY;
const positionValue = minmax(
clientCoord - (direction === Direction.HORIZONTAL ? origin.x : origin.y),
0,
direction === Direction.HORIZONTAL ? dimensions.x : dimensions.y,
);
setPosition(positionValue);
}
// Construct event handler refs
// Prevents unnecessary function recreation
const calculatePositionRef = useRef((_: MouseEvent | TouchEvent) => {});
const startSliderInteractionRef = useRef((_: MouseEvent | TouchEvent) => {});
const processSliderInteractionRef = useRef(
(_: MouseEvent | TouchEvent) => {},
);
const endSliderInteractionRef = useRef((_: MouseEvent | TouchEvent) => {});
function startSliderInteraction(event: MouseEvent | TouchEvent) {
event.preventDefault();
calculatePosition(event);
setIsDragging(true);
if (event instanceof MouseEvent) {
document.addEventListener("mousemove", processSliderInteraction);
document.addEventListener("mouseup", endSliderInteraction);
} else {
document.addEventListener("touchmove", processSliderInteraction);
document.addEventListener("touchend", endSliderInteraction);
document.addEventListener("touchcancel", endSliderInteraction);
}
}
function processSliderInteraction(event: MouseEvent | TouchEvent) {
event.preventDefault();
calculatePosition(event);
}
function endSliderInteraction(event: MouseEvent | TouchEvent) {
event.preventDefault();
setIsDragging(false);
if (event instanceof MouseEvent) {
document.removeEventListener("mousemove", processSliderInteraction);
document.removeEventListener("mouseup", endSliderInteraction);
} else {
document.removeEventListener("touchmove", processSliderInteraction);
document.removeEventListener("touchend", endSliderInteraction);
document.removeEventListener("touchcancel", endSliderInteraction);
}
}
function adjustPositionWithScroll(event: WheelEvent) {
event.preventDefault();
setScrollLength((prev) =>
handleScroll(
prev,
direction === Direction.HORIZONTAL ? event.deltaY : -event.deltaY,
() =>
setPosition((prev: number) =>
minmax(
prev - 1,
0,
direction === Direction.HORIZONTAL ? dimensions.x : dimensions.y,
),
),
() =>
setPosition((prev: number) =>
minmax(
prev + 1,
0,
direction === Direction.HORIZONTAL ? dimensions.x : dimensions.y,
),
),
),
);
}
// Store dependencies as refs
// Always use latest values
const directionRef = useRef(direction);
const originRef = useRef(origin);
const dimensionsRef = useRef(dimensions);
useEffect(() => {
if (sliderRef.current) {
sliderRef.current.addEventListener("wheel", adjustPositionWithScroll);
sliderRef.current.addEventListener("mousedown", startSliderInteraction);
sliderRef.current.addEventListener("touchstart", startSliderInteraction);
}
directionRef.current = direction;
originRef.current = origin;
dimensionsRef.current = dimensions;
}, [direction, origin, dimensions]);
return () => {
if (sliderRef.current) {
sliderRef.current.removeEventListener(
"wheel",
adjustPositionWithScroll,
// Setup scroll handlers
const handleScrollUp = () => {
const dir = directionRef.current;
const dims = dimensionsRef.current;
setPosition((prev: number) =>
minmax(prev - 1, 0, chooseValueByDirection(dir, dims.x, dims.y)),
);
};
const handleScrollDown = () => {
const dir = directionRef.current;
const dims = dimensionsRef.current;
setPosition((prev: number) =>
minmax(prev + 1, 0, chooseValueByDirection(dir, dims.x, dims.y)),
);
};
// Initialize scroll handling
const { addScrollListener, removeScrollListener } = useScroll({
targetRef: sliderRef,
onScrollUp: handleScrollUp,
onScrollDown: handleScrollDown,
});
// Update handler functions when dependencies change via reference
useEffect(() => {
calculatePositionRef.current = (event: MouseEvent | TouchEvent) => {
const dir = directionRef.current;
const orig = originRef.current;
const dims = dimensionsRef.current;
const clientCoord = isTouchEvent(event)
? chooseValueByDirection(
dir,
event.touches[0].clientX,
event.touches[0].clientY,
)
: chooseValueByDirection(dir, event.clientX, event.clientY);
const positionValue = minmax(
clientCoord - chooseValueByDirection(dir, orig.x, orig.y),
0,
chooseValueByDirection(dir, dims.x, dims.y),
);
setPosition(positionValue);
};
startSliderInteractionRef.current = (event: MouseEvent | TouchEvent) => {
event.preventDefault();
calculatePositionRef.current(event);
setIsDragging(true);
if (!isTouchEvent(event)) {
document.addEventListener(
"mousemove",
processSliderInteractionRef.current,
);
sliderRef.current.removeEventListener(
"mousedown",
startSliderInteraction,
document.addEventListener("mouseup", endSliderInteractionRef.current, {
passive: true,
});
} else {
document.addEventListener(
"touchmove",
processSliderInteractionRef.current,
);
sliderRef.current.removeEventListener(
"touchstart",
startSliderInteraction,
document.addEventListener("touchend", endSliderInteractionRef.current, {
passive: true,
});
document.addEventListener(
"touchcancel",
endSliderInteractionRef.current,
{
passive: true,
},
);
}
};
});
processSliderInteractionRef.current = (event: MouseEvent | TouchEvent) => {
event.preventDefault();
calculatePositionRef.current(event);
};
endSliderInteractionRef.current = (event: MouseEvent | TouchEvent) => {
setIsDragging(false);
if (!isTouchEvent(event)) {
document.removeEventListener(
"mousemove",
processSliderInteractionRef.current,
);
document.removeEventListener(
"mouseup",
endSliderInteractionRef.current,
);
} else {
document.removeEventListener(
"touchmove",
processSliderInteractionRef.current,
);
document.removeEventListener(
"touchend",
endSliderInteractionRef.current,
);
document.removeEventListener(
"touchcancel",
endSliderInteractionRef.current,
);
}
};
}, []);
// Set up entry listeners
useEffect(() => {
const currentRef = sliderRef.current;
if (currentRef) {
addScrollListener();
currentRef.addEventListener(
"mousedown",
startSliderInteractionRef.current,
);
currentRef.addEventListener(
"touchstart",
startSliderInteractionRef.current,
);
}
return () => {
if (currentRef) {
removeScrollListener();
currentRef.removeEventListener(
"mousedown",
startSliderInteractionRef.current,
);
currentRef.removeEventListener(
"touchstart",
startSliderInteractionRef.current,
);
}
};
}, []);
return { sliderRef, isDragging };
}
+175
View File
@@ -0,0 +1,175 @@
import { useState, useRef, useEffect } from "react";
import { useCrosshair } from "../crosshair";
import type { CartesianSpace } from "../../types";
// Test Fixtures
function TestSquare() {
const [origin, setOrigin] = useState<CartesianSpace>({ x: 0, y: 0 });
const [dimensions, setDimensions] = useState<CartesianSpace>({ x: 0, y: 0 });
const [xPosition, setXPosition] = useState(0);
const [yPosition, setYPosition] = useState(0);
const { crosshairRef, isDragging } = useCrosshair({
origin,
dimensions,
setXPosition,
setYPosition,
});
const boundaryRef = useRef<HTMLDivElement>(null);
const xCrosshairRef = useRef<HTMLSpanElement>(null);
const yCrosshairRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
const element = boundaryRef.current;
if (element) {
const rect = element.getBoundingClientRect();
setOrigin({ x: rect.left, y: rect.top });
setDimensions({ x: rect.width, y: rect.height });
}
}, []);
return (
<>
<div
ref={crosshairRef}
data-cy="crosshair-container"
style={{
position: "relative",
width: 250,
height: 250,
margin: 50,
border: "3px solid black",
cursor: "crosshair",
}}
>
<div
ref={boundaryRef}
data-cy="boundary"
style={{
position: "absolute",
top: 12,
bottom: 12,
right: 12,
left: 12,
background: "rgb(200, 200, 200)",
}}
></div>
<span
ref={yCrosshairRef}
data-cy="y-crosshair"
style={{
position: "absolute",
width: 250,
height: 25,
top: yPosition,
left: 0,
background: "rgba(255, 0, 0, 0.5)",
pointerEvents: "none",
}}
/>
<span
ref={xCrosshairRef}
data-cy="x-crosshair"
style={{
position: "absolute",
width: 25,
height: 250,
top: 0,
left: xPosition,
background: "rgba(0, 255, 0, 0.5)",
pointerEvents: "none",
}}
/>
</div>
<p>
X Position: <span data-cy="x-position-display">{xPosition}</span>px
<br />Y Position: <span data-cy="y-position-display">{yPosition}</span>
px
</p>
<p>
Is Dragging:{" "}
<span data-cy="is-dragging-display">
{isDragging ? "True" : "False"}
</span>
</p>
</>
);
}
// Tests
const assertPosition = (
expectedXPosition: number,
expectedYPosition: number,
) => {
cy.dataCy("x-position-display").should("contain", expectedXPosition);
cy.dataCy("y-position-display").should("contain", expectedYPosition);
cy.dataCy("x-crosshair").should("have.css", "left", `${expectedXPosition}px`);
cy.dataCy("y-crosshair").should("have.css", "top", `${expectedYPosition}px`);
};
const triggerMouseEvent = (eventType: string, x: number, y: number) => {
cy.dataCy("crosshair-container").trigger(eventType, {
clientX: x,
clientY: y,
eventConstructor: "MouseEvent",
});
};
const triggerTouchEvent = (eventType: string, x: number, y: number) => {
cy.dataCy("crosshair-container").trigger(eventType, {
clientX: x,
clientY: y,
touches: [{ clientX: x, clientY: y }],
eventConstructor: "TouchEvent",
});
};
describe("crosshair tests", () => {
beforeEach(() => {
cy.mount(<TestSquare />);
});
it("moves the crosshairs with mouse events", () => {
assertPosition(0, 0);
triggerMouseEvent("mousedown", 70, 60);
assertPosition(0, 0);
triggerMouseEvent("mousemove", 180, 180);
assertPosition(107, 115);
triggerMouseEvent("mousemove", 500, 500);
assertPosition(225, 225);
triggerMouseEvent("mousemove", 0, 250);
assertPosition(0, 185);
triggerMouseEvent("mouseup", 0, 250);
assertPosition(0, 185);
});
it("moves the crosshairs with touch events", () => {
assertPosition(0, 0);
triggerTouchEvent("touchstart", 70, 60);
assertPosition(0, 0);
triggerTouchEvent("touchmove", 180, 180);
assertPosition(107, 115);
triggerTouchEvent("touchmove", 500, 500);
assertPosition(225, 225);
triggerTouchEvent("touchmove", 0, 250);
assertPosition(0, 185);
triggerTouchEvent("touchend", 0, 250);
assertPosition(0, 185);
});
});
View File
+205 -27
View File
@@ -4,18 +4,17 @@ import type { CartesianSpace } from "../../types";
// Test Fixtures
function printMeasurements(name: string, rect: DOMRect | undefined) {
console.log(
`${name} Measurements: (${rect?.left}, ${rect?.top}), (${rect?.width}, ${rect?.height})`,
);
}
function TestSlider() {
function TestSlider({
direction = Direction.HORIZONTAL,
}: {
direction?: Direction;
}) {
const [origin, setOrigin] = useState<CartesianSpace>({ x: 0, y: 0 });
const [dimensions, setDimensions] = useState<CartesianSpace>({ x: 0, y: 0 });
const [position, setPosition] = useState(0);
const [sliderValue, setSliderValue] = useState(0);
const { sliderRef, isDragging } = useSlider({
direction: Direction.HORIZONTAL,
direction,
origin,
dimensions,
setPosition,
@@ -32,10 +31,23 @@ function TestSlider() {
const rect = element.getBoundingClientRect();
setOrigin({ x: rect.left, y: rect.top });
setDimensions({ x: rect.width, y: rect!.height });
setDimensions({ x: rect.width, y: rect.height });
}
}, []);
useEffect(() => {
const maxValue =
direction == Direction.HORIZONTAL ? dimensions.x : dimensions.y;
if (maxValue > 0) {
const percentage = parseFloat(((position / maxValue) * 100).toFixed(3));
setSliderValue(percentage);
} else {
setSliderValue(0);
}
}, [dimensions, direction, position]);
const isHorizontal = direction === Direction.HORIZONTAL;
return (
<>
<div
@@ -43,8 +55,8 @@ function TestSlider() {
ref={sliderRef}
style={{
position: "relative",
width: 375,
height: 50,
width: isHorizontal ? 300 : 50,
height: isHorizontal ? 50 : 300,
border: "3px solid black",
margin: 50,
cursor: "pointer",
@@ -55,10 +67,10 @@ function TestSlider() {
ref={railRef}
style={{
position: "absolute",
left: 25,
right: 25,
top: 0,
bottom: 0,
left: isHorizontal ? 25 : 0,
right: isHorizontal ? 25 : 0,
top: isHorizontal ? 0 : 25,
bottom: isHorizontal ? 0 : 25,
background: "rgb(200,200,200)",
}}
/>
@@ -67,8 +79,9 @@ function TestSlider() {
ref={handleRef}
style={{
position: "absolute",
left: position,
top: 0,
...(isHorizontal
? { left: position, top: 0 }
: { left: 0, top: position }),
width: 50,
height: 50,
background: "rgba(255,0,0,0.5)",
@@ -78,23 +91,18 @@ function TestSlider() {
</div>
<p>
Position: <span data-cy="position-display">{position}</span>px
<br /> Value: <span data-cy="value-display">{sliderValue}</span>
</p>
</>
);
}
// Tests
describe("Slider Hook Tests", () => {
beforeEach(() => {
cy.mount(<TestSlider />);
});
function createTestUtils(isHorizontal = true) {
const assertPosition = (expectedPosition: number) => {
cy.dataCy("position-display").should("contain", expectedPosition);
cy.dataCy("slider-handle").should(
"have.css",
"left",
isHorizontal ? "left" : "top",
`${expectedPosition}px`,
);
};
@@ -107,7 +115,49 @@ describe("Slider Hook Tests", () => {
});
};
it("moves the slider with mouse drag.", () => {
const triggerTouchEvent = (eventType: string, x: number, y: number) => {
cy.dataCy("slider-container").trigger(eventType, {
clientX: x,
clientY: y,
touches: [{ clientX: x, clientY: y }],
eventConstructor: "TouchEvent",
});
};
const triggerWheelEvent = (deltaY: number) => {
cy.dataCy("slider-container").trigger("wheel", {
deltaY,
eventConstructor: "WheelEvent",
});
};
return {
assertPosition,
triggerMouseEvent,
triggerTouchEvent,
triggerWheelEvent,
};
}
const isTouchSupported = () => {
return typeof TouchEvent !== "undefined";
};
// Tests
describe("horizontal slider hook tests", () => {
beforeEach(() => {
cy.mount(<TestSlider direction={Direction.HORIZONTAL} />);
});
const {
assertPosition,
triggerMouseEvent,
triggerTouchEvent,
triggerWheelEvent,
} = createTestUtils(true);
it("moves the slider with mouse events.", () => {
assertPosition(0);
triggerMouseEvent("mousedown", 86, 53);
@@ -117,7 +167,7 @@ describe("Slider Hook Tests", () => {
assertPosition(64);
triggerMouseEvent("mousemove", 500, 500);
assertPosition(325);
assertPosition(250);
triggerMouseEvent("mousemove", 250, 250);
assertPosition(164);
@@ -125,4 +175,132 @@ describe("Slider Hook Tests", () => {
triggerMouseEvent("mouseup", 250, 250);
assertPosition(164);
});
if (isTouchSupported()) {
it("moves the slider with touch events.", () => {
assertPosition(0);
triggerTouchEvent("touchstart", 86, 53);
assertPosition(0);
triggerTouchEvent("touchmove", 150, 150);
assertPosition(64);
triggerTouchEvent("touchmove", 500, 500);
assertPosition(250);
triggerTouchEvent("touchmove", 250, 250);
assertPosition(164);
triggerTouchEvent("touchend", 250, 250);
assertPosition(164);
});
} else {
console.log("Skipping Unsupported Touch Event Tests");
}
it("moves the slider with mouse wheel scrolling", () => {
assertPosition(0);
triggerWheelEvent(100);
assertPosition(1);
triggerWheelEvent(100);
assertPosition(2);
triggerWheelEvent(-100);
assertPosition(1);
// Many smaller scrolls, to simulate touchpads
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
assertPosition(4);
});
});
describe("vertical slider hook tests", () => {
beforeEach(() => {
cy.mount(<TestSlider direction={Direction.VERTICAL} />);
});
const {
assertPosition,
triggerMouseEvent,
triggerTouchEvent,
triggerWheelEvent,
} = createTestUtils(false);
it("moves the slider with mouse drag.", () => {
assertPosition(0);
triggerMouseEvent("mousedown", 86, 53);
assertPosition(0);
triggerMouseEvent("mousemove", 150, 150);
assertPosition(72);
triggerMouseEvent("mousemove", 500, 500);
assertPosition(250);
triggerMouseEvent("mousemove", 250, 250);
assertPosition(172);
triggerMouseEvent("mouseup", 250, 250);
assertPosition(172);
});
if (isTouchSupported()) {
it("moves the slider with touch events.", () => {
assertPosition(0);
triggerTouchEvent("touchstart", 86, 53);
assertPosition(0);
triggerTouchEvent("touchmove", 150, 150);
assertPosition(72);
triggerTouchEvent("touchmove", 500, 500);
assertPosition(250);
triggerTouchEvent("touchmove", 250, 250);
assertPosition(172);
triggerTouchEvent("touchend", 250, 250);
assertPosition(172);
});
} else {
console.log("Skipping Unsupported Touch Event Tests");
}
it("moves the slider with mouse wheel scrolling", () => {
assertPosition(0);
triggerWheelEvent(100);
assertPosition(1);
triggerWheelEvent(100);
assertPosition(2);
triggerWheelEvent(-100);
assertPosition(1);
// Many smaller scrolls, to simulate touchpads
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
triggerWheelEvent(20);
assertPosition(4);
});
});