From 0fbdf41678d80b9fde2cb587f6b400715812cbbd Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Tue, 28 Oct 2025 10:34:49 +1100 Subject: [PATCH] fix(mobile): move modals to document.body to escape panel DOM constraints Fix mobile modal positioning by moving modals to document.body on first use. Root cause: Modals were nested inside .rpg-panel which has 'transform' in its transition property (line 40 of style.css). This creates a containing block that constrains position:fixed children to the panel viewport instead of the document viewport, causing modals to be cut off on mobile. Changes: 1. confirmDialog.js - Modified showConfirmDialog() and showAlertDialog(): - Check if modal is already in document.body container - Create 'document-body-modals' container at body level (once) - Move modal to body container to escape panel constraints - Container has pointer-events: none to pass clicks through - Individual modals have pointer-events: auto to receive clicks 2. style.css - Added pointer-events: auto to .rpg-modal: - Ensures clicks work when modal is in pointer-events: none container Result: - Modals now render over entire SillyTavern viewport - Properly centered with full z-index stacking - No longer constrained by panel's transform/stacking context - Works correctly on both desktop and mobile --- src/systems/dashboard/confirmDialog.js | 54 +++++++++++++++++++++----- style.css | 1 + 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/systems/dashboard/confirmDialog.js b/src/systems/dashboard/confirmDialog.js index 2fe2c59..0acdacd 100644 --- a/src/systems/dashboard/confirmDialog.js +++ b/src/systems/dashboard/confirmDialog.js @@ -31,6 +31,28 @@ export function showConfirmDialog(options) { // Get modal elements const modal = document.getElementById('rpg-confirm-dialog'); + + if (!modal) { + console.error('[ConfirmDialog] Modal not found'); + return resolve(false); + } + + // CRITICAL: Move modal to document.body on first use to escape panel constraints + // The panel has transform in its transition which creates a containing block, + // constraining position:fixed children to the panel instead of viewport + if (modal.parentElement?.id !== 'document-body-modals') { + // Create container for modals at body level (only once) + let bodyModalsContainer = document.getElementById('document-body-modals'); + if (!bodyModalsContainer) { + bodyModalsContainer = document.createElement('div'); + bodyModalsContainer.id = 'document-body-modals'; + bodyModalsContainer.style.cssText = 'position: fixed; inset: 0; pointer-events: none; z-index: 10000;'; + document.body.appendChild(bodyModalsContainer); + } + bodyModalsContainer.appendChild(modal); + console.log('[ConfirmDialog] Moved modal to document.body to escape panel constraints'); + } + const modalContent = modal.querySelector('.rpg-confirm-content'); const icon = document.getElementById('rpg-confirm-icon'); const titleEl = document.getElementById('rpg-confirm-title'); @@ -39,11 +61,6 @@ export function showConfirmDialog(options) { const cancelBtn = document.getElementById('rpg-confirm-cancel'); const closeBtn = modal.querySelector('.rpg-confirm-close'); - if (!modal) { - console.error('[ConfirmDialog] Modal not found'); - return resolve(false); - } - // Set icon based on variant const iconMap = { danger: 'fa-solid fa-triangle-exclamation', @@ -139,6 +156,28 @@ export function showAlertDialog(options) { // Get modal elements const modal = document.getElementById('rpg-confirm-dialog'); + + if (!modal) { + console.error('[ConfirmDialog] Modal not found'); + return resolve(); + } + + // CRITICAL: Move modal to document.body on first use to escape panel constraints + // The panel has transform in its transition which creates a containing block, + // constraining position:fixed children to the panel instead of viewport + if (modal.parentElement?.id !== 'document-body-modals') { + // Create container for modals at body level (only once) + let bodyModalsContainer = document.getElementById('document-body-modals'); + if (!bodyModalsContainer) { + bodyModalsContainer = document.createElement('div'); + bodyModalsContainer.id = 'document-body-modals'; + bodyModalsContainer.style.cssText = 'position: fixed; inset: 0; pointer-events: none; z-index: 10000;'; + document.body.appendChild(bodyModalsContainer); + } + bodyModalsContainer.appendChild(modal); + console.log('[ConfirmDialog] Moved modal to document.body to escape panel constraints'); + } + const modalContent = modal.querySelector('.rpg-confirm-content'); const icon = document.getElementById('rpg-confirm-icon'); const titleEl = document.getElementById('rpg-confirm-title'); @@ -147,11 +186,6 @@ export function showAlertDialog(options) { const cancelBtn = document.getElementById('rpg-confirm-cancel'); const closeBtn = modal.querySelector('.rpg-confirm-close'); - if (!modal) { - console.error('[ConfirmDialog] Modal not found'); - return resolve(); - } - // Set icon based on variant const iconMap = { danger: 'fa-solid fa-triangle-exclamation', diff --git a/style.css b/style.css index d5140e2..81747f5 100644 --- a/style.css +++ b/style.css @@ -1441,6 +1441,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld { justify-content: center; align-items: center; padding: 1rem; /* Ensure content doesn't touch screen edges on mobile */ + pointer-events: auto; /* Ensure clicks work when moved to body container */ } /* Modal Content Container */