fix(app): keyboard resizes container, tab bar margin, HTTPS for PWA install
- Root container height now bound to visualViewport.height when keyboard is open — the whole layout shrinks instead of being pushed offscreen - Tab bar gets 24px vertical margin (12px top + 12px bottom + safe area) - Added @vitejs/plugin-basic-ssl for HTTPS dev server — required for PWA install on non-localhost origins (LAN IP access) - Improved useVisualViewport to track fullHeight for accurate keyboard offset calculation across orientation changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a0a9cf8e54
commit
0fa78cbf1d
@@ -38,9 +38,13 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.49.0",
|
||||
"@storybook/addon-essentials": "^8.6.0",
|
||||
"@storybook/vue3": "^8.6.0",
|
||||
"@storybook/vue3-vite": "^8.6.0",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@vitejs/plugin-basic-ssl": "^2.1.4",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
"duck-duck-scrape": "^2.2.7",
|
||||
"eslint": "^10.0.2",
|
||||
@@ -48,6 +52,7 @@
|
||||
"globals": "^17.4.0",
|
||||
"happy-dom": "^20.8.3",
|
||||
"rss-parser": "^3.13.0",
|
||||
"storybook": "^8.6.0",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.8.0",
|
||||
@@ -55,10 +60,6 @@
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-pwa": "^1.2.0",
|
||||
"vitest": "^4.0.18",
|
||||
"@storybook/addon-essentials": "^8.6.0",
|
||||
"@storybook/vue3": "^8.6.0",
|
||||
"@storybook/vue3-vite": "^8.6.0",
|
||||
"storybook": "^8.6.0",
|
||||
"vue-eslint-parser": "^10.4.0",
|
||||
"vue-tsc": "^3.2.5"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="h-dvh flex flex-col" :class="currentTheme">
|
||||
<div class="h-dvh flex flex-col" :class="currentTheme" :style="rootStyle">
|
||||
<RouterView />
|
||||
<ArticleOverlay />
|
||||
<PlayerBar v-if="!isMobile" />
|
||||
@@ -17,6 +17,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { useArchy } from '@/composables/useArchy'
|
||||
import { useVisualViewport } from '@/composables/useVisualViewport'
|
||||
import ArticleOverlay from '@/components/content/ArticleOverlay.vue'
|
||||
import PlayerBar from '@/components/player/PlayerBar.vue'
|
||||
import PassphraseDialog from '@/components/ui/PassphraseDialog.vue'
|
||||
@@ -32,8 +33,18 @@ const archy = useArchy()
|
||||
|
||||
const windowWidth = ref(window.innerWidth)
|
||||
const isMobile = computed(() => windowWidth.value < 1024)
|
||||
const { viewportHeight, isKeyboardOpen } = useVisualViewport()
|
||||
function onResize() { windowWidth.value = window.innerWidth }
|
||||
|
||||
// On mobile, bind height to visualViewport.height so the container
|
||||
// actually shrinks when the keyboard opens (dvh doesn't do this reliably)
|
||||
const rootStyle = computed(() => {
|
||||
if (isMobile.value && isKeyboardOpen.value) {
|
||||
return { height: `${viewportHeight.value}px` }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
const showPassphrase = ref(false)
|
||||
const isCreatingPassphrase = ref(false)
|
||||
|
||||
|
||||
@@ -4,28 +4,46 @@ import { ref, onMounted, onUnmounted } from 'vue'
|
||||
* Tracks the visual viewport to detect mobile keyboard open/close.
|
||||
* Uses the VisualViewport API to compute keyboard height as the difference
|
||||
* between window.innerHeight and visualViewport.height.
|
||||
*
|
||||
* When the keyboard opens, viewportHeight shrinks to the visible area above
|
||||
* the keyboard. Bind your container's height to viewportHeight to make the
|
||||
* layout resize instead of the browser pushing content offscreen.
|
||||
*/
|
||||
export function useVisualViewport() {
|
||||
const keyboardHeight = ref(0)
|
||||
const isKeyboardOpen = ref(false)
|
||||
const viewportHeight = ref(typeof window !== 'undefined' ? window.innerHeight : 0)
|
||||
|
||||
// Store the initial full height so we can compute keyboard offset
|
||||
let fullHeight = typeof window !== 'undefined' ? window.innerHeight : 0
|
||||
|
||||
function onViewportChange() {
|
||||
const vv = window.visualViewport
|
||||
if (!vv) return
|
||||
const kbHeight = Math.max(0, window.innerHeight - vv.height)
|
||||
const kbHeight = Math.max(0, fullHeight - vv.height)
|
||||
keyboardHeight.value = kbHeight
|
||||
isKeyboardOpen.value = kbHeight > 100
|
||||
viewportHeight.value = vv.height
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
// Update full height when orientation changes or browser chrome resizes
|
||||
const vv = window.visualViewport
|
||||
if (vv && !isKeyboardOpen.value) {
|
||||
fullHeight = vv.height
|
||||
viewportHeight.value = vv.height
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const vv = window.visualViewport
|
||||
if (vv) {
|
||||
fullHeight = vv.height
|
||||
viewportHeight.value = vv.height
|
||||
vv.addEventListener('resize', onViewportChange)
|
||||
vv.addEventListener('scroll', onViewportChange)
|
||||
onViewportChange()
|
||||
}
|
||||
window.addEventListener('resize', onWindowResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -34,6 +52,7 @@ export function useVisualViewport() {
|
||||
vv.removeEventListener('resize', onViewportChange)
|
||||
vv.removeEventListener('scroll', onViewportChange)
|
||||
}
|
||||
window.removeEventListener('resize', onWindowResize)
|
||||
})
|
||||
|
||||
return { keyboardHeight, isKeyboardOpen, viewportHeight }
|
||||
|
||||
@@ -309,11 +309,11 @@
|
||||
<!-- Inline player bar on mobile (above tab bar) -->
|
||||
<PlayerBar variant="inline" />
|
||||
|
||||
<!-- Bottom tab bar (iOS HIG: 49pt + safe area) -->
|
||||
<!-- Bottom tab bar (iOS HIG: 49pt + safe area + 24px margin) -->
|
||||
<div
|
||||
v-show="!isKeyboardOpen"
|
||||
class="shrink-0"
|
||||
:style="{ paddingBottom: 'env(safe-area-inset-bottom, 0px)' }"
|
||||
class="shrink-0 pt-3 pb-3"
|
||||
:style="{ paddingBottom: 'calc(12px + env(safe-area-inset-bottom, 0px))' }"
|
||||
>
|
||||
<div class="flex items-center h-[49px] px-2 gap-1">
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import { resolve } from 'path'
|
||||
import { tmdbPlugin } from './vite-tmdb'
|
||||
@@ -15,6 +16,7 @@ export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
basicSsl(),
|
||||
tmdbPlugin(),
|
||||
devChatsPlugin(),
|
||||
musicSearchPlugin(),
|
||||
|
||||
Reference in New Issue
Block a user