Skip to content

Commit 740ef61

Browse files
authored
Merge pull request #174 from duhrer/GH-170
GH-170: Fixed optional focus indicator in modals.
2 parents 0c36179 + 037f23a commit 740ef61

8 files changed

+91
-77
lines changed

src/css/focus-fix.css

+2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
position: fixed;
55
top: 0;
66
width: 100%;
7+
z-index: 99998;
78
}
89

910
.gamepad-navigator-focus-overlay-pointer {
1011
border: 3px dashed white;
1112
content: '';
1213
height: 0;
1314
mix-blend-mode: difference;
15+
pointer-events: none;
1416
position: absolute;
1517
width: 0;
1618
z-index: 99999;

src/js/content_scripts/focus-overlay.js

+76-43
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,58 @@
66
fluid.defaults("gamepad.focusOverlay.pointer", {
77
gradeNames: ["gamepad.templateRenderer"],
88
markup: {
9-
container: "<div class='gamepad-navigator-focus-overlay-pointer' hidden></div>"
9+
container: "<div class='gamepad-navigator-focus-overlay-pointer'></div>"
1010
},
11-
model: {
12-
focusOverlayElement: false,
13-
hideFocusOverlay: true
14-
},
15-
modelListeners: {
16-
focusOverlayElement: {
17-
excludeSource: "init",
18-
funcName: "gamepad.focusOverlay.pointer.trackFocusOverlayElement",
11+
listeners: {
12+
"onCreate.listenForWindowFocusEvents": {
13+
funcName: "gamepad.focusOverlay.pointer.listenForWindowFocusEvents",
1914
args: ["{that}"]
2015
}
2116
},
22-
modelRelay: {
23-
hideFocusOverlay: {
24-
source: "{that}.model.hideFocusOverlay",
25-
target: "{that}.model.dom.container.attr.hidden"
17+
invokers: {
18+
handleFocusin: {
19+
funcName: "gamepad.focusOverlay.pointer.handleFocusin",
20+
args: ["{that}", "{arguments}.0"] // event
21+
}
22+
},
23+
modelListeners: {
24+
modalManagerShadowElement: {
25+
funcName: "gamepad.focusOverlay.pointer.listenForModalFocusEvents",
26+
args: ["{that}"]
2627
}
2728
}
2829
});
2930

30-
gamepad.focusOverlay.pointer.trackFocusOverlayElement = function (that) {
31+
gamepad.focusOverlay.pointer.listenForWindowFocusEvents = function (that) {
32+
window.addEventListener("focusin", that.handleFocusin);
33+
};
34+
35+
gamepad.focusOverlay.pointer.listenForModalFocusEvents = function (that) {
36+
var modalManagerShadowElement = fluid.get(that, "model.modalManagerShadowElement");
37+
if (modalManagerShadowElement) {
38+
modalManagerShadowElement.addEventListener("focusin", that.handleFocusin);
39+
}
40+
};
41+
42+
gamepad.focusOverlay.pointer.handleFocusin = function (that) {
3143
var containerDomElement = that.container[0];
32-
if (that.model.focusOverlayElement) {
33-
var clientRect = that.model.focusOverlayElement.getBoundingClientRect();
3444

35-
// Our outline is three pixels, so we adjust everything accordingly.
36-
containerDomElement.style.left = (clientRect.x + window.scrollX - 3) + "px";
37-
containerDomElement.style.top = (clientRect.y + window.scrollY - 3) + "px";
38-
containerDomElement.style.height = (clientRect.height) + "px";
39-
containerDomElement.style.width = (clientRect.width) + "px";
45+
var activeElement = fluid.get(that.model, "modalManagerShadowElement.activeElement") || document.activeElement;
4046

41-
var elementStyles = getComputedStyle(that.model.focusOverlayElement);
42-
var borderRadiusValue = elementStyles.getPropertyValue("border-radius");
43-
if (borderRadiusValue.length) {
44-
containerDomElement.style.borderRadius = borderRadiusValue;
45-
}
46-
else {
47-
containerDomElement.style.borderRadius = 0;
48-
}
47+
var clientRect = activeElement.getBoundingClientRect();
48+
49+
// Our outline is three pixels, so we adjust everything accordingly.
50+
containerDomElement.style.left = (clientRect.x + window.scrollX - 3) + "px";
51+
containerDomElement.style.top = (clientRect.y + window.scrollY - 3) + "px";
52+
containerDomElement.style.height = (clientRect.height) + "px";
53+
containerDomElement.style.width = (clientRect.width) + "px";
54+
55+
var elementStyles = getComputedStyle(activeElement);
56+
var borderRadiusValue = elementStyles.getPropertyValue("border-radius");
57+
if (borderRadiusValue.length) {
58+
containerDomElement.style.borderRadius = borderRadiusValue;
4959
}
5060
else {
51-
containerDomElement.style.height = 0;
52-
containerDomElement.style.width = 0;
5361
containerDomElement.style.borderRadius = 0;
5462
}
5563
};
@@ -60,22 +68,27 @@
6068
container: "<div class='gamepad-navigator-focus-overlay'></div>"
6169
},
6270
model: {
63-
activeModal: false,
6471
shadowElement: false,
6572

66-
focusOverlayElement: "{gamepad.focusOverlay}.model.focusOverlayElement",
73+
modalManagerShadowElement: false,
74+
6775
prefs: {},
6876
hideFocusOverlay: true
6977
},
78+
modelRelay: {
79+
hideFocusOverlay: {
80+
source: "{that}.model.hideFocusOverlay",
81+
target: "{that}.model.dom.container.attr.hidden"
82+
}
83+
},
7084
components: {
7185
pointer: {
7286
container: "{that}.model.shadowElement",
7387
type: "gamepad.focusOverlay.pointer",
7488
createOnEvent: "onShadowReady",
7589
options: {
7690
model: {
77-
focusOverlayElement: "{gamepad.focusOverlay}.model.focusOverlayElement",
78-
hideFocusOverlay: "{gamepad.focusOverlay}.model.hideFocusOverlay"
91+
modalManagerShadowElement: "{gamepad.focusOverlay}.model.modalManagerShadowElement"
7992
}
8093
}
8194
}
@@ -86,22 +99,42 @@
8699
funcName: "gamepad.focusOverlay.shouldDisplayOverlay",
87100
args: ["{that}"]
88101
},
89-
focusOverlayElement: {
90-
excludeSource: "init",
91-
funcName: "gamepad.focusOverlay.shouldDisplayOverlay",
102+
modalManagerShadowElement: {
103+
funcName: "gamepad.focusOverlay.listenForModalFocusEvents",
92104
args: ["{that}"]
93-
},
94-
activeModal: {
95-
excludeSource: "init",
96-
funcName: "gamepad.focusOverlay.shouldDisplayOverlay",
105+
}
106+
},
107+
listeners: {
108+
"onCreate.listenForWindowFocusEvents": {
109+
funcName: "gamepad.focusOverlay.listenForWindowFocusEvents",
97110
args: ["{that}"]
98111
}
112+
},
113+
invokers: {
114+
shouldDisplayOverlay: {
115+
funcName: "gamepad.focusOverlay.shouldDisplayOverlay",
116+
args: ["{that}", "{arguments}.0"] // event
117+
}
99118
}
100119
});
101120

102121
gamepad.focusOverlay.shouldDisplayOverlay = function (that) {
122+
var activeElement = fluid.get(that.model, "modalManagerShadowElement.activeElement") || document.activeElement;
103123
var fixFocus = fluid.get(that, "model.prefs.fixFocus") ? true : false;
104-
var hideFocusOverlay = !fixFocus || !that.model.focusOverlayElement;
124+
var hideFocusOverlay = !fixFocus || !activeElement;
105125
that.applier.change("hideFocusOverlay", hideFocusOverlay);
106126
};
127+
128+
gamepad.focusOverlay.listenForWindowFocusEvents = function (that) {
129+
window.addEventListener("focusin", that.shouldDisplayOverlay);
130+
window.addEventListener("focusout", that.shouldDisplayOverlay);
131+
};
132+
133+
gamepad.focusOverlay.listenForModalFocusEvents = function (that) {
134+
var modalManagerShadowElement = fluid.get(that, "model.modalManagerShadowElement");
135+
if (modalManagerShadowElement) {
136+
modalManagerShadowElement.addEventListener("focusin", that.shouldDisplayOverlay);
137+
modalManagerShadowElement.addEventListener("focusout", that.shouldDisplayOverlay);
138+
}
139+
};
107140
})(fluid);

src/js/content_scripts/input-mapper-content-utils.js

+6-26
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,13 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
240240
// 7 elements, at position 0, add -1 would be (7 + 0 -1) % 7, or 6
241241
that.currentTabIndex = (that.tabbableElements.length + activeElementIndex + increment) % that.tabbableElements.length;
242242
var elementToFocus = that.tabbableElements[that.currentTabIndex];
243-
gamepad.inputMapperUtils.content.focus(that, elementToFocus);
243+
elementToFocus.focus();
244244

245245
// If focus didn't succeed, make one more attempt, to attempt to avoid focus traps (See #118).
246246
if (!that.model.activeModal && elementToFocus !== document.activeElement) {
247247
that.currentTabIndex = (that.tabbableElements.length + activeElementIndex + increment) % that.tabbableElements.length;
248248
var failoverElementToFocus = that.tabbableElements[that.currentTabIndex];
249-
gamepad.inputMapperUtils.content.focus(that, failoverElementToFocus);
249+
failoverElementToFocus.focus();
250250
}
251251
}
252252
};
@@ -392,7 +392,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
392392
// Ensure that we "wrap" in both directions.
393393
var buttonToFocusIndex = (allButtons.length + (currentButtonIndex + increment)) % allButtons.length;
394394
var buttonToFocus = allButtons[buttonToFocusIndex];
395-
gamepad.inputMapperUtils.content.focus(that, buttonToFocus);
395+
buttonToFocus.focus();
396396
buttonToFocus.click();
397397
}
398398
};
@@ -587,7 +587,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
587587
element.classList.toggle("no-focus-indicator", false);
588588
});
589589

590-
gamepad.inputMapperUtils.content.focus(that, element);
590+
element.focus();
591591
}
592592
};
593593

@@ -600,7 +600,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
600600
};
601601

602602
gamepad.inputMapperUtils.content.enterFullscreen = function (that) {
603-
if (document.fullscreen) {
603+
if (document.fullscreenElement) {
604604
that.vibrate();
605605
}
606606
else {
@@ -609,31 +609,11 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
609609
};
610610

611611
gamepad.inputMapperUtils.content.exitFullscreen = function (that) {
612-
if (document.fullscreen) {
612+
if (document.fullscreenElement) {
613613
document.exitFullscreen();
614614
}
615615
else {
616616
that.vibrate();
617617
}
618618
};
619-
620-
/**
621-
*
622-
* Simulate focus on an element, including triggering visible focus.
623-
*
624-
* @param {Object} that - The input mapper component itself.
625-
* @param {HTMLElement} element - The element to simulate focus on.
626-
*
627-
*/
628-
gamepad.inputMapperUtils.content.focus = function (that, element) {
629-
if (that.model.prefs.fixFocus) {
630-
that.applier.change("focusOverlayElement", element);
631-
632-
element.addEventListener("blur", function () {
633-
that.applier.change("focusOverlayElement", false);
634-
});
635-
}
636-
637-
element.focus();
638-
};
639619
})(fluid, jQuery);

src/js/content_scripts/input-mapper.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,8 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
225225
type: "gamepad.focusOverlay",
226226
options: {
227227
model: {
228-
activeModal: "{gamepad.inputMapper}.model.activeModal",
229-
focusOverlayElement: "{gamepad.inputMapper}.model.focusOverlayElement",
230-
prefs: "{gamepad.inputMapper}.model.prefs"
228+
prefs: "{gamepad.inputMapper}.model.prefs",
229+
modalManagerShadowElement: "{gamepad.inputMapper}.model.shadowElement"
231230
}
232231
}
233232
}

src/js/content_scripts/list-selector.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
123123

124124
var toFocus = fluid.get(activeItems, that.model.focusedItemIndex);
125125
if (toFocus) {
126-
gamepad.inputMapperUtils.content.focus(that, toFocus);
126+
toFocus.focus();
127127
}
128128
};
129129

src/js/content_scripts/modal.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
166166
event.preventDefault();
167167
modalManager.applier.change("activeModal", false);
168168
if (modalManager.model.lastExternalFocused && modalManager.model.lastExternalFocused.focus) {
169-
gamepad.inputMapperUtils.content.focus(modalManager, modalManager.model.lastExternalFocused);
169+
modalManager.model.lastExternalFocused.focus();
170170
}
171171
};
172172

@@ -188,7 +188,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
188188
if (tabbableElements.length) {
189189
var elementIndex = reverse ? tabbableElements.length - 1 : 0;
190190
var elementToFocus = tabbableElements[elementIndex];
191-
gamepad.inputMapperUtils.content.focus(that, elementToFocus);
191+
elementToFocus.focus();
192192
}
193193
};
194194

src/js/content_scripts/select-operator.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,6 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
128128
transaction.commit();
129129

130130
that.closeModal(event);
131-
gamepad.inputMapperUtils.content.focus(that, selectElement);
131+
selectElement.focus();
132132
};
133133
})(fluid);

src/js/settings/bindingsPanels.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ https://github.com/fluid-lab/gamepad-navigator/blob/main/LICENSE
275275
var removeButtons = that.locate("removeButton");
276276
var removeButtonToFocus = fluid.get(removeButtons, focusIndexAfterRemove);
277277
if (removeButtonToFocus) {
278-
gamepad.inputMapperUtils.content.focus(that, removeButtonToFocus);
278+
removeButtonToFocus.focus();
279279
}
280280
}
281281
else {

0 commit comments

Comments
 (0)