diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png
index 51197814d40..c6c1a2d880f 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png
index d6978bd6ab2..ff6cce8fe9f 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png
index 7b6912a77b6..6755f318f78 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png
index 9bdb2a0050d..41c48a001a7 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png
index 2160bf71c71..946763fa06b 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png
index e18e09840ac..9b5d9e71eb6 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png
index 2925d01cc40..39edcfcfb0f 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png
index 625d61957f4..5d00b4c0a20 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png
index 636bbd74326..c6ba890db5d 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png
index 6bf467070f7..21aae4987c5 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png
index 31175c53c52..cd84a1e4519 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png
index d1e17793401..9ec7a229728 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png
index a72dc772243..e97a03df44d 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png
index 6796d9a484f..adb36b2796f 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png
index 2d747790320..edcd8a84e14 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Chrome-linux.png
index dd8e90750f3..8f969d0ba43 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Firefox-linux.png
index eaa50d6e5fb..265eef0f687 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Safari-linux.png
index a291fe4c078..9f2416e4f12 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png
index 2217a13a189..7fdc24274c2 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png
index 6d49226ac1d..aa87aa60c88 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png
index 3619f94220f..067b270569c 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png
index 69df91ae522..46f2c980b7e 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png
index e8afb2df67d..f620cdf642c 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png
index fd6120b1681..a144e9df1a3 100644
Binary files a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Chrome-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Chrome-linux.png
index aee611bf378..13d07317bca 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Firefox-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Firefox-linux.png
index 9f3d30bfff5..770a9fa4bde 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Safari-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Safari-linux.png
index 3a851565133..4f503d01279 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Safari-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Chrome-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Chrome-linux.png
index 9660aeeadcd..097abe9a87a 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Firefox-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Firefox-linux.png
index 6c1d1ee97b7..d64ca070c3c 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Safari-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Safari-linux.png
index 35e0df2fb84..7fa31180ae1 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Safari-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-end-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Chrome-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Chrome-linux.png
index 9660aeeadcd..097abe9a87a 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Firefox-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Firefox-linux.png
index 6c1d1ee97b7..d64ca070c3c 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Safari-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Safari-linux.png
index 35e0df2fb84..7fa31180ae1 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Safari-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Chrome-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Chrome-linux.png
index aee611bf378..13d07317bca 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Firefox-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Firefox-linux.png
index 9f3d30bfff5..770a9fa4bde 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Safari-linux.png b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Safari-linux.png
index 3a851565133..4f503d01279 100644
Binary files a/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Safari-linux.png and b/core/src/components/fab/test/safe-area/fab.e2e.ts-snapshots/fab-safe-area-horizontal-start-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Chrome-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Chrome-linux.png
index d23db0f8082..847203b959b 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Firefox-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Firefox-linux.png
index dec6cbe80d4..7505285ca78 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Safari-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Safari-linux.png
index b526f0f0a61..14c96b40bf9 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Safari-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Chrome-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Chrome-linux.png
index a690482d70c..f13e9e1f2e6 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Firefox-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Firefox-linux.png
index 78668e8a1c7..2f1193a4a01 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Safari-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Safari-linux.png
index a2d9893fc26..5ae9984cfe3 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Safari-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-left-notch-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Chrome-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Chrome-linux.png
index 9303d7426af..0f76f7185e0 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Firefox-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Firefox-linux.png
index f364b05c25c..c4139d9ef55 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Safari-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Safari-linux.png
index a7168d36400..ba2ceae4263 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Safari-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Chrome-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Chrome-linux.png
index 58c28a228f8..a0a915efe72 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Firefox-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Firefox-linux.png
index 927087d578b..401f304853f 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Safari-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Safari-linux.png
index 221ed10fc32..949acf332de 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Safari-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-end-safe-area-right-notch-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Chrome-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Chrome-linux.png
index 30038ec4c37..ae939a0efd6 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Firefox-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Firefox-linux.png
index 3d9d4b61321..d059cae1394 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Safari-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Safari-linux.png
index 36580ae58cb..8069aae42a6 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Safari-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Chrome-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Chrome-linux.png
index 6c5d9946a2e..b02c9047c8f 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Firefox-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Firefox-linux.png
index 3cb029908fa..7d1cb3fb061 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Safari-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Safari-linux.png
index 52e0ca5ceb0..62b847347e6 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Safari-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-left-notch-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Chrome-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Chrome-linux.png
index 1f9094154ab..3420ea7d779 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Firefox-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Firefox-linux.png
index 4e89bfd3183..dfb2430c1b4 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Safari-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Safari-linux.png
index c289cff38b8..e6d1c1b2789 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Safari-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Chrome-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Chrome-linux.png
index a9689438292..c73aed5103f 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Firefox-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Firefox-linux.png
index f7f75c751fb..e0ccec89203 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Safari-linux.png b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Safari-linux.png
index a8ebda13d1e..4843b6da194 100644
Binary files a/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Safari-linux.png and b/core/src/components/menu/test/safe-area/menu.e2e.ts-snapshots/menu-start-safe-area-right-notch-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/gestures/sheet.ts b/core/src/components/modal/gestures/sheet.ts
index afac8f3d3ed..a9f7855e4d4 100644
--- a/core/src/components/modal/gestures/sheet.ts
+++ b/core/src/components/modal/gestures/sheet.ts
@@ -52,7 +52,8 @@ export const createSheetGesture = (
expandToScroll: boolean,
getCurrentBreakpoint: () => number,
onDismiss: () => void,
- onBreakpointChange: (breakpoint: number) => void
+ onBreakpointChange: (breakpoint: number) => void,
+ onGestureMove?: () => void
) => {
// Defaults for the sheet swipe animation
const defaultBackdrop = [
@@ -423,6 +424,9 @@ export const createSheetGesture = (
offset = clamp(0.0001, processedStep, maxStep);
animation.progressStep(offset);
+
+ // Notify modal of position change for safe-area updates
+ onGestureMove?.();
};
const onEnd = (detail: GestureDetail) => {
diff --git a/core/src/components/modal/gestures/swipe-to-close.ts b/core/src/components/modal/gestures/swipe-to-close.ts
index 17ec454ff15..c81a6a6ba21 100644
--- a/core/src/components/modal/gestures/swipe-to-close.ts
+++ b/core/src/components/modal/gestures/swipe-to-close.ts
@@ -20,7 +20,8 @@ export const createSwipeToCloseGesture = (
el: HTMLIonModalElement,
animation: Animation,
statusBarStyle: StatusBarStyle,
- onDismiss: () => void
+ onDismiss: () => void,
+ onGestureMove?: () => void
) => {
/**
* The step value at which a card modal
@@ -199,6 +200,9 @@ export const createSwipeToCloseGesture = (
animation.progressStep(clampedStep);
+ // Notify modal of position change for safe-area updates
+ onGestureMove?.();
+
/**
* When swiping down half way, the status bar style
* should be reset to its default value.
diff --git a/core/src/components/modal/modal.scss b/core/src/components/modal/modal.scss
index 7c5ec7916fe..ac4cb533b48 100644
--- a/core/src/components/modal/modal.scss
+++ b/core/src/components/modal/modal.scss
@@ -94,10 +94,6 @@ ion-backdrop {
:host {
--width: #{$modal-inset-width};
--height: #{$modal-inset-height-small};
- --ion-safe-area-top: 0px;
- --ion-safe-area-bottom: 0px;
- --ion-safe-area-right: 0px;
- --ion-safe-area-left: 0px;
}
}
diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx
index a96d59c8e9f..bf57c36f073 100644
--- a/core/src/components/modal/modal.tsx
+++ b/core/src/components/modal/modal.tsx
@@ -1,5 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Method, Prop, State, Watch, h, writeTask } from '@stencil/core';
+import { win } from '@utils/browser';
import { findIonContent, printIonContentErrorMsg } from '@utils/content';
import { CoreDelegate, attachComponent, detachComponent } from '@utils/framework-delegate';
import { raf, inheritAttributes, hasLazyBuild, getElementRoot } from '@utils/helpers';
@@ -98,10 +99,18 @@ export class Modal implements ComponentInterface, OverlayInterface {
// Mutation observer to watch for parent removal
private parentRemovalObserver?: MutationObserver;
+ // Watches for dynamic footer additions/removals to update safe-area padding
+ private footerObserver?: MutationObserver;
// Cached original parent from before modal is moved to body during presentation
private cachedOriginalParent?: HTMLElement;
// Cached ion-page ancestor for child route passthrough
private cachedPageParent?: HTMLElement | null;
+ // Whether to skip coordinate-based safe-area detection (for fullscreen phone modals)
+ private skipSafeAreaCoordinateDetection = false;
+ // Cached safe-area values to avoid getComputedStyle calls during gestures
+ private cachedSafeAreas?: { top: number; bottom: number; left: number; right: number };
+ // Track previous safe-area state to avoid redundant DOM writes
+ private prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
lastFocus?: HTMLElement;
animation?: Animation;
@@ -276,7 +285,11 @@ export class Modal implements ComponentInterface, OverlayInterface {
@Listen('resize', { target: 'window' })
onWindowResize() {
- // Only handle resize for iOS card modals when no custom animations are provided
+ // Invalidate safe-area cache on resize (device rotation may change values)
+ this.cachedSafeAreas = undefined;
+ this.updateSafeAreaOverrides();
+
+ // Only handle view transition for iOS card modals when no custom animations are provided
if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) {
return;
}
@@ -406,6 +419,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
this.triggerController.removeClickListener();
this.cleanupViewTransitionListener();
this.cleanupParentRemovalObserver();
+ // Reset safe-area state to handle removal without dismiss (e.g., framework unmount)
+ this.resetSafeAreaState();
}
componentWillLoad() {
@@ -592,6 +607,9 @@ export class Modal implements ComponentInterface, OverlayInterface {
await waitForMount();
}
+ // Predict safe-area needs based on modal configuration to avoid visual snap
+ this.setInitialSafeAreaOverrides(presentingElement);
+
writeTask(() => this.el.classList.add('show-modal'));
const hasCardModal = presentingElement !== undefined;
@@ -659,6 +677,9 @@ export class Modal implements ComponentInterface, OverlayInterface {
this.initSwipeToClose();
}
+ // Now that animation is complete, update safe-area based on actual position
+ this.updateSafeAreaOverrides();
+
// Initialize view transition listener for iOS card modals
this.initViewTransitionListener();
@@ -692,33 +713,39 @@ export class Modal implements ComponentInterface, OverlayInterface {
const statusBarStyle = this.statusBarStyle ?? StatusBarStyle.Default;
- this.gesture = createSwipeToCloseGesture(el, ani, statusBarStyle, () => {
- /**
- * While the gesture animation is finishing
- * it is possible for a user to tap the backdrop.
- * This would result in the dismiss animation
- * being played again. Typically this is avoided
- * by setting `presented = false` on the overlay
- * component; however, we cannot do that here as
- * that would prevent the element from being
- * removed from the DOM.
- */
- this.gestureAnimationDismissing = true;
-
- /**
- * Reset the status bar style as the dismiss animation
- * starts otherwise the status bar will be the wrong
- * color for the duration of the dismiss animation.
- * The dismiss method does this as well, but
- * in this case it's only called once the animation
- * has finished.
- */
- setCardStatusBarDefault(this.statusBarStyle);
- this.animation!.onFinish(async () => {
- await this.dismiss(undefined, GESTURE);
- this.gestureAnimationDismissing = false;
- });
- });
+ this.gesture = createSwipeToCloseGesture(
+ el,
+ ani,
+ statusBarStyle,
+ () => {
+ /**
+ * While the gesture animation is finishing
+ * it is possible for a user to tap the backdrop.
+ * This would result in the dismiss animation
+ * being played again. Typically this is avoided
+ * by setting `presented = false` on the overlay
+ * component; however, we cannot do that here as
+ * that would prevent the element from being
+ * removed from the DOM.
+ */
+ this.gestureAnimationDismissing = true;
+
+ /**
+ * Reset the status bar style as the dismiss animation
+ * starts otherwise the status bar will be the wrong
+ * color for the duration of the dismiss animation.
+ * The dismiss method does this as well, but
+ * in this case it's only called once the animation
+ * has finished.
+ */
+ setCardStatusBarDefault(this.statusBarStyle);
+ this.animation!.onFinish(async () => {
+ await this.dismiss(undefined, GESTURE);
+ this.gestureAnimationDismissing = false;
+ });
+ },
+ () => this.updateSafeAreaOverrides()
+ );
this.gesture.enable(true);
}
@@ -755,7 +782,9 @@ export class Modal implements ComponentInterface, OverlayInterface {
this.currentBreakpoint = breakpoint;
this.ionBreakpointDidChange.emit({ breakpoint });
}
- }
+ this.updateSafeAreaOverrides();
+ },
+ () => this.updateSafeAreaOverrides()
);
this.gesture = gesture;
@@ -849,6 +878,203 @@ export class Modal implements ComponentInterface, OverlayInterface {
this.cachedPageParent = undefined;
}
+ /**
+ * Sets initial safe-area overrides based on modal configuration before
+ * the modal becomes visible. This predicts whether the modal will touch
+ * screen edges to avoid a visual snap after animation completes.
+ */
+ private setInitialSafeAreaOverrides(presentingElement: HTMLElement | undefined) {
+ const style = this.el.style;
+ const mode = getIonMode(this);
+ const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
+ // Card modals only exist in iOS mode - in MD mode, presentingElement is ignored
+ const isCardModal = presentingElement !== undefined && mode === 'ios';
+ const isTablet = window.innerWidth >= 768;
+
+ // Sheet modals always touch bottom edge, never top/left/right
+ if (isSheetModal) {
+ style.setProperty('--ion-safe-area-top', '0px');
+ style.setProperty('--ion-safe-area-left', '0px');
+ style.setProperty('--ion-safe-area-right', '0px');
+ return;
+ }
+
+ // Card modals have rounded top corners
+ if (isCardModal) {
+ style.setProperty('--ion-safe-area-top', '0px');
+ if (isTablet) {
+ // On tablets, card modals are inset from all edges
+ this.zeroAllSafeAreas();
+ } else {
+ // On phones, card modals still extend to the bottom edge
+ style.setProperty('--ion-safe-area-left', '0px');
+ style.setProperty('--ion-safe-area-right', '0px');
+ this.applyFullscreenSafeArea();
+ }
+ return;
+ }
+
+ // Phone-sized fullscreen modals inherit safe areas and use wrapper padding
+ if (!isTablet) {
+ this.applyFullscreenSafeArea();
+ return;
+ }
+
+ // Check if tablet modal is fullscreen via CSS custom properties
+ const computedStyle = getComputedStyle(this.el);
+ const width = computedStyle.getPropertyValue('--width').trim();
+ const height = computedStyle.getPropertyValue('--height').trim();
+ const isFullscreen = width === '100%' && height === '100%';
+
+ if (isFullscreen) {
+ this.applyFullscreenSafeArea();
+ } else {
+ // Centered dialog doesn't touch edges
+ this.zeroAllSafeAreas();
+ }
+ }
+
+ /**
+ * Applies safe-area handling for fullscreen modals.
+ * Adds wrapper padding when no footer is present to prevent
+ * content from overlapping system navigation areas.
+ */
+ private applyFullscreenSafeArea() {
+ this.skipSafeAreaCoordinateDetection = true;
+ this.updateFooterPadding();
+
+ // Watch for dynamic footer additions/removals (e.g., async data loading)
+ // Use subtree:true to support wrapped footers in framework components
+ // (e.g., ... )
+ if (!this.footerObserver && win !== undefined && 'MutationObserver' in win) {
+ this.footerObserver = new MutationObserver(() => this.updateFooterPadding());
+ this.footerObserver.observe(this.el, { childList: true, subtree: true });
+ }
+ }
+
+ /**
+ * Updates wrapper padding based on footer presence.
+ * Called initially and when footer is dynamically added/removed.
+ */
+ private updateFooterPadding() {
+ if (!this.wrapperEl) return;
+
+ const hasFooter = this.el.querySelector('ion-footer') !== null;
+ if (hasFooter) {
+ this.wrapperEl.style.removeProperty('padding-bottom');
+ this.wrapperEl.style.removeProperty('box-sizing');
+ } else {
+ this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
+ this.wrapperEl.style.setProperty('box-sizing', 'border-box');
+ }
+ }
+
+ /**
+ * Sets all safe-area CSS variables to 0px for modals that
+ * don't touch screen edges.
+ */
+ private zeroAllSafeAreas() {
+ const style = this.el.style;
+ style.setProperty('--ion-safe-area-top', '0px');
+ style.setProperty('--ion-safe-area-bottom', '0px');
+ style.setProperty('--ion-safe-area-left', '0px');
+ style.setProperty('--ion-safe-area-right', '0px');
+ }
+
+ /**
+ * Resets all safe-area related state and styles.
+ * Called during dismiss and disconnectedCallback to ensure clean state
+ * for re-presentation of inline modals.
+ */
+ private resetSafeAreaState() {
+ this.skipSafeAreaCoordinateDetection = false;
+ this.cachedSafeAreas = undefined;
+ this.prevSafeAreaState = { top: false, bottom: false, left: false, right: false };
+ this.footerObserver?.disconnect();
+ this.footerObserver = undefined;
+
+ // Clear wrapper styles that may have been set for safe-area handling
+ if (this.wrapperEl) {
+ this.wrapperEl.style.removeProperty('padding-bottom');
+ this.wrapperEl.style.removeProperty('box-sizing');
+ }
+
+ // Clear safe-area CSS variable overrides
+ const style = this.el.style;
+ style.removeProperty('--ion-safe-area-top');
+ style.removeProperty('--ion-safe-area-bottom');
+ style.removeProperty('--ion-safe-area-left');
+ style.removeProperty('--ion-safe-area-right');
+ }
+
+ /**
+ * Gets the root safe-area values from the document element.
+ * Uses cached values during gestures to avoid getComputedStyle calls.
+ */
+ private getSafeAreaValues(): { top: number; bottom: number; left: number; right: number } {
+ if (!this.cachedSafeAreas) {
+ const rootStyle = getComputedStyle(document.documentElement);
+ this.cachedSafeAreas = {
+ top: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-top')) || 0,
+ bottom: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-bottom')) || 0,
+ left: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-left')) || 0,
+ right: parseFloat(rootStyle.getPropertyValue('--ion-safe-area-right')) || 0,
+ };
+ }
+ return this.cachedSafeAreas;
+ }
+
+ /**
+ * Updates safe-area CSS variable overrides based on whether the modal
+ * extends into each safe-area region. Called after animation
+ * and during gestures to handle dynamic position changes.
+ *
+ * Optimized to avoid redundant DOM writes by tracking previous state.
+ */
+ private updateSafeAreaOverrides() {
+ if (this.skipSafeAreaCoordinateDetection) {
+ return;
+ }
+
+ const wrapper = this.wrapperEl;
+ if (!wrapper) {
+ return;
+ }
+
+ const rect = wrapper.getBoundingClientRect();
+ const safeAreas = this.getSafeAreaValues();
+
+ const extendsIntoTop = rect.top < safeAreas.top;
+ const extendsIntoBottom = rect.bottom > window.innerHeight - safeAreas.bottom;
+ const extendsIntoLeft = rect.left < safeAreas.left;
+ const extendsIntoRight = rect.right > window.innerWidth - safeAreas.right;
+
+ // Only update DOM when state actually changes
+ const prev = this.prevSafeAreaState;
+ const style = this.el.style;
+
+ if (extendsIntoTop !== prev.top) {
+ extendsIntoTop ? style.removeProperty('--ion-safe-area-top') : style.setProperty('--ion-safe-area-top', '0px');
+ prev.top = extendsIntoTop;
+ }
+ if (extendsIntoBottom !== prev.bottom) {
+ extendsIntoBottom
+ ? style.removeProperty('--ion-safe-area-bottom')
+ : style.setProperty('--ion-safe-area-bottom', '0px');
+ prev.bottom = extendsIntoBottom;
+ }
+ if (extendsIntoLeft !== prev.left) {
+ extendsIntoLeft ? style.removeProperty('--ion-safe-area-left') : style.setProperty('--ion-safe-area-left', '0px');
+ prev.left = extendsIntoLeft;
+ }
+ if (extendsIntoRight !== prev.right) {
+ extendsIntoRight
+ ? style.removeProperty('--ion-safe-area-right')
+ : style.setProperty('--ion-safe-area-right', '0px');
+ prev.right = extendsIntoRight;
+ }
+ }
+
private sheetOnDismiss() {
/**
* While the gesture animation is finishing
@@ -961,6 +1187,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
}
this.currentBreakpoint = undefined;
this.animation = undefined;
+ // Reset safe-area state for potential re-presentation
+ this.resetSafeAreaState();
unlock();
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Chrome-linux.png
index dcd19712e99..39ff1e2ba52 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Firefox-linux.png
index aa87ce45424..7f5fa6082a4 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Chrome-linux.png
index d627a28c705..7e4f8efa019 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Firefox-linux.png
index 90c3297b3e6..c3a3fbb40a9 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Chrome-linux.png
index 3013b111828..9c783d5e500 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Firefox-linux.png
index e9ab0e35017..c4742c5a055 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Safari-linux.png
index f484160464c..a738598707e 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Chrome-linux.png
index 50a1560e71a..0a235e6efd9 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Firefox-linux.png
index aa3076fd43a..bb10b08013c 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Safari-linux.png
index 15896e6b684..ac35e488c60 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Chrome-linux.png
index a91b1bf3bb4..e306148728c 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Firefox-linux.png
index a2ea869f72a..15724725c20 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Safari-linux.png
index b56f30abc74..cb3f4586995 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Chrome-linux.png
index bd83c2164c9..5d912a81961 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Firefox-linux.png
index 1e264be58e1..0cdc03eadd8 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Safari-linux.png
index 65fab21a0c7..ef9f46f34f3 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Chrome-linux.png
index 2ac52f2d9c6..dafbde597d9 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Firefox-linux.png
index cd03d981152..70371a95e14 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Safari-linux.png
index 0faf177715b..5210ca2078f 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Chrome-linux.png
index d2b1d607bf6..d3fb2697565 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Firefox-linux.png
index 2b7093b0eb4..398589e8c0d 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Safari-linux.png
index 83c959aa1f4..1f71e539b3f 100644
Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Chrome-linux.png
index f188eb46258..44929c42bed 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Firefox-linux.png
index 9c0ef9a90c4..8d61f2883cb 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Safari-linux.png
index d45c49fe7fe..4399b2afa3b 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Chrome-linux.png
index 599fbd77e76..7393cf7a796 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Firefox-linux.png
index eb7dff3ea17..446e0d64206 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Safari-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Safari-linux.png
index a3317be484b..e37a220e0a2 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-ios-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Chrome-linux.png
index a9538fbdab2..311e61d141c 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Firefox-linux.png
index 6ab9a6430e6..318a8b4bea6 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Safari-linux.png
index ac69eb9a371..5ba4557d9da 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Chrome-linux.png
index dca58f00ee6..8e140f3c714 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Firefox-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Firefox-linux.png
index 79d4376a7e7..05d96cb99ba 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Safari-linux.png b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Safari-linux.png
index 3c08445786a..0fadc9eb0f1 100644
Binary files a/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Safari-linux.png and b/core/src/components/modal/test/custom/modal.e2e.ts-snapshots/modal-custom-present-md-rtl-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Chrome-linux.png
index 35395c2f9b1..d9ef05c11e8 100644
Binary files a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Firefox-linux.png
index c25f3f922d1..cc754a6685d 100644
Binary files a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Safari-linux.png
index 82482d6927e..dc426c19186 100644
Binary files a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-ios-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Chrome-linux.png
index 5b93a9766fc..aec170314cb 100644
Binary files a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Firefox-linux.png
index 6a15fab242b..56aad283993 100644
Binary files a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Safari-linux.png
index b358d9fa5f9..9fa0d3587a7 100644
Binary files a/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/dark-mode/model.e2e.ts-snapshots/modal-dark-color-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/modal/test/safe-area/index.html b/core/src/components/modal/test/safe-area/index.html
new file mode 100644
index 00000000000..fee7e96d079
--- /dev/null
+++ b/core/src/components/modal/test/safe-area/index.html
@@ -0,0 +1,263 @@
+
+
+
+
+ Modal - Safe Area
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modal - Safe Area
+
+
+
+
+ Test safe-area handling in modals.
+
+
+
+
+ With Footer
+
+
+
+
+ Default Modal
+ Centered dialog on tablet - should NOT have safe-area padding
+
+ Present
+
+
+
+
+ Fullscreen Modal
+ Full screen - footer handles safe-area
+
+ Present
+
+
+
+
+ Sheet Modal (Partial)
+ At 0.5 breakpoint - should have bottom safe-area only
+
+ Present
+
+
+
+
+ Sheet Modal (Full)
+ At 1.0 breakpoint - should have bottom safe-area
+
+ Present
+
+
+
+
+ Card Modal (iOS)
+ Card presentation with presentingElement
+
+ Present
+
+
+
+
+
+ Without Footer (wrapper padding)
+
+
+
+
+ Fullscreen Modal (no footer)
+ Wrapper padding should prevent content overlap
+
+
+
+
+
+
+ Card Modal (no footer)
+ On phones, wrapper padding should prevent content overlap
+
+
+
+
+
+
+ Default Modal (no footer)
+ On phones, wrapper padding should prevent content overlap
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/components/modal/test/safe-area/modal.e2e.ts b/core/src/components/modal/test/safe-area/modal.e2e.ts
new file mode 100644
index 00000000000..39bd2bb2467
--- /dev/null
+++ b/core/src/components/modal/test/safe-area/modal.e2e.ts
@@ -0,0 +1,252 @@
+import { expect } from '@playwright/test';
+import type { E2EPage } from '@utils/test/playwright';
+import { configs, test, Viewports } from '@utils/test/playwright';
+
+/**
+ * Safe-area tests verify that modals correctly handle safe-area insets
+ * based on modal type and screen size.
+ *
+ * These tests use simulated safe-area values (34px bottom) set in index.html.
+ * They verify the modal wrapper has correct padding applied.
+ */
+
+// Helper to get the modal wrapper's computed padding-bottom
+async function getWrapperPaddingBottom(page: E2EPage): Promise {
+ const modal = page.locator('ion-modal');
+ return modal.evaluate((el: HTMLIonModalElement) => {
+ const wrapper = el.shadowRoot?.querySelector('.modal-wrapper');
+ if (!wrapper) return '0px';
+ return getComputedStyle(wrapper).paddingBottom;
+ });
+}
+
+// Helper to check if modal has a footer
+async function modalHasFooter(page: E2EPage): Promise {
+ const modal = page.locator('ion-modal');
+ return modal.evaluate((el: HTMLIonModalElement) => {
+ return el.querySelector('ion-footer') !== null;
+ });
+}
+
+// Phone viewport (less than 768px width)
+const PhoneViewport = { width: 390, height: 844 };
+
+// =============================================================================
+// Phone Tests - Fullscreen modals need wrapper padding when no footer
+// =============================================================================
+
+configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('modal: safe-area - phone'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.setViewportSize(PhoneViewport);
+ await page.goto('/src/components/modal/test/safe-area', config);
+ });
+
+ test('fullscreen modal without footer should have wrapper padding', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#fullscreen-no-footer');
+ await ionModalDidPresent.next();
+
+ const hasFooter = await modalHasFooter(page);
+ expect(hasFooter).toBe(false);
+
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ // Should have safe-area padding (34px as set in test HTML)
+ expect(paddingBottom).toBe('34px');
+ });
+
+ test('fullscreen modal with footer should not have wrapper padding', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#fullscreen-modal');
+ await ionModalDidPresent.next();
+
+ const hasFooter = await modalHasFooter(page);
+ expect(hasFooter).toBe(true);
+
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ // Footer handles safe-area, wrapper should have no padding
+ expect(paddingBottom).toBe('0px');
+ });
+
+ test('default modal without footer should have wrapper padding on phone', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#default-no-footer');
+ await ionModalDidPresent.next();
+
+ // On phones, default modals are fullscreen
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ expect(paddingBottom).toBe('34px');
+ });
+ });
+});
+
+configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('modal: safe-area - card modal on phone'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.setViewportSize(PhoneViewport);
+ await page.goto('/src/components/modal/test/safe-area', config);
+ });
+
+ test('card modal without footer should have wrapper padding on phone', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#card-modal-no-footer');
+ await ionModalDidPresent.next();
+
+ // Card modals on phones still extend to bottom edge
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ expect(paddingBottom).toBe('34px');
+ });
+
+ test('card modal with footer should not have wrapper padding', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#card-modal');
+ await ionModalDidPresent.next();
+
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ expect(paddingBottom).toBe('0px');
+ });
+ });
+});
+
+// =============================================================================
+// Tablet Tests - Centered dialogs don't need safe-area, fullscreen does
+// =============================================================================
+
+configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('modal: safe-area - tablet'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.setViewportSize(Viewports.tablet.portrait);
+ await page.goto('/src/components/modal/test/safe-area', config);
+ });
+
+ test('default modal should not have wrapper padding on tablet', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#default-modal');
+ await ionModalDidPresent.next();
+
+ // Centered dialog on tablet - inset from edges, no padding needed
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ expect(paddingBottom).toBe('0px');
+ });
+
+ test('fullscreen modal without footer should have wrapper padding on tablet', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#fullscreen-no-footer');
+ await ionModalDidPresent.next();
+
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ expect(paddingBottom).toBe('34px');
+ });
+
+ test('fullscreen modal with footer should not have wrapper padding', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#fullscreen-modal');
+ await ionModalDidPresent.next();
+
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ expect(paddingBottom).toBe('0px');
+ });
+ });
+});
+
+configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('modal: safe-area - card modal on tablet'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.setViewportSize(Viewports.tablet.portrait);
+ await page.goto('/src/components/modal/test/safe-area', config);
+ });
+
+ test('card modal should not have wrapper padding on tablet', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#card-modal');
+ await ionModalDidPresent.next();
+
+ // Card modals on tablets are inset from all edges
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ expect(paddingBottom).toBe('0px');
+ });
+ });
+});
+
+// =============================================================================
+// Sheet Modal Tests - Always touch bottom edge
+// =============================================================================
+
+configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('modal: safe-area - sheet modal'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.setViewportSize(Viewports.tablet.portrait);
+ await page.goto('/src/components/modal/test/safe-area', config);
+ });
+
+ test('sheet modal should not have wrapper padding (footer handles safe-area)', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://outsystemsrd.atlassian.net/browse/FW-6830',
+ });
+
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+
+ await page.click('#sheet-modal-full');
+ await ionModalDidPresent.next();
+
+ // Sheet modals with footer - footer handles the safe area
+ const paddingBottom = await getWrapperPaddingBottom(page);
+ expect(paddingBottom).toBe('0px');
+ });
+ });
+});
diff --git a/core/src/components/popover/animations/ios.enter.ts b/core/src/components/popover/animations/ios.enter.ts
index aa4e0568143..22e8da58d4f 100644
--- a/core/src/components/popover/animations/ios.enter.ts
+++ b/core/src/components/popover/animations/ios.enter.ts
@@ -61,6 +61,8 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
top,
left,
bottom,
+ checkSafeAreaTop,
+ checkSafeAreaBottom,
checkSafeAreaLeft,
checkSafeAreaRight,
arrowTop,
@@ -118,15 +120,27 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
baseEl.classList.add('popover-bottom');
}
- if (bottom !== undefined) {
- contentEl.style.setProperty('bottom', `${bottom}px`);
- }
-
+ /**
+ * Safe area CSS variable adjustments.
+ * When the popover is positioned near an edge, we add the corresponding
+ * safe-area inset to ensure the popover doesn't overlap with system UI
+ * (status bars, home indicators, navigation bars on Android API 36+, etc.)
+ */
+ const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
+ const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
+ let topValue = `${top}px`;
+ let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
let leftValue = `${left}px`;
+ if (checkSafeAreaTop) {
+ topValue = `${top}px${safeAreaTop}`;
+ }
+ if (checkSafeAreaBottom && bottomValue !== undefined) {
+ bottomValue = `${bottom}px${safeAreaBottom}`;
+ }
if (checkSafeAreaLeft) {
leftValue = `${left}px${safeAreaLeft}`;
}
@@ -134,7 +148,11 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
leftValue = `${left}px${safeAreaRight}`;
}
- contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
+ if (bottomValue !== undefined) {
+ contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
+ }
+
+ contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
diff --git a/core/src/components/popover/animations/md.enter.ts b/core/src/components/popover/animations/md.enter.ts
index e25f745cec4..e8a1e1adc5b 100644
--- a/core/src/components/popover/animations/md.enter.ts
+++ b/core/src/components/popover/animations/md.enter.ts
@@ -47,7 +47,17 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
- const { originX, originY, top, left, bottom } = calculateWindowAdjustment(
+ const {
+ originX,
+ originY,
+ top,
+ left,
+ bottom,
+ checkSafeAreaTop,
+ checkSafeAreaBottom,
+ checkSafeAreaLeft,
+ checkSafeAreaRight,
+ } = calculateWindowAdjustment(
side,
results.top,
results.left,
@@ -62,6 +72,34 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
results.referenceCoordinates
);
+ /**
+ * Safe area CSS variable adjustments.
+ * When the popover is positioned near an edge, we add the corresponding
+ * safe-area inset to ensure the popover doesn't overlap with system UI
+ * (status bars, home indicators, navigation bars on Android API 36+, etc.)
+ */
+ const safeAreaTop = ' + var(--ion-safe-area-top, 0)';
+ const safeAreaBottom = ' + var(--ion-safe-area-bottom, 0)';
+ const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
+ const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
+
+ let topValue = `${top}px`;
+ let bottomValue = bottom !== undefined ? `${bottom}px` : undefined;
+ let leftValue = `${left}px`;
+
+ if (checkSafeAreaTop) {
+ topValue = `${top}px${safeAreaTop}`;
+ }
+ if (checkSafeAreaBottom && bottomValue !== undefined) {
+ bottomValue = `${bottom}px${safeAreaBottom}`;
+ }
+ if (checkSafeAreaLeft) {
+ leftValue = `${left}px${safeAreaLeft}`;
+ }
+ if (checkSafeAreaRight) {
+ leftValue = `${left}px${safeAreaRight}`;
+ }
+
const baseAnimation = createAnimation();
const backdropAnimation = createAnimation();
const wrapperAnimation = createAnimation();
@@ -81,13 +119,13 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
contentAnimation
.addElement(contentEl)
.beforeStyles({
- top: `calc(${top}px + var(--offset-y, 0px))`,
- left: `calc(${left}px + var(--offset-x, 0px))`,
+ top: `calc(${topValue} + var(--offset-y, 0px))`,
+ left: `calc(${leftValue} + var(--offset-x, 0px))`,
'transform-origin': `${originY} ${originX}`,
})
.beforeAddWrite(() => {
- if (bottom !== undefined) {
- contentEl.style.setProperty('bottom', `${bottom}px`);
+ if (bottomValue !== undefined) {
+ contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
}
})
.fromTo('transform', 'scale(0.8)', 'scale(1)');
diff --git a/core/src/components/popover/test/safe-area/index.html b/core/src/components/popover/test/safe-area/index.html
new file mode 100644
index 00000000000..271d5fa02c6
--- /dev/null
+++ b/core/src/components/popover/test/safe-area/index.html
@@ -0,0 +1,159 @@
+
+
+
+
+ Popover - Safe Area
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Popover - Safe Area Positioning
+
+
+
+
+ Test that popovers are positioned away from unsafe areas (shown in red).
+ The popover should be moved up/down to avoid overlapping the safe-area zones.
+
+
+
+
+ Small Popover (Center)
+ Floating popover - positioned in center, no adjustment needed
+
+ Present
+
+
+
+
+ Large Popover
+ Tall content that may extend toward bottom safe area
+
+ Present
+
+
+
+ Trigger Near Bottom
+
+ Near Bottom Right
+
+
+
+
+
+ Option 1
+ Option 2
+ Option 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts b/core/src/components/popover/test/safe-area/popover.e2e.ts
new file mode 100644
index 00000000000..13658a256d0
--- /dev/null
+++ b/core/src/components/popover/test/safe-area/popover.e2e.ts
@@ -0,0 +1,76 @@
+import { expect } from '@playwright/test';
+import { configs, test } from '@utils/test/playwright';
+
+/**
+ * Safe-area tests verify that popovers are correctly positioned
+ * to avoid overlapping with safe-area zones (status bars, navigation bars, etc.)
+ *
+ * This is especially important for Android API 36+ where edge-to-edge mode
+ * is enforced and apps can no longer opt out.
+ */
+
+// Tests that apply to both iOS and MD modes
+configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('popover: safe-area positioning'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/src/components/popover/test/safe-area', config);
+ });
+
+ test('popover pinned to bottom should account for safe-area-bottom in position', async ({ page }, testInfo) => {
+ testInfo.annotations.push({
+ type: 'issue',
+ description: 'https://github.com/ionic-team/ionic-framework/issues/30900',
+ });
+
+ // Use a smaller viewport to force the popover to be constrained
+ await page.setViewportSize({ width: 375, height: 500 });
+
+ const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
+
+ // Click the trigger near the bottom of the screen
+ await page.click('#bottom-trigger');
+ await ionPopoverDidPresent.next();
+
+ // Target the specific popover that was presented (the one with trigger="bottom-trigger")
+ const popover = page.locator('ion-popover[trigger="bottom-trigger"]');
+ const popoverContent = popover.locator('.popover-content');
+
+ // Get the computed bottom style - should include safe-area calc
+ const bottomStyle = await popoverContent.evaluate((el) => el.style.bottom);
+
+ // The bottom should include the safe-area-bottom CSS variable
+ // This ensures the popover is positioned above the unsafe area
+ expect(bottomStyle).toContain('var(--ion-safe-area-bottom');
+ });
+ });
+});
+
+// iOS-specific tests
+configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
+ test.describe(title('popover: safe-area positioning - ios specific'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/src/components/popover/test/safe-area', config);
+ });
+
+ test('floating popover should not have safe-area adjustments', async ({ page }) => {
+ const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
+
+ await page.click('#small-popover-trigger');
+ await ionPopoverDidPresent.next();
+
+ // Target the specific popover
+ const popover = page.locator('ion-popover[trigger="small-popover-trigger"]');
+ const popoverContent = popover.locator('.popover-content');
+
+ // Get the computed top and bottom styles
+ const topStyle = await popoverContent.evaluate((el) => el.style.top);
+ const bottomStyle = await popoverContent.evaluate((el) => el.style.bottom);
+
+ // A floating popover in the middle shouldn't have safe-area adjustments
+ // The top should be a simple calc without safe-area
+ expect(topStyle).not.toContain('var(--ion-safe-area-top');
+ // The bottom should not be set for a floating popover
+ expect(bottomStyle).toBe('');
+ });
+ });
+});
diff --git a/core/src/components/popover/utils.ts b/core/src/components/popover/utils.ts
index 794ebb20884..759091c01db 100644
--- a/core/src/components/popover/utils.ts
+++ b/core/src/components/popover/utils.ts
@@ -30,6 +30,8 @@ export interface PopoverStyles {
bottom?: number;
originX: string;
originY: string;
+ checkSafeAreaTop: boolean;
+ checkSafeAreaBottom: boolean;
checkSafeAreaLeft: boolean;
checkSafeAreaRight: boolean;
arrowTop: number;
@@ -829,6 +831,8 @@ export const calculateWindowAdjustment = (
let bottom;
let originX = contentOriginX;
let originY = contentOriginY;
+ let checkSafeAreaTop = false;
+ let checkSafeAreaBottom = false;
let checkSafeAreaLeft = false;
let checkSafeAreaRight = false;
const triggerTop = triggerCoordinates
@@ -874,26 +878,57 @@ export const calculateWindowAdjustment = (
* We chose 12 here so that the popover position looks a bit nicer as
* it is not right up against the edge of the screen.
*/
- top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
+ top = Math.max(bodyPadding, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
arrowTop = top + contentHeight;
originY = 'bottom';
addPopoverBottomClass = true;
+ /**
+ * If the popover is positioned near the top edge, account for safe area.
+ * This ensures the popover doesn't overlap with status bars or notches.
+ */
+ if (top <= bodyPadding + safeAreaMargin) {
+ checkSafeAreaTop = true;
+ top = bodyPadding;
+ }
+
/**
* If not enough room for popover to appear
* above trigger, then cut it off.
*/
} else {
bottom = bodyPadding;
+ /**
+ * When the popover is pinned to the bottom, account for safe area.
+ * This ensures the popover doesn't overlap with home indicators
+ * or navigation bars (e.g., Android API 36+ edge-to-edge).
+ */
+ checkSafeAreaBottom = true;
}
}
+ /**
+ * Final check: If the popover extends into any safe-area region,
+ * ensure the corresponding flag is set regardless of side.
+ * This handles cases where a side-positioned popover (left/right)
+ * still needs bottom safe-area padding because it extends into that region.
+ */
+ const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
+ if (popoverBottom + safeAreaMargin > bodyHeight) {
+ checkSafeAreaBottom = true;
+ }
+ if (top < safeAreaMargin) {
+ checkSafeAreaTop = true;
+ }
+
return {
top,
left,
bottom,
originX,
originY,
+ checkSafeAreaTop,
+ checkSafeAreaBottom,
checkSafeAreaLeft,
checkSafeAreaRight,
arrowTop,