From 64ce2d449a1cc3445cb5ee66f1b2a4b0d72345ac Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 7 Jan 2026 11:57:18 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=B1(frontend)=20add=20touch=20detectio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../apps/impress/src/core/AppProvider.tsx | 9 +++-- .../BlockNoteToolBar/BlockNoteToolbar.tsx | 5 +-- .../impress/src/stores/useResponsiveStore.tsx | 39 +++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/frontend/apps/impress/src/core/AppProvider.tsx b/src/frontend/apps/impress/src/core/AppProvider.tsx index b52f8fd2..ac5f12e7 100644 --- a/src/frontend/apps/impress/src/core/AppProvider.tsx +++ b/src/frontend/apps/impress/src/core/AppProvider.tsx @@ -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 diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx index 4fa98db9..f5f9eee3 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/BlockNoteToolbar.tsx @@ -35,7 +35,7 @@ export const BlockNoteToolbar = () => { const [onConfirm, setOnConfirm] = useState<() => void | Promise>(); 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 ? ( @@ -133,7 +133,6 @@ const MobileFormattingToolbarController = ({ return ( div { diff --git a/src/frontend/apps/impress/src/stores/useResponsiveStore.tsx b/src/frontend/apps/impress/src/stores/useResponsiveStore.tsx index 7da3133c..cc9ea092 100644 --- a/src/frontend/apps/impress/src/stores/useResponsiveStore.tsx +++ b/src/frontend/apps/impress/src/stores/useResponsiveStore.tsx @@ -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((set) => ({ @@ -29,6 +37,9 @@ export const useResponsiveStore = create((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((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); + }; + }, }));