iOS: fix modal+validation scrolling (#29023)

This commit is contained in:
Michael Geers 2026-04-12 08:07:46 +02:00 • committed by GitHub
parent 94c2606797
commit 21efd23fcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 29 additions and 2 deletions

View file

@ -444,6 +444,11 @@ a:hover {
}
}
.modal {
/* prevent touch-scroll chaining from the modal to <html> on iOS Safari */
overscroll-behavior: contain;
}
.modal-body {
padding: 1rem 0 0;
}

View file

@ -188,6 +188,7 @@ import YamlEntry from "./YamlEntry.vue";
import AuthCodeDisplay from "../AuthCodeDisplay.vue";
import AuthConnectButton from "../AuthConnectButton.vue";
import { initialTestState, performTest } from "../utils/test";
import { reportValidityInModal } from "../utils/reportValidityInModal";
import { initialAuthState, prepareAuthLogin } from "../utils/authProvider";
import sleep from "@/utils/sleep";
import { ConfigType } from "@/types/evcc";
@ -618,7 +619,7 @@ export default defineComponent({
// trigger browser validation
if (this.$refs["form"]) {
if (!(this.$refs["form"] as HTMLFormElement).reportValidity()) {
if (!reportValidityInModal(this.$refs["form"] as HTMLFormElement)) {
return;
}
}

View file

@ -0,0 +1,20 @@
/**
* `form.reportValidity()` with the document scroll restored afterwards.
*
* The native call scroll-into-views the first invalid field and leaks scroll
* up to `<html>` (overflow:hidden only blocks user scroll). On iOS Safari
* that wedges the modal's touch scrolling onto the page underneath.
*/
export function reportValidityInModal(form: HTMLFormElement): boolean {
const { scrollX, scrollY } = window;
const valid = form.reportValidity();
if (window.scrollX !== scrollX || window.scrollY !== scrollY) {
// Chromium ignores scrollTop writes while overflow-y:hidden; relax briefly.
const html = document.documentElement;
const prev = html.style.overflowY;
html.style.overflowY = "auto";
window.scrollTo(scrollX, scrollY);
html.style.overflowY = prev;
}
return valid;
}

View file

@ -1,5 +1,6 @@
import type { AxiosResponse } from "axios";
import sleep from "@/utils/sleep";
import { reportValidityInModal } from "./reportValidityInModal";
export type TestState = {
isUnknown: boolean;
@ -28,7 +29,7 @@ export const performTest = async (
api: () => Promise<AxiosResponse<any, any>>,
form: HTMLElement | undefined
) => {
if (form && !(form as HTMLFormElement).reportValidity()) return false;
if (form && !reportValidityInModal(form as HTMLFormElement)) return false;
state.isUnknown = false;
state.isSuccess = false;
state.isRunning = true;