📱(frontend) add touch detection

We rely on the size of the screen to determine
if we are on a touch device, but this is not
always accurate, for example on laptops
when we resize the window to a smaller size,
it could be misinterpreted as a touch device.
To improve this, we add detection for touch input
so we can more accurately determine if the user
is using touch or mouse input.
This commit is contained in:
Anthony LC
2026-01-07 12:03:14 +01:00
parent 2be5cda88e
commit 64ce2d449a
3 changed files with 47 additions and 6 deletions
@@ -52,14 +52,17 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
const { theme } = useCunninghamTheme();
const { replace } = useRouter();
const initializeResizeListener = useResponsiveStore(
(state) => state.initializeResizeListener,
);
const { initializeResizeListener, initializeInputDetection } =
useResponsiveStore();
useEffect(() => {
return initializeResizeListener();
}, [initializeResizeListener]);
useEffect(() => {
return initializeInputDetection();
}, [initializeInputDetection]);
/**
* Update the global router replace function
* This allows us to use the router replace function globally
@@ -35,7 +35,7 @@ export const BlockNoteToolbar = () => {
const [onConfirm, setOnConfirm] = useState<() => void | Promise<void>>();
const { t } = useTranslation();
const { data: conf } = useConfig();
const { isMobile, isTablet } = useResponsiveStore();
const { isTablet, isInputTouch } = useResponsiveStore();
const toolbarItems = useMemo(() => {
let toolbarItems = getFormattingToolbarItems([
@@ -96,7 +96,7 @@ export const BlockNoteToolbar = () => {
return (
<>
{isMobile || isTablet ? (
{isInputTouch && isTablet ? (
<MobileFormattingToolbarController
formattingToolbar={formattingToolbar}
/>
@@ -133,7 +133,6 @@ const MobileFormattingToolbarController = ({
return (
<Box
$margin="auto"
$position="absolute"
$css={`
& > div {
@@ -1,6 +1,7 @@
import { create } from 'zustand';
export type ScreenSize = 'small-mobile' | 'mobile' | 'tablet' | 'desktop';
export type InputMethod = 'touch' | 'mouse' | 'unknown';
export interface UseResponsiveStore {
isMobile: boolean;
@@ -10,7 +11,11 @@ export interface UseResponsiveStore {
screenWidth: number;
setScreenSize: (size: ScreenSize) => void;
isDesktop: boolean;
isTouchCapable: boolean;
isInputTouch: boolean;
inputMethod: InputMethod;
initializeResizeListener: () => () => void;
initializeInputDetection: () => () => void;
}
const initialState = {
@@ -20,6 +25,9 @@ const initialState = {
isDesktop: false,
screenSize: 'desktop' as ScreenSize,
screenWidth: 0,
isTouchCapable: false,
isInputTouch: false,
inputMethod: 'unknown' as InputMethod,
};
export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
@@ -29,6 +37,9 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
isTablet: initialState.isTablet,
screenSize: initialState.screenSize,
screenWidth: initialState.screenWidth,
isTouchCapable: initialState.isTouchCapable,
isInputTouch: initialState.isInputTouch,
inputMethod: initialState.inputMethod,
setScreenSize: (size: ScreenSize) => set(() => ({ screenSize: size })),
initializeResizeListener: () => {
const resizeHandler = () => {
@@ -84,4 +95,32 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
window.removeEventListener('resize', debouncedResizeHandler);
};
},
initializeInputDetection: () => {
// Detect if device has touch capability
const isTouchCapable =
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
// @ts-ignore - for older browsers
navigator.msMaxTouchPoints > 0;
set({ isTouchCapable });
// Track actual input method being used
const handleTouchStart = () => {
set({ inputMethod: 'touch', isInputTouch: true });
};
const handleMouseMove = () => {
set({ inputMethod: 'mouse', isInputTouch: false });
};
// Listen for first interaction to determine input method
window.addEventListener('touchstart', handleTouchStart, { once: false });
window.addEventListener('mousemove', handleMouseMove, { once: false });
return () => {
window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('mousemove', handleMouseMove);
};
},
}));