@@ -68,6 +68,65 @@ function attachSelect2PositioningFix($field, $modal) {
6868 });
6969 }
7070
71+ /* *
72+ * Prevent infinite focus loops when third-party overlays (like Colorbox)
73+ * attempt to grab focus while a Bootstrap modal is open.
74+ */
75+ (function installFocusGuard () {
76+ if (window ._backpackFocusGuardInstalled ) return ;
77+ window ._backpackFocusGuardInstalled = true ;
78+
79+ // Keep a short-lived map of recently-handled targets to avoid rapid re-entrancy
80+ const recentFocusTargets = new WeakMap ();
81+ const REENTRANCY_WINDOW_MS = 50 ;
82+
83+ document .addEventListener (' focusin' , function (e ) {
84+ try {
85+ // If there's no Bootstrap modal shown, do nothing.
86+ const openModal = document .querySelector (' .modal.show' );
87+ if (! openModal) return ;
88+
89+ const target = e .target ;
90+
91+ // If the focus is inside the open modal, allow it.
92+ if (openModal .contains (target)) return ;
93+
94+ // If the event is user-initiated, allow it.
95+ if (e .isTrusted ) return ;
96+
97+ // If we've recently handled focus for this target, bail out to prevent loops
98+ const last = recentFocusTargets .get (target) || 0 ;
99+ const now = Date .now ();
100+ if (now - last < REENTRANCY_WINDOW_MS ) {
101+ if (typeof e .stopImmediatePropagation === ' function' ) e .stopImmediatePropagation ();
102+ if (typeof e .preventDefault === ' function' ) e .preventDefault ();
103+ return ;
104+ }
105+
106+ // Mark this target as handled for a short window
107+ recentFocusTargets .set (target, now);
108+
109+ // Stop other listeners from handling this focusin to avoid re-entrant focus loops
110+ if (typeof e .stopImmediatePropagation === ' function' ) {
111+ e .stopImmediatePropagation ();
112+ }
113+
114+ // restore focus to a sensible element inside the modal
115+ setTimeout (function () {
116+ const restore = openModal .querySelector (' [autofocus], input, select, textarea, button, [tabindex]:not([tabindex="-1"])' );
117+ if (restore && typeof restore .focus === ' function' ) {
118+ try { restore .focus (); } catch (err) { /* ignore */ }
119+ } else {
120+ // fallback: focus the modal itself
121+ try { openModal .focus (); } catch (err) { /* ignore */ }
122+ }
123+ }, 0 );
124+ } catch (err) {
125+ // ignore any errors from the guard
126+ }
127+ }, true ); // capture phase so we can stop propagation before bubble handlers
128+ })();
129+
71130 /* *
72131 * Set up MutationObserver for repeatable fields in a specific modal
73132 * Watches for new rows and initializes their fields automatically
0 commit comments