Fix trackpad pinch-to-zoom on Safari macOS
This commit is contained in:
parent
13a95bf517
commit
5e209eca1c
|
|
@ -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
1
components.d.ts
vendored
|
|
@ -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
2
desktop/Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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/*"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
".": {
|
".": {
|
||||||
|
|
|
||||||
|
|
@ -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
7
src/global.d.ts
vendored
|
|
@ -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[]>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue