fix(webui): fix iOS Safari input zoom and keyboard issues

- Add global CSS rule to prevent auto-zoom on input focus by ensuring
  font-size >= 16px on touch devices
- Update bottom sheet to use dvh units and visualViewport API for
  proper keyboard handling on iOS Safari
- Update modal components (ab-add-rss, ab-search-modal) to use dvh
  units with vh fallback for older browsers
- Add scroll-margin-bottom for inputs in bottom sheets

Closes #959

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
EstrellaXD
2026-01-26 15:15:07 +01:00
parent 1f5d92f50b
commit bb8adf6813
4 changed files with 88 additions and 11 deletions

View File

@@ -402,13 +402,18 @@ function subscribe() {
.add-modal {
width: 100%;
max-width: 480px;
max-height: 90vh;
max-height: 90dvh; // Use dynamic viewport height for iOS Safari keyboard support
display: flex;
flex-direction: column;
background: var(--color-surface);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
overflow: hidden;
// Fallback for browsers that don't support dvh
@supports not (max-height: 1dvh) {
max-height: 90vh;
}
}
.add-header {

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { computed, ref, onMounted, onUnmounted, watch } from 'vue';
import { usePointerSwipe } from '@vueuse/core';
import {
Dialog,
@@ -17,7 +17,7 @@ const props = withDefaults(
}>(),
{
closeable: true,
maxHeight: '85vh',
maxHeight: '85dvh',
}
);
@@ -30,15 +30,54 @@ const sheetRef = ref<HTMLElement | null>(null);
const dragHandleRef = ref<HTMLElement | null>(null);
const translateY = ref(0);
const isDragging = ref(false);
const keyboardHeight = ref(0);
// Handle iOS Safari virtual keyboard using visualViewport API
function handleViewportResize() {
if (window.visualViewport) {
const viewport = window.visualViewport;
// Calculate keyboard height as the difference between window height and viewport height
const newKeyboardHeight = window.innerHeight - viewport.height;
keyboardHeight.value = Math.max(0, newKeyboardHeight);
}
}
// Set up visualViewport listeners when sheet is shown
watch(() => props.show, (isVisible) => {
if (isVisible && window.visualViewport) {
window.visualViewport.addEventListener('resize', handleViewportResize);
window.visualViewport.addEventListener('scroll', handleViewportResize);
handleViewportResize();
} else if (window.visualViewport) {
window.visualViewport.removeEventListener('resize', handleViewportResize);
window.visualViewport.removeEventListener('scroll', handleViewportResize);
keyboardHeight.value = 0;
}
}, { immediate: true });
onUnmounted(() => {
if (window.visualViewport) {
window.visualViewport.removeEventListener('resize', handleViewportResize);
window.visualViewport.removeEventListener('scroll', handleViewportResize);
}
});
const sheetStyle = computed(() => {
if (isDragging.value && translateY.value > 0) {
return {
transform: `translateY(${translateY.value}px)`,
transition: 'none',
};
const style: Record<string, string> = {};
// Apply keyboard offset for iOS Safari
if (keyboardHeight.value > 0) {
style.transform = `translateY(-${keyboardHeight.value}px)`;
style.transition = 'transform 0.25s ease-out';
}
return {};
// Apply drag offset
if (isDragging.value && translateY.value > 0) {
style.transform = `translateY(${translateY.value}px)`;
style.transition = 'none';
}
return style;
});
const { distanceY } = usePointerSwipe(dragHandleRef, {
@@ -145,6 +184,7 @@ function close() {
z-index: 102;
width: 100%;
max-width: 640px;
max-height: 85dvh; // Use dynamic viewport height for iOS Safari keyboard support
background: var(--color-surface);
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
box-shadow: var(--shadow-lg);
@@ -153,6 +193,11 @@ function close() {
flex-direction: column;
pointer-events: auto;
@include safeAreaBottom(padding-bottom);
// Fallback for browsers that don't support dvh
@supports not (max-height: 1dvh) {
max-height: 85vh;
}
}
&__handle {
@@ -191,6 +236,13 @@ function close() {
overflow-y: auto;
padding: 16px 20px;
-webkit-overflow-scrolling: touch;
// Ensure inputs scroll into view when focused on iOS Safari
:deep(input),
:deep(textarea),
:deep(select) {
scroll-margin-bottom: 20px;
}
}
}
</style>

View File

@@ -370,7 +370,7 @@ function clearFilters() {
.modal-content {
width: 100%;
max-width: 1100px;
max-height: calc(100vh - 100px);
max-height: calc(100dvh - 100px); // Use dynamic viewport height for iOS Safari keyboard support
display: flex;
flex-direction: column;
background: var(--color-surface);
@@ -379,8 +379,17 @@ function clearFilters() {
overflow: hidden;
transition: background-color var(--transition-normal);
// Fallback for browsers that don't support dvh
@supports not (max-height: 1dvh) {
max-height: calc(100vh - 100px);
}
@include forDesktop {
max-height: calc(100vh - 120px);
max-height: calc(100dvh - 120px);
@supports not (max-height: 1dvh) {
max-height: calc(100vh - 120px);
}
}
}

View File

@@ -73,3 +73,14 @@ input::-webkit-inner-spin-button {
outline-offset: 2px;
border-radius: var(--radius-sm);
}
// iOS Safari auto-zoom prevention
// iOS Safari zooms in when focusing inputs with font-size < 16px
// This rule ensures all form inputs are at least 16px on touch devices
@media (hover: none) and (pointer: coarse) {
input:not([type="checkbox"]):not([type="radio"]),
textarea,
select {
font-size: 16px !important;
}
}