From f59690d51230fc44c11ce1111ba974f184f5bca8 Mon Sep 17 00:00:00 2001 From: Ignacio Ildefonso de Miguel Ruano Date: Wed, 4 Feb 2026 11:16:16 +0100 Subject: [PATCH] feat: adding trackpad mode --- package-lock.json | 10 +- .../components/ZoomSettings/ZoomSettings.tsx | 19 ++ .../fossflow-lib/src/config/zoomSettings.ts | 6 +- packages/fossflow-lib/src/i18n/bn-BD.ts | 5 +- packages/fossflow-lib/src/i18n/en-US.ts | 4 +- packages/fossflow-lib/src/i18n/es-ES.ts | 4 +- packages/fossflow-lib/src/i18n/fr-FR.ts | 5 +- packages/fossflow-lib/src/i18n/hi-IN.ts | 5 +- packages/fossflow-lib/src/i18n/id-ID.ts | 5 +- packages/fossflow-lib/src/i18n/it-IT.ts | 5 +- packages/fossflow-lib/src/i18n/pl-PL.ts | 5 +- packages/fossflow-lib/src/i18n/pt-BR.ts | 5 +- packages/fossflow-lib/src/i18n/ru-RU.ts | 5 +- packages/fossflow-lib/src/i18n/tr-TR.ts | 5 +- packages/fossflow-lib/src/i18n/zh-CN.ts | 5 +- .../src/interaction/useInteractionManager.ts | 165 +++++++++++++----- .../fossflow-lib/src/types/isoflowProps.ts | 2 + 17 files changed, 195 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index d3305dbe..31d3249f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fossflow-monorepo", - "version": "1.9.2", + "version": "1.10.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fossflow-monorepo", - "version": "1.9.2", + "version": "1.10.3", "workspaces": [ "packages/*" ], @@ -18835,7 +18835,7 @@ } }, "packages/fossflow-app": { - "version": "1.9.2", + "version": "1.10.3", "dependencies": { "@isoflow/isopacks": "^0.0.10", "fossflow": "*", @@ -18883,7 +18883,7 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" }, "packages/fossflow-backend": { - "version": "1.9.2", + "version": "1.10.3", "dependencies": { "cors": "^2.8.5", "dotenv": "^16.6.1", @@ -18896,7 +18896,7 @@ }, "packages/fossflow-lib": { "name": "fossflow", - "version": "1.9.2", + "version": "1.10.3", "license": "MIT", "dependencies": { "@emotion/react": "^11.14.0", diff --git a/packages/fossflow-lib/src/components/ZoomSettings/ZoomSettings.tsx b/packages/fossflow-lib/src/components/ZoomSettings/ZoomSettings.tsx index 87506b14..3f612a4d 100644 --- a/packages/fossflow-lib/src/components/ZoomSettings/ZoomSettings.tsx +++ b/packages/fossflow-lib/src/components/ZoomSettings/ZoomSettings.tsx @@ -48,6 +48,25 @@ export const ZoomSettings = () => { } /> + + handleToggle('trackpadMode')} + /> + } + label={ + + + {locale.settings.zoom.trackpadMode} + + + {locale.settings.zoom.trackpadModeDesc} + + + } + /> diff --git a/packages/fossflow-lib/src/config/zoomSettings.ts b/packages/fossflow-lib/src/config/zoomSettings.ts index cbf9ef9f..d210f5aa 100644 --- a/packages/fossflow-lib/src/config/zoomSettings.ts +++ b/packages/fossflow-lib/src/config/zoomSettings.ts @@ -1,9 +1,13 @@ export interface ZoomSettings { // Zoom behavior zoomToCursor: boolean; + // Trackpad mode: scroll=pan, pinch=zoom + trackpadMode: boolean; } export const DEFAULT_ZOOM_SETTINGS: ZoomSettings = { // Default to zoom-to-cursor for better UX - zoomToCursor: true + zoomToCursor: true, + // Default to mouse mode (scroll=zoom) for backwards compatibility + trackpadMode: false }; diff --git a/packages/fossflow-lib/src/i18n/bn-BD.ts b/packages/fossflow-lib/src/i18n/bn-BD.ts index 2633c329..ee36c489 100644 --- a/packages/fossflow-lib/src/i18n/bn-BD.ts +++ b/packages/fossflow-lib/src/i18n/bn-BD.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "মাউস হুইল ব্যবহার করার সময় জুম আচরণ কনফিগার করুন।", zoomToCursor: "কার্সারে জুম করুন", - zoomToCursorDesc: "সক্রিয় থাকলে, মাউস কার্সার অবস্থানে কেন্দ্রীভূত জুম ইন/আউট। নিষ্ক্রিয় থাকলে, জুম ক্যানভাসে কেন্দ্রীভূত।" + zoomToCursorDesc: "সক্রিয় থাকলে, মাউস কার্সার অবস্থানে কেন্দ্রীভূত জুম ইন/আউট। নিষ্ক্রিয় থাকলে, জুম ক্যানভাসে কেন্দ্রীভূত।", + + trackpadMode: "ট্র্যাকপ্যাড মোড", + trackpadModeDesc: "সক্ষম থাকলে: দুই আঙুলের স্ক্রল ক্যানভাস প্যান করে, পিঞ্চ ব্রাউজারকে প্রভাবিত না করে জুম করে। নিষ্ক্রিয় থাকলে: স্ক্রল হুইল ক্যানভাস জুম করে (ডিফল্ট মাউস আচরণ)।" }, hotkeys: { title: "শর্টকাট সেটিংস", diff --git a/packages/fossflow-lib/src/i18n/en-US.ts b/packages/fossflow-lib/src/i18n/en-US.ts index b9b76e9c..d170f67d 100644 --- a/packages/fossflow-lib/src/i18n/en-US.ts +++ b/packages/fossflow-lib/src/i18n/en-US.ts @@ -119,7 +119,9 @@ const locale: LocaleProps = { zoom: { description: "Configure zoom behavior when using the mouse wheel.", zoomToCursor: "Zoom to Cursor", - zoomToCursorDesc: "When enabled, zoom in/out centered on the mouse cursor position. When disabled, zoom is centered on the canvas." + zoomToCursorDesc: "When enabled, zoom in/out centered on the mouse cursor position. When disabled, zoom is centered on the canvas.", + trackpadMode: "Trackpad Mode", + trackpadModeDesc: "When enabled: two-finger scroll pans the canvas, pinch zooms without affecting the browser. When disabled: scroll wheel zooms the canvas (default mouse behavior)." }, hotkeys: { title: "Hotkey Settings", diff --git a/packages/fossflow-lib/src/i18n/es-ES.ts b/packages/fossflow-lib/src/i18n/es-ES.ts index 3e376baf..52c89984 100644 --- a/packages/fossflow-lib/src/i18n/es-ES.ts +++ b/packages/fossflow-lib/src/i18n/es-ES.ts @@ -119,7 +119,9 @@ const locale: LocaleProps = { zoom: { description: "Configura el comportamiento del zoom al usar la rueda del ratón.", zoomToCursor: "Zoom al cursor", - zoomToCursorDesc: "Cuando está habilitado, el zoom se centra en la posición del cursor del ratón. Cuando está deshabilitado, el zoom se centra en el lienzo." + zoomToCursorDesc: "Cuando está habilitado, el zoom se centra en la posición del cursor del ratón. Cuando está deshabilitado, el zoom se centra en el lienzo.", + trackpadMode: "Modo Trackpad", + trackpadModeDesc: "Cuando está activado: desplazamiento con dos dedos mueve el lienzo, pellizcar hace zoom sin afectar el navegador. Cuando está desactivado: la rueda hace zoom en el lienzo (comportamiento predeterminado del ratón)." }, hotkeys: { title: "Configuración de atajos", diff --git a/packages/fossflow-lib/src/i18n/fr-FR.ts b/packages/fossflow-lib/src/i18n/fr-FR.ts index ea96e22b..c773b24a 100644 --- a/packages/fossflow-lib/src/i18n/fr-FR.ts +++ b/packages/fossflow-lib/src/i18n/fr-FR.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "Configurer le comportement du zoom lors de l'utilisation de la molette de la souris.", zoomToCursor: "Zoom sur le curseur", - zoomToCursorDesc: "Lorsqu'il est activé, le zoom est centré sur la position du curseur de la souris. Lorsqu'il est désactivé, le zoom est centré sur le canevas." + zoomToCursorDesc: "Lorsqu'il est activé, le zoom est centré sur la position du curseur de la souris. Lorsqu'il est désactivé, le zoom est centré sur le canevas.", + + trackpadMode: "Mode Trackpad", + trackpadModeDesc: "Lorsqu'il est activé : le défilement à deux doigts déplace le canevas, le pincement effectue un zoom sans affecter le navigateur. Lorsqu'il est désactivé : la molette effectue un zoom sur le canevas (comportement par défaut de la souris)." }, hotkeys: { title: "Paramètres des raccourcis", diff --git a/packages/fossflow-lib/src/i18n/hi-IN.ts b/packages/fossflow-lib/src/i18n/hi-IN.ts index 87cad003..14e9d38c 100644 --- a/packages/fossflow-lib/src/i18n/hi-IN.ts +++ b/packages/fossflow-lib/src/i18n/hi-IN.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "माउस व्हील का उपयोग करते समय ज़ूम व्यवहार को कॉन्फ़िगर करें।", zoomToCursor: "कर्सर पर ज़ूम करें", - zoomToCursorDesc: "सक्षम होने पर, माउस कर्सर की स्थिति पर केंद्रित ज़ूम इन/आउट। अक्षम होने पर, ज़ूम कैनवास पर केंद्रित होता है।" + zoomToCursorDesc: "सक्षम होने पर, माउस कर्सर की स्थिति पर केंद्रित ज़ूम इन/आउट। अक्षम होने पर, ज़ूम कैनवास पर केंद्रित होता है।", + + trackpadMode: "ट्रैकपैड मोड", + trackpadModeDesc: "सक्षम होने पर: दो उंगलियों से स्क्रॉल करने पर कैनवास पैन होता है, पिंच करने पर ब्राउज़र को प्रभावित किए बिना ज़ूम होता है। अक्षम होने पर: माउस व्हील से कैनवास ज़ूम होता है (डिफ़ॉल्ट माउस व्यवहार)।" }, hotkeys: { title: "शॉर्टकट सेटिंग्स", diff --git a/packages/fossflow-lib/src/i18n/id-ID.ts b/packages/fossflow-lib/src/i18n/id-ID.ts index 2cef3dc5..0ab53b8f 100644 --- a/packages/fossflow-lib/src/i18n/id-ID.ts +++ b/packages/fossflow-lib/src/i18n/id-ID.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "Konfigurasi perilaku zoom saat menggunakan roda mouse.", zoomToCursor: "Zoom ke Kursor", - zoomToCursorDesc: "Saat diaktifkan, zoom masuk/keluar terpusat pada posisi kursor mouse. Saat dinonaktifkan, zoom terpusat pada kanvas." + zoomToCursorDesc: "Saat diaktifkan, zoom masuk/keluar terpusat pada posisi kursor mouse. Saat dinonaktifkan, zoom terpusat pada kanvas.", + + trackpadMode: "Mode Trackpad", + trackpadModeDesc: "Saat diaktifkan: gulir dua jari menggeser kanvas, cubit untuk memperbesar tanpa mempengaruhi browser. Saat dinonaktifkan: roda gulir memperbesar kanvas (perilaku mouse default)." }, hotkeys: { title: "Pengaturan Pintasan", diff --git a/packages/fossflow-lib/src/i18n/it-IT.ts b/packages/fossflow-lib/src/i18n/it-IT.ts index 6c797d75..d7d150d5 100644 --- a/packages/fossflow-lib/src/i18n/it-IT.ts +++ b/packages/fossflow-lib/src/i18n/it-IT.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "Configura il comportamento dello zoom quando si usa la rotella del mouse.", zoomToCursor: "Zoom sul cursore", - zoomToCursorDesc: "Se abilitato, ingrandisci o riduci centrando sul cursore del mouse. Se disabilitato, lo zoom è centrato sulla tela." + zoomToCursorDesc: "Se abilitato, ingrandisci o riduci centrando sul cursore del mouse. Se disabilitato, lo zoom è centrato sulla tela.", + + trackpadMode: "Modalità Trackpad", + trackpadModeDesc: "Quando abilitato: lo scorrimento con due dita sposta la tela, il pizzico ingrandisce senza influire sul browser. Quando disabilitato: la rotellina del mouse ingrandisce la tela (comportamento predefinito del mouse)." }, hotkeys: { title: "Impostazioni scorciatoie", diff --git a/packages/fossflow-lib/src/i18n/pl-PL.ts b/packages/fossflow-lib/src/i18n/pl-PL.ts index 528c1b26..d7352655 100644 --- a/packages/fossflow-lib/src/i18n/pl-PL.ts +++ b/packages/fossflow-lib/src/i18n/pl-PL.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "Skonfiguruj zachowanie powiększania podczas korzystania z kółka myszy.", zoomToCursor: "Powiększ do kursora", - zoomToCursorDesc: "Po włączeniu funkcji powiększanie/pomniejszanie odbywa się w oparciu o położenie kursora myszy. Po wyłączeniu funkcji Powiększ do kursora odbywa się w oparciu o położenie obszaru roboczego." + zoomToCursorDesc: "Po włączeniu funkcji powiększanie/pomniejszanie odbywa się w oparciu o położenie kursora myszy. Po wyłączeniu funkcji Powiększ do kursora odbywa się w oparciu o położenie obszaru roboczego.", + + trackpadMode: "Tryb Trackpada", + trackpadModeDesc: "Po włączeniu: przewijanie dwoma palcami przesuwa płótno, uszczypnięcie powiększa bez wpływu na przeglądarkę. Po wyłączeniu: kółko myszy powiększa płótno (domyślne zachowanie myszy)." }, hotkeys: { title: "Ustawienia skrótów klawiszowych", diff --git a/packages/fossflow-lib/src/i18n/pt-BR.ts b/packages/fossflow-lib/src/i18n/pt-BR.ts index 568023c9..b1bcb85a 100644 --- a/packages/fossflow-lib/src/i18n/pt-BR.ts +++ b/packages/fossflow-lib/src/i18n/pt-BR.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "Configurar o comportamento do zoom ao usar a roda do mouse.", zoomToCursor: "Zoom no cursor", - zoomToCursorDesc: "Quando habilitado, o zoom é centralizado na posição do cursor do mouse. Quando desabilitado, o zoom é centralizado na tela." + zoomToCursorDesc: "Quando habilitado, o zoom é centralizado na posição do cursor do mouse. Quando desabilitado, o zoom é centralizado na tela.", + + trackpadMode: "Modo Trackpad", + trackpadModeDesc: "Quando ativado: rolagem com dois dedos move a tela, pinça com zoom sem afetar o navegador. Quando desativado: a roda do mouse amplia a tela (comportamento padrão do mouse)." }, hotkeys: { title: "Configurações de atalhos", diff --git a/packages/fossflow-lib/src/i18n/ru-RU.ts b/packages/fossflow-lib/src/i18n/ru-RU.ts index 104477d6..1a4b4634 100644 --- a/packages/fossflow-lib/src/i18n/ru-RU.ts +++ b/packages/fossflow-lib/src/i18n/ru-RU.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "Настройте поведение масштабирования при использовании колесика мыши.", zoomToCursor: "Масштабировать к курсору", - zoomToCursorDesc: "При включении масштабирование центрируется на позиции курсора мыши. При выключении масштабирование центрируется на холсте." + zoomToCursorDesc: "При включении масштабирование центрируется на позиции курсора мыши. При выключении масштабирование центрируется на холсте.", + + trackpadMode: "Режим трекпада", + trackpadModeDesc: "При включении: прокрутка двумя пальцами перемещает холст, сжатие масштабирует без влияния на браузер. При выключении: колесико мыши масштабирует холст (стандартное поведение мыши)." }, hotkeys: { title: "Настройки горячих клавиш", diff --git a/packages/fossflow-lib/src/i18n/tr-TR.ts b/packages/fossflow-lib/src/i18n/tr-TR.ts index b6b42fb4..114d9f6a 100644 --- a/packages/fossflow-lib/src/i18n/tr-TR.ts +++ b/packages/fossflow-lib/src/i18n/tr-TR.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "Fare tekerleği kullanılırken yakınlaştırma davranışını yapılandırın.", zoomToCursor: "İmlece Yakınlaştır", - zoomToCursorDesc: "Etkinleştirildiğinde, fare imleci konumunda merkezlenmiş olarak yakınlaştırır/uzaklaştırır. Devre dışı bırakıldığında, yakınlaştırma tuvalde merkezlenir." + zoomToCursorDesc: "Etkinleştirildiğinde, fare imleci konumunda merkezlenmiş olarak yakınlaştırır/uzaklaştırır. Devre dışı bırakıldığında, yakınlaştırma tuvalde merkezlenir.", + + trackpadMode: "Trackpad Modu", + trackpadModeDesc: "Etkinleştirildiğinde: iki parmakla kaydırma tuvali hareket ettirir, kıstırma tarayıcıyı etkilemeden yakınlaştırır. Devre dışı bırakıldığında: fare tekerleği tuvali yakınlaştırır (varsayılan fare davranışı)." }, hotkeys: { title: "Kısayol Tuşu Ayarları", diff --git a/packages/fossflow-lib/src/i18n/zh-CN.ts b/packages/fossflow-lib/src/i18n/zh-CN.ts index 92366943..28ec8290 100644 --- a/packages/fossflow-lib/src/i18n/zh-CN.ts +++ b/packages/fossflow-lib/src/i18n/zh-CN.ts @@ -119,7 +119,10 @@ const locale: LocaleProps = { zoom: { description: "配置使用鼠标滚轮时的缩放行为。", zoomToCursor: "光标缩放", - zoomToCursorDesc: "启用时,以鼠标光标位置为中心进行缩放。禁用时,以画布中心进行缩放。" + zoomToCursorDesc: "启用时,以鼠标光标位置为中心进行缩放。禁用时,以画布中心进行缩放。", + + trackpadMode: "触控板模式", + trackpadModeDesc: "启用时:双指滚动可平移画布,双指捏合可缩放且不影响浏览器。禁用时:鼠标滚轮缩放画布(默认鼠标行为)。" }, hotkeys: { title: "快捷键设置", diff --git a/packages/fossflow-lib/src/interaction/useInteractionManager.ts b/packages/fossflow-lib/src/interaction/useInteractionManager.ts index 4a753823..42374bfe 100644 --- a/packages/fossflow-lib/src/interaction/useInteractionManager.ts +++ b/packages/fossflow-lib/src/interaction/useInteractionManager.ts @@ -422,56 +422,127 @@ export const useInteractionManager = () => { }; const onScroll = (e: WheelEvent) => { - const zoomToCursor = uiState.zoomSettings.zoomToCursor; - const oldZoom = uiState.zoom; + const trackpadMode = uiState.zoomSettings.trackpadMode; + const isPinchGesture = e.ctrlKey; // Browsers set ctrlKey during pinch gestures + + // Trackpad Mode: distinguish between scroll (pan) and pinch (zoom) + if (trackpadMode) { + if (isPinchGesture) { + // Pinch gesture: Zoom the canvas + // Prevent browser zoom + e.preventDefault(); + + const zoomToCursor = uiState.zoomSettings.zoomToCursor; + const oldZoom = uiState.zoom; + + // Calculate new zoom level + let newZoom: number; + if (e.deltaY > 0) { + newZoom = decrementZoom(oldZoom); + } else { + newZoom = incrementZoom(oldZoom); + } + + // If zoom didn't change (at min/max), no need to adjust scroll + if (newZoom === oldZoom) { + return; + } - // Calculate new zoom level - let newZoom: number; - if (e.deltaY > 0) { - newZoom = decrementZoom(oldZoom); + if (zoomToCursor && rendererRef.current && rendererSize) { + // Get mouse position relative to the renderer viewport + const rect = rendererRef.current.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + + // Calculate mouse position relative to viewport center + const mouseRelativeToCenterX = mouseX - rendererSize.width / 2; + const mouseRelativeToCenterY = mouseY - rendererSize.height / 2; + + // The point under the cursor in world space (before zoom) + const worldX = (mouseRelativeToCenterX - uiState.scroll.position.x) / oldZoom; + const worldY = (mouseRelativeToCenterY - uiState.scroll.position.y) / oldZoom; + + // After zooming, keep the same world point under the cursor + const newScrollX = mouseRelativeToCenterX - worldX * newZoom; + const newScrollY = mouseRelativeToCenterY - worldY * newZoom; + + // Apply zoom and adjusted scroll together + uiState.actions.setZoom(newZoom); + uiState.actions.setScroll({ + position: { + x: newScrollX, + y: newScrollY + }, + offset: uiState.scroll.offset + }); + } else { + // Zoom to center + uiState.actions.setZoom(newZoom); + } + } else { + // Two-finger scroll: Pan the canvas + // Prevent browser back/forward navigation on horizontal swipe + e.preventDefault(); + + const panSpeed = 1.0; // Adjust if needed for sensitivity + + uiState.actions.setScroll({ + position: { + x: uiState.scroll.position.x - e.deltaX * panSpeed, + y: uiState.scroll.position.y - e.deltaY * panSpeed + }, + offset: uiState.scroll.offset + }); + } } else { - newZoom = incrementZoom(oldZoom); - } + // Mouse Mode (default): scroll = zoom (original behavior) + const zoomToCursor = uiState.zoomSettings.zoomToCursor; + const oldZoom = uiState.zoom; + + // Calculate new zoom level + let newZoom: number; + if (e.deltaY > 0) { + newZoom = decrementZoom(oldZoom); + } else { + newZoom = incrementZoom(oldZoom); + } - // If zoom didn't change (at min/max), no need to adjust scroll - if (newZoom === oldZoom) { - return; - } + // If zoom didn't change (at min/max), no need to adjust scroll + if (newZoom === oldZoom) { + return; + } - if (zoomToCursor && rendererRef.current && rendererSize) { - // Get mouse position relative to the renderer viewport - const rect = rendererRef.current.getBoundingClientRect(); - const mouseX = e.clientX - rect.left; - const mouseY = e.clientY - rect.top; - - // Calculate mouse position relative to viewport center - const mouseRelativeToCenterX = mouseX - rendererSize.width / 2; - const mouseRelativeToCenterY = mouseY - rendererSize.height / 2; - - // The point under the cursor in world space (before zoom) - // World coordinates = (screen coordinates - scroll offset) / zoom - const worldX = (mouseRelativeToCenterX - uiState.scroll.position.x) / oldZoom; - const worldY = (mouseRelativeToCenterY - uiState.scroll.position.y) / oldZoom; - - // After zooming, to keep the same world point under the cursor: - // screen coordinates = world coordinates * newZoom + scroll offset - // We want: mouseRelativeToCenterX = worldX * newZoom + newScrollX - // Therefore: newScrollX = mouseRelativeToCenterX - worldX * newZoom - const newScrollX = mouseRelativeToCenterX - worldX * newZoom; - const newScrollY = mouseRelativeToCenterY - worldY * newZoom; - - // Apply zoom and adjusted scroll together - uiState.actions.setZoom(newZoom); - uiState.actions.setScroll({ - position: { - x: newScrollX, - y: newScrollY - }, - offset: uiState.scroll.offset - }); - } else { - // Original behavior: zoom to center - uiState.actions.setZoom(newZoom); + if (zoomToCursor && rendererRef.current && rendererSize) { + // Get mouse position relative to the renderer viewport + const rect = rendererRef.current.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + + // Calculate mouse position relative to viewport center + const mouseRelativeToCenterX = mouseX - rendererSize.width / 2; + const mouseRelativeToCenterY = mouseY - rendererSize.height / 2; + + // The point under the cursor in world space (before zoom) + const worldX = (mouseRelativeToCenterX - uiState.scroll.position.x) / oldZoom; + const worldY = (mouseRelativeToCenterY - uiState.scroll.position.y) / oldZoom; + + // After zooming, keep the same world point under the cursor + const newScrollX = mouseRelativeToCenterX - worldX * newZoom; + const newScrollY = mouseRelativeToCenterY - worldY * newZoom; + + // Apply zoom and adjusted scroll together + uiState.actions.setZoom(newZoom); + uiState.actions.setScroll({ + position: { + x: newScrollX, + y: newScrollY + }, + offset: uiState.scroll.offset + }); + } else { + // Original behavior: zoom to center + uiState.actions.setZoom(newZoom); + } } }; @@ -482,7 +553,7 @@ export const useInteractionManager = () => { el.addEventListener('touchstart', onTouchStart); el.addEventListener('touchmove', onTouchMove); el.addEventListener('touchend', onTouchEnd); - uiState.rendererEl?.addEventListener('wheel', onScroll, { passive: true }); + uiState.rendererEl?.addEventListener('wheel', onScroll, { passive: false }); return () => { el.removeEventListener('mousemove', onMouseEvent); diff --git a/packages/fossflow-lib/src/types/isoflowProps.ts b/packages/fossflow-lib/src/types/isoflowProps.ts index 9b072db8..b76b8dd7 100644 --- a/packages/fossflow-lib/src/types/isoflowProps.ts +++ b/packages/fossflow-lib/src/types/isoflowProps.ts @@ -127,6 +127,8 @@ export interface LocaleProps { description: string; zoomToCursor: string; zoomToCursorDesc: string; + trackpadMode: string; + trackpadModeDesc: string; }; hotkeys: { title: string;