From c8258b10037096bb6ef0a852c5ddd20d4e6b299e Mon Sep 17 00:00:00 2001 From: Jay George Date: Tue, 16 Dec 2025 12:53:53 +0000 Subject: [PATCH 1/9] Focus-trap stacks (works for narrow stacks) --- resources/js/components/ui/Stack/Stack.vue | 50 +++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/resources/js/components/ui/Stack/Stack.vue b/resources/js/components/ui/Stack/Stack.vue index 7afa41e40f5..e91b47ef535 100644 --- a/resources/js/components/ui/Stack/Stack.vue +++ b/resources/js/components/ui/Stack/Stack.vue @@ -25,6 +25,7 @@
0; + }; + + // Try to activate, with retries if needed + const tryActivate = (attempts = 0) => { + if (attempts > 20) { + // Give up after 20 attempts (2 seconds) + return; + } + + if (hasTabbableElements()) { + if (!this.focusTrap) { + this.focusTrap = createFocusTrap(this.$refs.stackContent, { + escapeDeactivates: false, // We handle ESC separately + returnFocusOnDeactivate: true, + }); + } + try { + this.focusTrap.activate(); + } catch (e) { + // If activation fails, retry after a short delay + setTimeout(() => tryActivate(attempts + 1), 100); + } + } else { + // No tabbable elements yet, retry after a short delay + setTimeout(() => tryActivate(attempts + 1), 100); + } + }; + + tryActivate(); }); }, }, @@ -237,8 +270,8 @@ export default { this.$nextTick(() => { this.visible = true; this.$emit('opened'); - // Initialize focus trap for narrow stacks - if (this.narrow && this.isTopStack) { + // Initialize focus trap for top stack + if (this.isTopStack) { this.initFocusTrap(); } }); From d9222b306571954769b63aa9b66ec50ba0b57a3d Mon Sep 17 00:00:00 2001 From: Jay George Date: Tue, 16 Dec 2025 13:18:02 +0000 Subject: [PATCH 3/9] Fix the initial focus on a narrow stack --- resources/js/components/ui/Stack/Stack.vue | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/resources/js/components/ui/Stack/Stack.vue b/resources/js/components/ui/Stack/Stack.vue index 94ca4bd93ba..97be5c7fe6d 100644 --- a/resources/js/components/ui/Stack/Stack.vue +++ b/resources/js/components/ui/Stack/Stack.vue @@ -226,6 +226,22 @@ export default { return; } + // Find the first input field (prioritize inputs over buttons) + const findFirstInput = () => { + // First, try to find an input, textarea, select, or contenteditable element + const firstInput = this.$refs.stackContent.querySelector( + 'input:not([readonly]):not([type="hidden"]), textarea:not([readonly]), select, [contenteditable="true"]' + ); + if (firstInput) { + return firstInput; + } + // Fallback to any tabbable element + const tabbable = this.$refs.stackContent.querySelector( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + return tabbable; + }; + // Check if there are any tabbable elements const hasTabbableElements = () => { const tabbable = this.$refs.stackContent.querySelectorAll( @@ -243,9 +259,11 @@ export default { if (hasTabbableElements()) { if (!this.focusTrap) { + // Use a function for initialFocus so it's evaluated at activation time this.focusTrap = createFocusTrap(this.$refs.stackContent, { escapeDeactivates: false, // We handle ESC separately returnFocusOnDeactivate: true, + initialFocus: () => findFirstInput() || undefined, // Focus first input if available }); } try { From 6597d7896f6c0db115f4d6df4b0ef534ea5c64dc Mon Sep 17 00:00:00 2001 From: Jay George Date: Tue, 16 Dec 2025 13:22:50 +0000 Subject: [PATCH 4/9] Allow clicks outside to work --- resources/js/components/ui/Stack/Stack.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/js/components/ui/Stack/Stack.vue b/resources/js/components/ui/Stack/Stack.vue index 97be5c7fe6d..e0b6cfbcdf3 100644 --- a/resources/js/components/ui/Stack/Stack.vue +++ b/resources/js/components/ui/Stack/Stack.vue @@ -263,6 +263,7 @@ export default { this.focusTrap = createFocusTrap(this.$refs.stackContent, { escapeDeactivates: false, // We handle ESC separately returnFocusOnDeactivate: true, + allowOutsideClick: true, // Allow clicks outside to work (e.g., hit area) initialFocus: () => findFirstInput() || undefined, // Focus first input if available }); } From ad1e4057f32b728fdfd8351246290a9cca74978f Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 19 Dec 2025 16:15:13 -0500 Subject: [PATCH 5/9] Revert "Allow clicks outside to work" This reverts commit 6597d7896f6c0db115f4d6df4b0ef534ea5c64dc. --- resources/js/components/ui/Stack/Stack.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/js/components/ui/Stack/Stack.vue b/resources/js/components/ui/Stack/Stack.vue index e0b6cfbcdf3..97be5c7fe6d 100644 --- a/resources/js/components/ui/Stack/Stack.vue +++ b/resources/js/components/ui/Stack/Stack.vue @@ -263,7 +263,6 @@ export default { this.focusTrap = createFocusTrap(this.$refs.stackContent, { escapeDeactivates: false, // We handle ESC separately returnFocusOnDeactivate: true, - allowOutsideClick: true, // Allow clicks outside to work (e.g., hit area) initialFocus: () => findFirstInput() || undefined, // Focus first input if available }); } From 713dc26926e634c73124f94dc5eb6ac1bb142145 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 19 Dec 2025 16:15:20 -0500 Subject: [PATCH 6/9] Revert "Fix the initial focus on a narrow stack" This reverts commit d9222b306571954769b63aa9b66ec50ba0b57a3d. --- resources/js/components/ui/Stack/Stack.vue | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/resources/js/components/ui/Stack/Stack.vue b/resources/js/components/ui/Stack/Stack.vue index 97be5c7fe6d..94ca4bd93ba 100644 --- a/resources/js/components/ui/Stack/Stack.vue +++ b/resources/js/components/ui/Stack/Stack.vue @@ -226,22 +226,6 @@ export default { return; } - // Find the first input field (prioritize inputs over buttons) - const findFirstInput = () => { - // First, try to find an input, textarea, select, or contenteditable element - const firstInput = this.$refs.stackContent.querySelector( - 'input:not([readonly]):not([type="hidden"]), textarea:not([readonly]), select, [contenteditable="true"]' - ); - if (firstInput) { - return firstInput; - } - // Fallback to any tabbable element - const tabbable = this.$refs.stackContent.querySelector( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ); - return tabbable; - }; - // Check if there are any tabbable elements const hasTabbableElements = () => { const tabbable = this.$refs.stackContent.querySelectorAll( @@ -259,11 +243,9 @@ export default { if (hasTabbableElements()) { if (!this.focusTrap) { - // Use a function for initialFocus so it's evaluated at activation time this.focusTrap = createFocusTrap(this.$refs.stackContent, { escapeDeactivates: false, // We handle ESC separately returnFocusOnDeactivate: true, - initialFocus: () => findFirstInput() || undefined, // Focus first input if available }); } try { From c1d74dbb916bcc9de4b0ba5e07f30b727589929a Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 19 Dec 2025 16:15:25 -0500 Subject: [PATCH 7/9] Revert "Make stacks focusable (works for all stacks)" This reverts commit 13a640234ef548dd1694f6ace41f4240c5632dbe. --- resources/js/components/ui/Stack/Stack.vue | 55 +++++----------------- 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/resources/js/components/ui/Stack/Stack.vue b/resources/js/components/ui/Stack/Stack.vue index 94ca4bd93ba..e91b47ef535 100644 --- a/resources/js/components/ui/Stack/Stack.vue +++ b/resources/js/components/ui/Stack/Stack.vue @@ -143,8 +143,8 @@ export default { watch: { isTopStack(newVal) { - // When this stack becomes the top stack while visible, activate focus trap - if (newVal && this.visible && !this.focusTrap) { + // When this narrow stack becomes the top stack while visible, activate focus trap + if (newVal && this.visible && this.narrow && !this.focusTrap) { this.initFocusTrap(); } }, @@ -216,51 +216,18 @@ export default { }, initFocusTrap() { - if (!this.isTopStack) { + if (!this.narrow || !this.isTopStack) { return; } - // Wait for content to be rendered and check for tabbable elements this.$nextTick(() => { - if (!this.$refs.stackContent) { - return; + if (this.$refs.stackContent) { + this.focusTrap = createFocusTrap(this.$refs.stackContent, { + escapeDeactivates: false, // We handle ESC separately + returnFocusOnDeactivate: true, + }); + this.focusTrap.activate(); } - - // Check if there are any tabbable elements - const hasTabbableElements = () => { - const tabbable = this.$refs.stackContent.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ); - return tabbable.length > 0; - }; - - // Try to activate, with retries if needed - const tryActivate = (attempts = 0) => { - if (attempts > 20) { - // Give up after 20 attempts (2 seconds) - return; - } - - if (hasTabbableElements()) { - if (!this.focusTrap) { - this.focusTrap = createFocusTrap(this.$refs.stackContent, { - escapeDeactivates: false, // We handle ESC separately - returnFocusOnDeactivate: true, - }); - } - try { - this.focusTrap.activate(); - } catch (e) { - // If activation fails, retry after a short delay - setTimeout(() => tryActivate(attempts + 1), 100); - } - } else { - // No tabbable elements yet, retry after a short delay - setTimeout(() => tryActivate(attempts + 1), 100); - } - }; - - tryActivate(); }); }, }, @@ -270,8 +237,8 @@ export default { this.$nextTick(() => { this.visible = true; this.$emit('opened'); - // Initialize focus trap for top stack - if (this.isTopStack) { + // Initialize focus trap for narrow stacks + if (this.narrow && this.isTopStack) { this.initFocusTrap(); } }); From c5db70fc09d65e42fc9fac8820e1d2341595d643 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 19 Dec 2025 16:15:43 -0500 Subject: [PATCH 8/9] Revert "Focus-trap stacks (works for narrow stacks)" This reverts commit c8258b10037096bb6ef0a852c5ddd20d4e6b299e. --- resources/js/components/ui/Stack/Stack.vue | 50 +--------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/resources/js/components/ui/Stack/Stack.vue b/resources/js/components/ui/Stack/Stack.vue index e91b47ef535..7afa41e40f5 100644 --- a/resources/js/components/ui/Stack/Stack.vue +++ b/resources/js/components/ui/Stack/Stack.vue @@ -25,7 +25,6 @@
- + + +
@@ -40,7 +42,11 @@