Skip to content

Commit bb8adf6

Browse files
EstrellaXDclaude
andcommitted
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>
1 parent 1f5d92f commit bb8adf6

4 files changed

Lines changed: 87 additions & 10 deletions

File tree

webui/src/components/ab-add-rss.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,13 +402,18 @@ function subscribe() {
402402
.add-modal {
403403
width: 100%;
404404
max-width: 480px;
405-
max-height: 90vh;
405+
max-height: 90dvh; // Use dynamic viewport height for iOS Safari keyboard support
406406
display: flex;
407407
flex-direction: column;
408408
background: var(--color-surface);
409409
border-radius: var(--radius-xl);
410410
box-shadow: var(--shadow-lg);
411411
overflow: hidden;
412+
413+
// Fallback for browsers that don't support dvh
414+
@supports not (max-height: 1dvh) {
415+
max-height: 90vh;
416+
}
412417
}
413418
414419
.add-header {

webui/src/components/basic/ab-bottom-sheet.vue

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts" setup>
2-
import { computed, ref } from 'vue';
2+
import { computed, ref, onMounted, onUnmounted, watch } from 'vue';
33
import { usePointerSwipe } from '@vueuse/core';
44
import {
55
Dialog,
@@ -17,7 +17,7 @@ const props = withDefaults(
1717
}>(),
1818
{
1919
closeable: true,
20-
maxHeight: '85vh',
20+
maxHeight: '85dvh',
2121
}
2222
);
2323
@@ -30,15 +30,54 @@ const sheetRef = ref<HTMLElement | null>(null);
3030
const dragHandleRef = ref<HTMLElement | null>(null);
3131
const translateY = ref(0);
3232
const isDragging = ref(false);
33+
const keyboardHeight = ref(0);
34+
35+
// Handle iOS Safari virtual keyboard using visualViewport API
36+
function handleViewportResize() {
37+
if (window.visualViewport) {
38+
const viewport = window.visualViewport;
39+
// Calculate keyboard height as the difference between window height and viewport height
40+
const newKeyboardHeight = window.innerHeight - viewport.height;
41+
keyboardHeight.value = Math.max(0, newKeyboardHeight);
42+
}
43+
}
44+
45+
// Set up visualViewport listeners when sheet is shown
46+
watch(() => props.show, (isVisible) => {
47+
if (isVisible && window.visualViewport) {
48+
window.visualViewport.addEventListener('resize', handleViewportResize);
49+
window.visualViewport.addEventListener('scroll', handleViewportResize);
50+
handleViewportResize();
51+
} else if (window.visualViewport) {
52+
window.visualViewport.removeEventListener('resize', handleViewportResize);
53+
window.visualViewport.removeEventListener('scroll', handleViewportResize);
54+
keyboardHeight.value = 0;
55+
}
56+
}, { immediate: true });
57+
58+
onUnmounted(() => {
59+
if (window.visualViewport) {
60+
window.visualViewport.removeEventListener('resize', handleViewportResize);
61+
window.visualViewport.removeEventListener('scroll', handleViewportResize);
62+
}
63+
});
3364
3465
const sheetStyle = computed(() => {
66+
const style: Record<string, string> = {};
67+
68+
// Apply keyboard offset for iOS Safari
69+
if (keyboardHeight.value > 0) {
70+
style.transform = `translateY(-${keyboardHeight.value}px)`;
71+
style.transition = 'transform 0.25s ease-out';
72+
}
73+
74+
// Apply drag offset
3575
if (isDragging.value && translateY.value > 0) {
36-
return {
37-
transform: `translateY(${translateY.value}px)`,
38-
transition: 'none',
39-
};
76+
style.transform = `translateY(${translateY.value}px)`;
77+
style.transition = 'none';
4078
}
41-
return {};
79+
80+
return style;
4281
});
4382
4483
const { distanceY } = usePointerSwipe(dragHandleRef, {
@@ -145,6 +184,7 @@ function close() {
145184
z-index: 102;
146185
width: 100%;
147186
max-width: 640px;
187+
max-height: 85dvh; // Use dynamic viewport height for iOS Safari keyboard support
148188
background: var(--color-surface);
149189
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
150190
box-shadow: var(--shadow-lg);
@@ -153,6 +193,11 @@ function close() {
153193
flex-direction: column;
154194
pointer-events: auto;
155195
@include safeAreaBottom(padding-bottom);
196+
197+
// Fallback for browsers that don't support dvh
198+
@supports not (max-height: 1dvh) {
199+
max-height: 85vh;
200+
}
156201
}
157202
158203
&__handle {
@@ -191,6 +236,13 @@ function close() {
191236
overflow-y: auto;
192237
padding: 16px 20px;
193238
-webkit-overflow-scrolling: touch;
239+
240+
// Ensure inputs scroll into view when focused on iOS Safari
241+
:deep(input),
242+
:deep(textarea),
243+
:deep(select) {
244+
scroll-margin-bottom: 20px;
245+
}
194246
}
195247
}
196248
</style>

webui/src/components/search/ab-search-modal.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ function clearFilters() {
370370
.modal-content {
371371
width: 100%;
372372
max-width: 1100px;
373-
max-height: calc(100vh - 100px);
373+
max-height: calc(100dvh - 100px); // Use dynamic viewport height for iOS Safari keyboard support
374374
display: flex;
375375
flex-direction: column;
376376
background: var(--color-surface);
@@ -379,8 +379,17 @@ function clearFilters() {
379379
overflow: hidden;
380380
transition: background-color var(--transition-normal);
381381
382+
// Fallback for browsers that don't support dvh
383+
@supports not (max-height: 1dvh) {
384+
max-height: calc(100vh - 100px);
385+
}
386+
382387
@include forDesktop {
383-
max-height: calc(100vh - 120px);
388+
max-height: calc(100dvh - 120px);
389+
390+
@supports not (max-height: 1dvh) {
391+
max-height: calc(100vh - 120px);
392+
}
384393
}
385394
}
386395

webui/src/style/global.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,14 @@ input::-webkit-inner-spin-button {
7373
outline-offset: 2px;
7474
border-radius: var(--radius-sm);
7575
}
76+
77+
// iOS Safari auto-zoom prevention
78+
// iOS Safari zooms in when focusing inputs with font-size < 16px
79+
// This rule ensures all form inputs are at least 16px on touch devices
80+
@media (hover: none) and (pointer: coarse) {
81+
input:not([type="checkbox"]):not([type="radio"]),
82+
textarea,
83+
select {
84+
font-size: 16px !important;
85+
}
86+
}

0 commit comments

Comments
 (0)