diff --git a/src/css/focus-fix.css b/src/css/focus-fix.css index f74041c..3d6df39 100644 --- a/src/css/focus-fix.css +++ b/src/css/focus-fix.css @@ -4,6 +4,7 @@ position: fixed; top: 0; width: 100%; + z-index: 99998; } .gamepad-navigator-focus-overlay-pointer { @@ -11,6 +12,7 @@ content: ''; height: 0; mix-blend-mode: difference; + pointer-events: none; position: absolute; width: 0; z-index: 99999; diff --git a/src/js/content_scripts/focus-overlay.js b/src/js/content_scripts/focus-overlay.js index 0491c5c..9e0866a 100644 --- a/src/js/content_scripts/focus-overlay.js +++ b/src/js/content_scripts/focus-overlay.js @@ -6,50 +6,58 @@ fluid.defaults("gamepad.focusOverlay.pointer", { gradeNames: ["gamepad.templateRenderer"], markup: { - container: "
" + container: "" }, - model: { - focusOverlayElement: false, - hideFocusOverlay: true - }, - modelListeners: { - focusOverlayElement: { - excludeSource: "init", - funcName: "gamepad.focusOverlay.pointer.trackFocusOverlayElement", + listeners: { + "onCreate.listenForWindowFocusEvents": { + funcName: "gamepad.focusOverlay.pointer.listenForWindowFocusEvents", args: ["{that}"] } }, - modelRelay: { - hideFocusOverlay: { - source: "{that}.model.hideFocusOverlay", - target: "{that}.model.dom.container.attr.hidden" + invokers: { + handleFocusin: { + funcName: "gamepad.focusOverlay.pointer.handleFocusin", + args: ["{that}", "{arguments}.0"] // event + } + }, + modelListeners: { + modalManagerShadowElement: { + funcName: "gamepad.focusOverlay.pointer.listenForModalFocusEvents", + args: ["{that}"] } } }); - gamepad.focusOverlay.pointer.trackFocusOverlayElement = function (that) { + gamepad.focusOverlay.pointer.listenForWindowFocusEvents = function (that) { + window.addEventListener("focusin", that.handleFocusin); + }; + + gamepad.focusOverlay.pointer.listenForModalFocusEvents = function (that) { + var modalManagerShadowElement = fluid.get(that, "model.modalManagerShadowElement"); + if (modalManagerShadowElement) { + modalManagerShadowElement.addEventListener("focusin", that.handleFocusin); + } + }; + + gamepad.focusOverlay.pointer.handleFocusin = function (that) { var containerDomElement = that.container[0]; - if (that.model.focusOverlayElement) { - var clientRect = that.model.focusOverlayElement.getBoundingClientRect(); - // Our outline is three pixels, so we adjust everything accordingly. - containerDomElement.style.left = (clientRect.x + window.scrollX - 3) + "px"; - containerDomElement.style.top = (clientRect.y + window.scrollY - 3) + "px"; - containerDomElement.style.height = (clientRect.height) + "px"; - containerDomElement.style.width = (clientRect.width) + "px"; + var activeElement = fluid.get(that.model, "modalManagerShadowElement.activeElement") || document.activeElement; - var elementStyles = getComputedStyle(that.model.focusOverlayElement); - var borderRadiusValue = elementStyles.getPropertyValue("border-radius"); - if (borderRadiusValue.length) { - containerDomElement.style.borderRadius = borderRadiusValue; - } - else { - containerDomElement.style.borderRadius = 0; - } + var clientRect = activeElement.getBoundingClientRect(); + + // Our outline is three pixels, so we adjust everything accordingly. + containerDomElement.style.left = (clientRect.x + window.scrollX - 3) + "px"; + containerDomElement.style.top = (clientRect.y + window.scrollY - 3) + "px"; + containerDomElement.style.height = (clientRect.height) + "px"; + containerDomElement.style.width = (clientRect.width) + "px"; + + var elementStyles = getComputedStyle(activeElement); + var borderRadiusValue = elementStyles.getPropertyValue("border-radius"); + if (borderRadiusValue.length) { + containerDomElement.style.borderRadius = borderRadiusValue; } else { - containerDomElement.style.height = 0; - containerDomElement.style.width = 0; containerDomElement.style.borderRadius = 0; } }; @@ -60,13 +68,19 @@ container: "" }, model: { - activeModal: false, shadowElement: false, - focusOverlayElement: "{gamepad.focusOverlay}.model.focusOverlayElement", + modalManagerShadowElement: false, + prefs: {}, hideFocusOverlay: true }, + modelRelay: { + hideFocusOverlay: { + source: "{that}.model.hideFocusOverlay", + target: "{that}.model.dom.container.attr.hidden" + } + }, components: { pointer: { container: "{that}.model.shadowElement", @@ -74,8 +88,7 @@ createOnEvent: "onShadowReady", options: { model: { - focusOverlayElement: "{gamepad.focusOverlay}.model.focusOverlayElement", - hideFocusOverlay: "{gamepad.focusOverlay}.model.hideFocusOverlay" + modalManagerShadowElement: "{gamepad.focusOverlay}.model.modalManagerShadowElement" } } } @@ -86,22 +99,42 @@ funcName: "gamepad.focusOverlay.shouldDisplayOverlay", args: ["{that}"] }, - focusOverlayElement: { - excludeSource: "init", - funcName: "gamepad.focusOverlay.shouldDisplayOverlay", + modalManagerShadowElement: { + funcName: "gamepad.focusOverlay.listenForModalFocusEvents", args: ["{that}"] - }, - activeModal: { - excludeSource: "init", - funcName: "gamepad.focusOverlay.shouldDisplayOverlay", + } + }, + listeners: { + "onCreate.listenForWindowFocusEvents": { + funcName: "gamepad.focusOverlay.listenForWindowFocusEvents", args: ["{that}"] } + }, + invokers: { + shouldDisplayOverlay: { + funcName: "gamepad.focusOverlay.shouldDisplayOverlay", + args: ["{that}", "{arguments}.0"] // event + } } }); gamepad.focusOverlay.shouldDisplayOverlay = function (that) { + var activeElement = fluid.get(that.model, "modalManagerShadowElement.activeElement") || document.activeElement; var fixFocus = fluid.get(that, "model.prefs.fixFocus") ? true : false; - var hideFocusOverlay = !fixFocus || !that.model.focusOverlayElement; + var hideFocusOverlay = !fixFocus || !activeElement; that.applier.change("hideFocusOverlay", hideFocusOverlay); }; + + gamepad.focusOverlay.listenForWindowFocusEvents = function (that) { + window.addEventListener("focusin", that.shouldDisplayOverlay); + window.addEventListener("focusout", that.shouldDisplayOverlay); + }; + + gamepad.focusOverlay.listenForModalFocusEvents = function (that) { + var modalManagerShadowElement = fluid.get(that, "model.modalManagerShadowElement"); + if (modalManagerShadowElement) { + modalManagerShadowElement.addEventListener("focusin", that.shouldDisplayOverlay); + modalManagerShadowElement.addEventListener("focusout", that.shouldDisplayOverlay); + } + }; })(fluid); diff --git a/src/js/content_scripts/input-mapper-content-utils.js b/src/js/content_scripts/input-mapper-content-utils.js index 4d3f4fe..6aa2a89 100644 --- a/src/js/content_scripts/input-mapper-content-utils.js +++ b/src/js/content_scripts/input-mapper-content-utils.js @@ -240,13 +240,13 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE // 7 elements, at position 0, add -1 would be (7 + 0 -1) % 7, or 6 that.currentTabIndex = (that.tabbableElements.length + activeElementIndex + increment) % that.tabbableElements.length; var elementToFocus = that.tabbableElements[that.currentTabIndex]; - gamepad.inputMapperUtils.content.focus(that, elementToFocus); + elementToFocus.focus(); // If focus didn't succeed, make one more attempt, to attempt to avoid focus traps (See #118). if (!that.model.activeModal && elementToFocus !== document.activeElement) { that.currentTabIndex = (that.tabbableElements.length + activeElementIndex + increment) % that.tabbableElements.length; var failoverElementToFocus = that.tabbableElements[that.currentTabIndex]; - gamepad.inputMapperUtils.content.focus(that, failoverElementToFocus); + failoverElementToFocus.focus(); } } }; @@ -392,7 +392,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE // Ensure that we "wrap" in both directions. var buttonToFocusIndex = (allButtons.length + (currentButtonIndex + increment)) % allButtons.length; var buttonToFocus = allButtons[buttonToFocusIndex]; - gamepad.inputMapperUtils.content.focus(that, buttonToFocus); + buttonToFocus.focus(); buttonToFocus.click(); } }; @@ -587,7 +587,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE element.classList.toggle("no-focus-indicator", false); }); - gamepad.inputMapperUtils.content.focus(that, element); + element.focus(); } }; @@ -600,7 +600,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE }; gamepad.inputMapperUtils.content.enterFullscreen = function (that) { - if (document.fullscreen) { + if (document.fullscreenElement) { that.vibrate(); } else { @@ -609,31 +609,11 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE }; gamepad.inputMapperUtils.content.exitFullscreen = function (that) { - if (document.fullscreen) { + if (document.fullscreenElement) { document.exitFullscreen(); } else { that.vibrate(); } }; - - /** - * - * Simulate focus on an element, including triggering visible focus. - * - * @param {Object} that - The input mapper component itself. - * @param {HTMLElement} element - The element to simulate focus on. - * - */ - gamepad.inputMapperUtils.content.focus = function (that, element) { - if (that.model.prefs.fixFocus) { - that.applier.change("focusOverlayElement", element); - - element.addEventListener("blur", function () { - that.applier.change("focusOverlayElement", false); - }); - } - - element.focus(); - }; })(fluid, jQuery); diff --git a/src/js/content_scripts/input-mapper.js b/src/js/content_scripts/input-mapper.js index c9e8c4f..4a3e318 100644 --- a/src/js/content_scripts/input-mapper.js +++ b/src/js/content_scripts/input-mapper.js @@ -225,9 +225,8 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE type: "gamepad.focusOverlay", options: { model: { - activeModal: "{gamepad.inputMapper}.model.activeModal", - focusOverlayElement: "{gamepad.inputMapper}.model.focusOverlayElement", - prefs: "{gamepad.inputMapper}.model.prefs" + prefs: "{gamepad.inputMapper}.model.prefs", + modalManagerShadowElement: "{gamepad.inputMapper}.model.shadowElement" } } } diff --git a/src/js/content_scripts/list-selector.js b/src/js/content_scripts/list-selector.js index 2897bcd..edacb41 100644 --- a/src/js/content_scripts/list-selector.js +++ b/src/js/content_scripts/list-selector.js @@ -123,7 +123,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE var toFocus = fluid.get(activeItems, that.model.focusedItemIndex); if (toFocus) { - gamepad.inputMapperUtils.content.focus(that, toFocus); + toFocus.focus(); } }; diff --git a/src/js/content_scripts/modal.js b/src/js/content_scripts/modal.js index a9c233e..73848e0 100644 --- a/src/js/content_scripts/modal.js +++ b/src/js/content_scripts/modal.js @@ -166,7 +166,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE event.preventDefault(); modalManager.applier.change("activeModal", false); if (modalManager.model.lastExternalFocused && modalManager.model.lastExternalFocused.focus) { - gamepad.inputMapperUtils.content.focus(modalManager, modalManager.model.lastExternalFocused); + modalManager.model.lastExternalFocused.focus(); } }; @@ -188,7 +188,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE if (tabbableElements.length) { var elementIndex = reverse ? tabbableElements.length - 1 : 0; var elementToFocus = tabbableElements[elementIndex]; - gamepad.inputMapperUtils.content.focus(that, elementToFocus); + elementToFocus.focus(); } }; diff --git a/src/js/content_scripts/select-operator.js b/src/js/content_scripts/select-operator.js index 57fa090..bafc406 100644 --- a/src/js/content_scripts/select-operator.js +++ b/src/js/content_scripts/select-operator.js @@ -128,6 +128,6 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE transaction.commit(); that.closeModal(event); - gamepad.inputMapperUtils.content.focus(that, selectElement); + selectElement.focus(); }; })(fluid); diff --git a/src/js/settings/bindingsPanels.js b/src/js/settings/bindingsPanels.js index 51106dc..135307e 100644 --- a/src/js/settings/bindingsPanels.js +++ b/src/js/settings/bindingsPanels.js @@ -275,7 +275,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE var removeButtons = that.locate("removeButton"); var removeButtonToFocus = fluid.get(removeButtons, focusIndexAfterRemove); if (removeButtonToFocus) { - gamepad.inputMapperUtils.content.focus(that, removeButtonToFocus); + removeButtonToFocus.focus(); } } else {