Fix trackpad pinch-to-zoom on Safari macOS

This commit is contained in:
Danila Poyarkov 2026-03-01 22:07:47 +03:00
parent 13a95bf517
commit 5e209eca1c
11 changed files with 49 additions and 11 deletions

View file

@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.2.2 (2026-03-01)
### Bug Fixes
- Fix trackpad pinch-to-zoom on Safari macOS
## 0.2.1 (2026-03-01) ## 0.2.1 (2026-03-01)
### UI ### UI

1
components.d.ts vendored
View file

@ -63,7 +63,6 @@ declare module 'vue' {
IconLucideLink: typeof import('~icons/lucide/link')['default'] IconLucideLink: typeof import('~icons/lucide/link')['default']
IconLucideLoaderCircle: typeof import('~icons/lucide/loader-circle')['default'] IconLucideLoaderCircle: typeof import('~icons/lucide/loader-circle')['default']
IconLucideMessageCircle: typeof import('~icons/lucide/message-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'] IconLucidePlus: typeof import('~icons/lucide/plus')['default']
IconLucideRadius: typeof import('~icons/lucide/radius')['default'] IconLucideRadius: typeof import('~icons/lucide/radius')['default']
IconLucideRotateCcw: typeof import('~icons/lucide/rotate-ccw')['default'] IconLucideRotateCcw: typeof import('~icons/lucide/rotate-ccw')['default']

2
desktop/Cargo.lock generated
View file

@ -2466,7 +2466,7 @@ dependencies = [
[[package]] [[package]]
name = "open_pencil" name = "open_pencil"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"font-kit", "font-kit",
"serde", "serde",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "open_pencil" name = "open_pencil"
version = "0.2.1" version = "0.2.2"
description = "A Tauri App" description = "A Tauri App"
authors = ["you"] authors = ["you"]
edition = "2021" edition = "2021"

View file

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "OpenPencil", "productName": "OpenPencil",
"version": "0.2.1", "version": "0.2.2",
"identifier": "net.dannote.open-pencil", "identifier": "net.dannote.open-pencil",
"build": { "build": {
"beforeDevCommand": "bun run dev", "beforeDevCommand": "bun run dev",

View file

@ -1,7 +1,7 @@
{ {
"name": "open-pencil-app", "name": "open-pencil-app",
"private": true, "private": true,
"version": "0.2.1", "version": "0.2.2",
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"

View file

@ -1,6 +1,6 @@
{ {
"name": "@open-pencil/cli", "name": "@open-pencil/cli",
"version": "0.2.1", "version": "0.2.2",
"type": "module", "type": "module",
"bin": { "bin": {
"open-pencil": "./src/index.ts" "open-pencil": "./src/index.ts"

View file

@ -1,6 +1,6 @@
{ {
"name": "@open-pencil/core", "name": "@open-pencil/core",
"version": "0.2.1", "version": "0.2.2",
"type": "module", "type": "module",
"exports": { "exports": {
".": { ".": {

View file

@ -1047,6 +1047,7 @@ export function useCanvasInput(
} }
// Touch support for iOS/mobile: single-finger pan, two-finger pinch-zoom // Touch support for iOS/mobile: single-finger pan, two-finger pinch-zoom
const isTouchDevice = matchMedia('(pointer: coarse)').matches
let activeTouches: Touch[] = [] let activeTouches: Touch[] = []
let pinchStartDist = 0 let pinchStartDist = 0
let pinchStartZoom = 0 let pinchStartZoom = 0
@ -1058,6 +1059,7 @@ export function useCanvasInput(
} }
function onTouchStart(e: TouchEvent) { function onTouchStart(e: TouchEvent) {
if (!isTouchDevice) return
e.preventDefault() e.preventDefault()
activeTouches = Array.from(e.touches) activeTouches = Array.from(e.touches)
const canvas = canvasRef.value const canvas = canvasRef.value
@ -1084,6 +1086,7 @@ export function useCanvasInput(
} }
function onTouchMove(e: TouchEvent) { function onTouchMove(e: TouchEvent) {
if (!isTouchDevice) return
e.preventDefault() e.preventDefault()
activeTouches = Array.from(e.touches) activeTouches = Array.from(e.touches)
const canvas = canvasRef.value const canvas = canvasRef.value
@ -1122,6 +1125,7 @@ export function useCanvasInput(
} }
function onTouchEnd(e: TouchEvent) { function onTouchEnd(e: TouchEvent) {
if (!isTouchDevice) return
e.preventDefault() e.preventDefault()
activeTouches = Array.from(e.touches) activeTouches = Array.from(e.touches)
@ -1155,6 +1159,31 @@ export function useCanvasInput(
useEventListener(canvasRef, 'touchend', onTouchEnd, { passive: false }) useEventListener(canvasRef, 'touchend', onTouchEnd, { passive: false })
useEventListener(canvasRef, 'touchcancel', 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 { return {
drag, drag,
cursorOverride cursorOverride

7
src/global.d.ts vendored
View file

@ -1,3 +1,10 @@
interface GestureEvent extends UIEvent {
scale: number
rotation: number
clientX: number
clientY: number
}
interface FilePickerAcceptType { interface FilePickerAcceptType {
description: string description: string
accept: Record<string, string[]> accept: Record<string, string[]>

View file

@ -37,10 +37,7 @@ useEventListener(
{ passive: false } { 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 params = useUrlSearchParams('history')
const showChrome = !('no-chrome' in params) const showChrome = !('no-chrome' in params)