import { createContext, useContext, useEffect, useState } from "react"; import type { ReactNode } from "react"; type PossibleValues = "mouse" | "touch"; interface Props { children: ReactNode; } const InputDeviceContext = createContext( "missing-input-device-provider" ); const DEFAULT_DEVICE: PossibleValues = "mouse"; export const InputDeviceProvider: React.FC = ({ children }) => { const [device, setDevice] = useState(""); useEffect(() => { let lastEvent = 0; const mouse = () => { if (Date.now() - lastEvent < 350) { // browsers send fake mouse events on touch after a possible delay. That's why if mouse event happens // quickly after (any) last event we will ignore that // we don't expect devices to be changing superfast, so we don't need to care that we can miss something // there is a better way to do that, but it's in experimental state at moment of writing return; } lastEvent = Date.now(); setDevice("mouse"); }; const touch = () => { lastEvent = Date.now(); setDevice("touch"); }; window.addEventListener("mousemove", mouse); window.addEventListener("wheel", mouse); window.addEventListener("touchmove", touch); window.addEventListener("touchend", touch); return () => { window.removeEventListener("wheel", mouse); window.removeEventListener("mousemove", mouse); window.removeEventListener("touchmove", touch); window.removeEventListener("touchend", touch); }; }, []); return {children}; }; export const useInputDevice = (overrideDefault?: T): PossibleValues | T => { const contextValue = useContext(InputDeviceContext); if (contextValue === "missing-input-device-provider") { throw new Error("InputDeviceProvider is missing in the tree"); } return contextValue || overrideDefault || DEFAULT_DEVICE; };