mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
|
const defaultOptions: Options = {
|
|
stepDown: (s) => s / 1.5,
|
|
stepUp: (s) => s * 1.5,
|
|
min: 0.13,
|
|
max: 2.25,
|
|
};
|
|
|
|
interface Options {
|
|
/**
|
|
* Allows you to specify how the step up will be handled so you can do fractional steps based on previous value.
|
|
*/
|
|
stepUp: (scale: number) => number;
|
|
stepDown: (scale: number) => number;
|
|
|
|
/**
|
|
* Set max and min values. If stepUp/down overshoots these bounds this will return min or max but internal scale value
|
|
* will still be what ever the step functions returned last.
|
|
*/
|
|
min?: number;
|
|
max?: number;
|
|
}
|
|
|
|
/**
|
|
* Keeps state and returns handlers that can be used to implement zooming functionality ideally by using it with
|
|
* 'transform: scale'. It returns handler for manual buttons with zoom in/zoom out function and a ref that can be
|
|
* used to zoom in/out with mouse wheel.
|
|
*/
|
|
export function useZoom({ stepUp, stepDown, min, max } = defaultOptions) {
|
|
const ref = useRef<HTMLElement>(null);
|
|
const [scale, setScale] = useState(1);
|
|
|
|
const onStepUp = useCallback(() => {
|
|
if (scale < (max ?? Infinity)) {
|
|
setScale(stepUp(scale));
|
|
}
|
|
}, [scale, stepUp, max]);
|
|
|
|
const onStepDown = useCallback(() => {
|
|
if (scale > (min ?? -Infinity)) {
|
|
setScale(stepDown(scale));
|
|
}
|
|
}, [scale, stepDown, min]);
|
|
|
|
const onWheel = useCallback(
|
|
function (event: Event) {
|
|
// Seems like typing for the addEventListener is lacking a bit
|
|
const wheelEvent = event as WheelEvent;
|
|
|
|
// Only do this with special key pressed similar to how google maps work.
|
|
// TODO: I would guess this won't work very well with touch right now
|
|
if (wheelEvent.ctrlKey || wheelEvent.metaKey) {
|
|
event.preventDefault();
|
|
|
|
setScale(Math.min(Math.max(min ?? -Infinity, scale + Math.min(wheelEvent.deltaY, 2) * -0.01), max ?? Infinity));
|
|
|
|
if (wheelEvent.deltaY < 0) {
|
|
const newScale = scale + Math.max(wheelEvent.deltaY, -4) * -0.015;
|
|
setScale(Math.max(min ?? -Infinity, newScale));
|
|
} else if (wheelEvent.deltaY > 0) {
|
|
const newScale = scale + Math.min(wheelEvent.deltaY, 4) * -0.015;
|
|
setScale(Math.min(max ?? Infinity, newScale));
|
|
}
|
|
}
|
|
},
|
|
[min, max, scale]
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!ref.current) {
|
|
return;
|
|
}
|
|
|
|
const zoomRef = ref.current;
|
|
|
|
// Adds listener for wheel event, we need the passive: false to be able to prevent default otherwise that
|
|
// cannot be used with passive listeners.
|
|
zoomRef.addEventListener('wheel', onWheel, { passive: false });
|
|
return () => {
|
|
if (zoomRef) {
|
|
zoomRef.removeEventListener('wheel', onWheel);
|
|
}
|
|
};
|
|
}, [onWheel]);
|
|
|
|
return {
|
|
onStepUp,
|
|
onStepDown,
|
|
scale: Math.max(Math.min(scale, max ?? Infinity), min ?? -Infinity),
|
|
isMax: scale >= (max ?? Infinity),
|
|
isMin: scale <= (min ?? -Infinity),
|
|
ref,
|
|
};
|
|
}
|