fix(modal): fix modal not closing in iOS PWA (#29)
- Add cursor:pointer to .modal-overlay so iOS Safari fires click events on the backdrop (iOS ignores clicks on non-interactive divs without it) - Add touchend fallback listener on overlay for belt-and-suspenders iOS support - Enlarge close button from target-sm (32px) to target-md (40px) to meet Apple touch-target guidelines; remove now-redundant ::before expansion - Swipe-to-close now only activates from the top handle zone (< 48px) or when the panel is scrolled to top, preventing accidental dismissal while scrolling form content downward
This commit is contained in:
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.14.2] - 2026-04-06
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Modal: overlay tap now reliably closes the modal on iOS Safari / PWA - added `cursor: pointer` to the overlay (iOS requires this on non-interactive elements to fire click events) and a `touchend` fallback (#29)
|
||||||
|
- Modal: close button enlarged from 32px to 40px to meet Apple's 44px touch-target recommendation (#29)
|
||||||
|
- Modal: swipe-to-close no longer triggers when scrolling content inside the sheet - drag only activates from the top handle zone or when the panel is scrolled to the top (#29)
|
||||||
|
|
||||||
## [0.14.1] - 2026-04-06
|
## [0.14.1] - 2026-04-06
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -115,14 +115,20 @@ function _wireSheetSwipe(panel) {
|
|||||||
let dragging = false;
|
let dragging = false;
|
||||||
|
|
||||||
panel.addEventListener('touchstart', (e) => {
|
panel.addEventListener('touchstart', (e) => {
|
||||||
startY = e.touches[0].clientY;
|
// Nur von der Handle-Zone (obere 48px) oder wenn Panel ganz oben → Swipe erlauben
|
||||||
|
const touchY = e.touches[0].clientY;
|
||||||
|
const rect = panel.getBoundingClientRect();
|
||||||
|
const isHandleZone = touchY - rect.top < 48;
|
||||||
|
const isScrolledToTop = panel.scrollTop <= 0;
|
||||||
|
if (!isHandleZone && !isScrolledToTop) return;
|
||||||
|
startY = touchY;
|
||||||
dragging = true;
|
dragging = true;
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
panel.addEventListener('touchmove', (e) => {
|
panel.addEventListener('touchmove', (e) => {
|
||||||
if (!dragging) return;
|
if (!dragging) return;
|
||||||
const dy = e.touches[0].clientY - startY;
|
const dy = e.touches[0].clientY - startY;
|
||||||
if (dy < 0) return; // Kein Swipe nach oben
|
if (dy < 0) { dragging = false; return; } // Aufwärts-Scroll: Swipe abbrechen
|
||||||
panel.style.transform = `translateY(${dy * 0.6}px)`;
|
panel.style.transform = `translateY(${dy * 0.6}px)`;
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
|
|
||||||
@@ -224,6 +230,12 @@ export function openModal({ title, content, onSave, onDelete, size = 'md' } = {}
|
|||||||
if (e.target === activeOverlay) closeModal();
|
if (e.target === activeOverlay) closeModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// iOS PWA: click-Events auf non-interactive divs sind unzuverlässig →
|
||||||
|
// touchend als Fallback (passive, damit Scroll nicht blockiert wird)
|
||||||
|
activeOverlay.addEventListener('touchend', (e) => {
|
||||||
|
if (e.target === activeOverlay) closeModal();
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
// Close-Button
|
// Close-Button
|
||||||
activeOverlay.querySelector('[data-action="close-modal"]')
|
activeOverlay.querySelector('[data-action="close-modal"]')
|
||||||
?.addEventListener('click', closeModal);
|
?.addEventListener('click', closeModal);
|
||||||
|
|||||||
@@ -626,6 +626,8 @@
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
animation: modal-overlay-in 0.2s ease forwards;
|
animation: modal-overlay-in 0.2s ease forwards;
|
||||||
|
/* iOS Safari: click-Events auf non-interactive divs erfordern cursor:pointer */
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@@ -642,6 +644,8 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||||
animation: modal-slide-up 0.25s var(--ease-out) forwards;
|
animation: modal-slide-up 0.25s var(--ease-out) forwards;
|
||||||
|
/* Cursor vom Overlay zurücksetzen */
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@@ -672,8 +676,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-panel__close {
|
.modal-panel__close {
|
||||||
width: var(--target-sm);
|
width: var(--target-md);
|
||||||
height: var(--target-sm);
|
height: var(--target-md);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -681,12 +685,7 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
transition: background-color var(--transition-fast);
|
transition: background-color var(--transition-fast);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
flex-shrink: 0;
|
||||||
|
|
||||||
.modal-panel__close::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: -8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-panel__close:hover {
|
.modal-panel__close:hover {
|
||||||
|
|||||||
Reference in New Issue
Block a user