Skip to content

Commit

Permalink
[@mantine/core] AngleSlider: Migrate to use-radial-move hook
Browse files Browse the repository at this point in the history
  • Loading branch information
rtivital committed Nov 26, 2024
1 parent 2357f45 commit 0c1e304
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 98 deletions.
126 changes: 31 additions & 95 deletions packages/@mantine/core/src/components/AngleSlider/AngleSlider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useCallback, useRef } from 'react';
import { clamp, useMergedRef, useUncontrolled } from '@mantine/hooks';
import { normalizeRadialValue, useMergedRef, useRadialMove, useUncontrolled } from '@mantine/hooks';
import {
Box,
BoxProps,
Expand Down Expand Up @@ -39,6 +38,12 @@ export interface AngleSliderProps
/** Called after the selection is finished */
onChangeEnd?: (value: number) => void;

/** Called in `onMouseDown` and `onTouchStart` events */
onScrubStart?: () => void;

/** Called in `onMouseUp` and `onTouchEnd` events */
onScrubEnd?: () => void;

/** Determines whether the label should be displayed inside the slider, `true` by default */
withLabel?: boolean;

Expand Down Expand Up @@ -74,30 +79,6 @@ export type AngleSliderFactory = Factory<{
vars: AngleSliderCssVariables;
}>;

function radiansToDegrees(radians: number) {
return radians * (180 / Math.PI);
}

function getElementCenter(element: HTMLElement) {
const rect = element.getBoundingClientRect();
return [rect.left + rect.width / 2, rect.top + rect.height / 2];
}

function getAngle(coordinates: [number, number], element: HTMLElement) {
const center = getElementCenter(element);
const x = coordinates[0] - center[0];
const y = coordinates[1] - center[1];
const deg = radiansToDegrees(Math.atan2(x, y)) + 180;
return 360 - deg;
}

function normalize(degree: number, step: number) {
const clamped = clamp(degree, 0, 360);
const high = Math.ceil(clamped / step);
const low = Math.round(clamped / step);
return high >= clamped / step ? (high * step === 360 ? 0 : high * step) : low * step;
}

const defaultProps: Partial<AngleSliderProps> = {
step: 1,
withLabel: true,
Expand Down Expand Up @@ -136,34 +117,20 @@ export const AngleSlider = factory<AngleSliderFactory>((_props, ref) => {
hiddenInputProps,
'aria-label': ariaLabel,
tabIndex,
onScrubStart,
onScrubEnd,
...others
} = props;

const rootRef = useRef<HTMLDivElement>(null);
const [_value, setValue] = useUncontrolled({
value,
defaultValue,
finalValue: 0,
onChange,
});

const getStyles = useStyles<AngleSliderFactory>({
name: 'AngleSlider',
classes,
props,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver,
});

const update = (event: MouseEvent, done = false) => {
const update = (val: number) => {
if (rootRef.current) {
const deg = getAngle([event.clientX, event.clientY], rootRef.current);
const val = normalize(deg, step || 1);
const newValue =
restrictToMarks && Array.isArray(marks)
? findClosestNumber(
Expand All @@ -173,67 +140,42 @@ export const AngleSlider = factory<AngleSliderFactory>((_props, ref) => {
: val;

setValue(newValue);
done && onChangeEnd?.(newValue);
}
};

const handleMouseMove = useCallback((event: MouseEvent) => {
update(event);
}, []);

const handleMouseUp = useCallback((event: MouseEvent) => {
update(event, true);
endTracking();
}, []);

const handleTouchMove = useCallback((event: TouchEvent) => {
event.preventDefault();
update(event.touches[0] as any);
}, []);

const handleTouchEnd = useCallback((event: TouchEvent) => {
update(event.changedTouches[0] as any, true);
endTracking();
}, []);

const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {
event.preventDefault();
onTouchStart?.(event);
beginTracking();
};

const beginTracking = () => {
document.addEventListener('mousemove', handleMouseMove, false);
document.addEventListener('mouseup', handleMouseUp, false);
document.addEventListener('touchmove', handleTouchMove, { passive: false });
document.addEventListener('touchend', handleTouchEnd, false);
};

const endTracking = () => {
document.removeEventListener('mousemove', handleMouseMove, false);
document.removeEventListener('mouseup', handleMouseUp, false);
document.removeEventListener('touchmove', handleTouchMove, false);
document.removeEventListener('touchend', handleTouchEnd, false);
};
const { ref: rootRef } = useRadialMove(update, {
step,
onChangeEnd,
onScrubStart,
onScrubEnd,
});

const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
onMouseDown?.(event);
beginTracking();
};
const getStyles = useStyles<AngleSliderFactory>({
name: 'AngleSlider',
classes,
props,
className,
style,
classNames,
styles,
unstyled,
vars,
varsResolver,
});

const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (disabled) {
return;
}

if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
const normalized = normalize(_value - step!, step!);
const normalized = normalizeRadialValue(_value - step!, step!);
setValue(normalized);
onChangeEnd?.(normalized);
}

if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
const normalized = normalize(_value + step!, step!);
const normalized = normalizeRadialValue(_value + step!, step!);
setValue(normalized);
onChangeEnd?.(normalized);
}
Expand All @@ -258,13 +200,7 @@ export const AngleSlider = factory<AngleSliderFactory>((_props, ref) => {
));

return (
<Box
ref={useMergedRef(ref, rootRef)}
{...getStyles('root', { focusable: true })}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
{...others}
>
<Box ref={useMergedRef(ref, rootRef)} {...getStyles('root', { focusable: true })} {...others}>
{marksItems && marksItems.length > 0 && <div {...getStyles('marks')}>{marksItems}</div>}

{withLabel && (
Expand Down
2 changes: 1 addition & 1 deletion packages/@mantine/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export { useThrottledValue } from './use-throttled-value/use-throttled-value';
export { useIsFirstRender } from './use-is-first-render/use-is-first-render';
export { useOrientation } from './use-orientation/use-orientation';
export { useFetch } from './use-fetch/use-fetch';
export { useRadialMove } from './use-radial-move/use-radial-move';
export { useRadialMove, normalizeRadialValue } from './use-radial-move/use-radial-move';

export type { UseMovePosition } from './use-move/use-move';
export type { OS } from './use-os/use-os';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function getDigitsAfterDot(value: number) {
return value.toString().split('.')[1]?.length || 0;
}

function normalize(degree: number, step: number) {
export function normalizeRadialValue(degree: number, step: number) {
const clamped = clamp(degree, 0, 360);
const high = Math.ceil(clamped / step);
const low = Math.round(clamped / step);
Expand Down Expand Up @@ -67,7 +67,7 @@ export function useRadialMove<T extends HTMLElement = HTMLDivElement>(
if (ref.current) {
ref.current.style.userSelect = 'none';
const deg = getAngle([event.clientX, event.clientY], ref.current);
const newValue = normalize(deg, step || 1);
const newValue = normalizeRadialValue(deg, step || 1);

onChange(newValue);
done && onChangeEnd?.(newValue);
Expand Down

0 comments on commit 0c1e304

Please sign in to comment.