Skip to content

Commit 211a7b2

Browse files
committed
Allow pinch-zoom gesture with dialogs on iOS
tailwindlabs#3602
1 parent d36e77f commit 211a7b2

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

packages/@headlessui-react/src/hooks/document-overflow/handle-ios-locking.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export function handleIOSLocking(): ScrollLockStep<ContainerMetadata> {
8787
// but still allow pinch-to-zoom.
8888
d.style(e.target, 'touchAction', 'pinch-zoom')
8989
}
90+
delete e.target.dataset.initialDistance
9091
}
9192
})
9293

@@ -96,6 +97,28 @@ export function handleIOSLocking(): ScrollLockStep<ContainerMetadata> {
9697
(e) => {
9798
// Check if we are scrolling inside any of the allowed containers, if not let's cancel the event!
9899
if (e.target instanceof HTMLElement) {
100+
if (e.touches.length === 2) {
101+
const [touch1, touch2] = e.touches
102+
const currentDistance = Math.hypot(
103+
touch2.clientX - touch1.clientX,
104+
touch2.clientY - touch1.clientY
105+
)
106+
107+
if (!e.target.dataset.initialDistance) {
108+
e.target.dataset.initialDistance = currentDistance.toString()
109+
return
110+
}
111+
112+
const initialDistance = parseFloat(e.target.dataset.initialDistance)
113+
const distanceChange = currentDistance - initialDistance
114+
115+
if (isFinite(distanceChange) && Math.abs(distanceChange) > 5) {
116+
return
117+
}
118+
} else if (e.touches.length === 1 && e.target.dataset.initialDistance) {
119+
delete e.target.dataset.initialDistance
120+
}
121+
99122
// Some inputs like `<input type=range>` use touch events to
100123
// allow interaction. We should not prevent this event.
101124
if (e.target.tagName === 'INPUT') {
@@ -150,6 +173,18 @@ export function handleIOSLocking(): ScrollLockStep<ContainerMetadata> {
150173
{ passive: false }
151174
)
152175

176+
d.addEventListener(doc, 'touchend', (e) => {
177+
if (e.target instanceof HTMLElement) {
178+
delete e.target.dataset.initialDistance
179+
}
180+
})
181+
182+
d.addEventListener(doc, 'touchcancel', (e) => {
183+
if (e.target instanceof HTMLElement) {
184+
delete e.target.dataset.initialDistance
185+
}
186+
})
187+
153188
// Restore scroll position if a scrollToElement was captured.
154189
d.add(() => {
155190
let newScrollPosition = window.scrollY ?? window.pageYOffset

packages/@headlessui-vue/src/hooks/document-overflow/handle-ios-locking.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export function handleIOSLocking(): ScrollLockStep<ContainerMetadata> {
8787
// but still allow pinch-to-zoom.
8888
d.style(e.target, 'touchAction', 'pinch-zoom')
8989
}
90+
delete e.target.dataset.initialDistance
9091
}
9192
})
9293

@@ -96,6 +97,28 @@ export function handleIOSLocking(): ScrollLockStep<ContainerMetadata> {
9697
(e) => {
9798
// Check if we are scrolling inside any of the allowed containers, if not let's cancel the event!
9899
if (e.target instanceof HTMLElement) {
100+
if (e.touches.length === 2) {
101+
const [touch1, touch2] = e.touches
102+
const currentDistance = Math.hypot(
103+
touch2.clientX - touch1.clientX,
104+
touch2.clientY - touch1.clientY
105+
)
106+
107+
if (!e.target.dataset.initialDistance) {
108+
e.target.dataset.initialDistance = currentDistance.toString()
109+
return
110+
}
111+
112+
const initialDistance = parseFloat(e.target.dataset.initialDistance)
113+
const distanceChange = currentDistance - initialDistance
114+
115+
if (isFinite(distanceChange) && Math.abs(distanceChange) > 5) {
116+
return
117+
}
118+
} else if (e.touches.length === 1 && e.target.dataset.initialDistance) {
119+
delete e.target.dataset.initialDistance
120+
}
121+
99122
// Some inputs like `<input type=range>` use touch events to
100123
// allow interaction. We should not prevent this event.
101124
if (e.target.tagName === 'INPUT') {
@@ -150,6 +173,18 @@ export function handleIOSLocking(): ScrollLockStep<ContainerMetadata> {
150173
{ passive: false }
151174
)
152175

176+
d.addEventListener(doc, 'touchend', (e) => {
177+
if (e.target instanceof HTMLElement) {
178+
delete e.target.dataset.initialDistance
179+
}
180+
})
181+
182+
d.addEventListener(doc, 'touchcancel', (e) => {
183+
if (e.target instanceof HTMLElement) {
184+
delete e.target.dataset.initialDistance
185+
}
186+
})
187+
153188
// Restore scroll position if a scrollToElement was captured.
154189
d.add(() => {
155190
let newScrollPosition = window.scrollY ?? window.pageYOffset

0 commit comments

Comments
 (0)