diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bafe75fd..e05a3c1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.2 (2026-03-01) + +### Bug Fixes + +- Fix trackpad pinch-to-zoom on Safari macOS + ## 0.2.1 (2026-03-01) ### UI diff --git a/components.d.ts b/components.d.ts index 81a14cb96..08622f113 100644 --- a/components.d.ts +++ b/components.d.ts @@ -63,7 +63,6 @@ declare module 'vue' { IconLucideLink: typeof import('~icons/lucide/link')['default'] IconLucideLoaderCircle: typeof import('~icons/lucide/loader-circle')['default'] IconLucideMessageCircle: typeof import('~icons/lucide/message-circle')['default'] - IconLucidePanelLeft: typeof import('~icons/lucide/panel-left')['default'] IconLucidePlus: typeof import('~icons/lucide/plus')['default'] IconLucideRadius: typeof import('~icons/lucide/radius')['default'] IconLucideRotateCcw: typeof import('~icons/lucide/rotate-ccw')['default'] diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index 4ba3eab7e..53f27188c 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2466,7 +2466,7 @@ dependencies = [ [[package]] name = "open_pencil" -version = "0.2.1" +version = "0.2.2" dependencies = [ "font-kit", "serde", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 70f2b6482..76cfde717 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "open_pencil" -version = "0.2.1" +version = "0.2.2" description = "A Tauri App" authors = ["you"] edition = "2021" diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json index 7e14d3a11..66a9d5dde 100644 --- a/desktop/tauri.conf.json +++ b/desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenPencil", - "version": "0.2.1", + "version": "0.2.2", "identifier": "net.dannote.open-pencil", "build": { "beforeDevCommand": "bun run dev", diff --git a/package.json b/package.json index a0ade6d30..6ad64043a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "open-pencil-app", "private": true, - "version": "0.2.1", + "version": "0.2.2", "type": "module", "workspaces": [ "packages/*" diff --git a/packages/cli/package.json b/packages/cli/package.json index b81050cb4..964a674b6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@open-pencil/cli", - "version": "0.2.1", + "version": "0.2.2", "type": "module", "bin": { "open-pencil": "./src/index.ts" diff --git a/packages/core/package.json b/packages/core/package.json index 4b9094df8..36327a07e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@open-pencil/core", - "version": "0.2.1", + "version": "0.2.2", "type": "module", "exports": { ".": { diff --git a/src/composables/use-canvas-input.ts b/src/composables/use-canvas-input.ts index b84e0056b..dcd761251 100644 --- a/src/composables/use-canvas-input.ts +++ b/src/composables/use-canvas-input.ts @@ -1047,6 +1047,7 @@ export function useCanvasInput( } // Touch support for iOS/mobile: single-finger pan, two-finger pinch-zoom + const isTouchDevice = matchMedia('(pointer: coarse)').matches let activeTouches: Touch[] = [] let pinchStartDist = 0 let pinchStartZoom = 0 @@ -1058,6 +1059,7 @@ export function useCanvasInput( } function onTouchStart(e: TouchEvent) { + if (!isTouchDevice) return e.preventDefault() activeTouches = Array.from(e.touches) const canvas = canvasRef.value @@ -1084,6 +1086,7 @@ export function useCanvasInput( } function onTouchMove(e: TouchEvent) { + if (!isTouchDevice) return e.preventDefault() activeTouches = Array.from(e.touches) const canvas = canvasRef.value @@ -1122,6 +1125,7 @@ export function useCanvasInput( } function onTouchEnd(e: TouchEvent) { + if (!isTouchDevice) return e.preventDefault() activeTouches = Array.from(e.touches) @@ -1155,6 +1159,31 @@ export function useCanvasInput( useEventListener(canvasRef, 'touchend', onTouchEnd, { passive: false }) useEventListener(canvasRef, 'touchcancel', onTouchEnd, { passive: false }) + // Safari macOS: trackpad pinch-to-zoom uses gesture events, not wheel+ctrlKey + let gestureStartZoom = 1 + useEventListener(canvasRef, 'gesturestart' as keyof HTMLElementEventMap, (e: Event) => { + e.preventDefault() + gestureStartZoom = store.state.zoom + }, { passive: false }) + useEventListener(canvasRef, 'gesturechange' as keyof HTMLElementEventMap, (e: Event) => { + e.preventDefault() + const ge = e as GestureEvent + const canvas = canvasRef.value + if (!canvas) return + const rect = canvas.getBoundingClientRect() + const sx = (ge.clientX ?? rect.width / 2) - rect.left + const sy = (ge.clientY ?? rect.height / 2) - rect.top + const newZoom = Math.max(0.02, Math.min(256, gestureStartZoom * ge.scale)) + const zoomRatio = newZoom / store.state.zoom + store.state.panX = sx - (sx - store.state.panX) * zoomRatio + store.state.panY = sy - (sy - store.state.panY) * zoomRatio + store.state.zoom = newZoom + store.requestRepaint() + }, { passive: false }) + useEventListener(canvasRef, 'gestureend' as keyof HTMLElementEventMap, (e: Event) => { + e.preventDefault() + }, { passive: false }) + return { drag, cursorOverride diff --git a/src/global.d.ts b/src/global.d.ts index 98bcec77c..05d349b83 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,3 +1,10 @@ +interface GestureEvent extends UIEvent { + scale: number + rotation: number + clientX: number + clientY: number +} + interface FilePickerAcceptType { description: string accept: Record diff --git a/src/views/EditorView.vue b/src/views/EditorView.vue index ace2460a6..541d4518c 100644 --- a/src/views/EditorView.vue +++ b/src/views/EditorView.vue @@ -37,10 +37,7 @@ useEventListener( { passive: false } ) -// Prevent Safari's native pinch-zoom gesture on the page -useEventListener(document, 'gesturestart', (e: Event) => e.preventDefault(), { passive: false }) -useEventListener(document, 'gesturechange', (e: Event) => e.preventDefault(), { passive: false }) -useEventListener(document, 'gestureend', (e: Event) => e.preventDefault(), { passive: false }) + const params = useUrlSearchParams('history') const showChrome = !('no-chrome' in params)