Skip to content

Commit c434e84

Browse files
conlanpatrekdiasbruno
authored andcommitted
[fixed] Respect overflow css property when determining whether or not a tabbable node is hidden
1 parent 4b19b3d commit c434e84

File tree

4 files changed

+124
-6
lines changed

4 files changed

+124
-6
lines changed

specs/Modal.helpers.spec.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/* eslint-env mocha */
2+
import "should";
3+
import tabbable from "../src/helpers/tabbable";
4+
import "sinon";
5+
6+
export default () => {
7+
describe("tabbable", () => {
8+
describe("without tabbable descendents", () => {
9+
it("returns an empty array", () => {
10+
const elem = document.createElement("div");
11+
tabbable(elem).should.deepEqual([]);
12+
});
13+
});
14+
15+
describe("with tabbable descendents", () => {
16+
let elem;
17+
beforeEach(() => {
18+
elem = document.createElement("div");
19+
document.body.appendChild(elem);
20+
});
21+
22+
afterEach(() => {
23+
document.body.removeChild(elem);
24+
});
25+
26+
it("includes descendent tabbable inputs", () => {
27+
const input = document.createElement("input");
28+
elem.appendChild(input);
29+
tabbable(elem).should.containEql(input);
30+
});
31+
32+
it("includes tabbable non-input elements", () => {
33+
const div = document.createElement("div");
34+
div.tabIndex = 1;
35+
elem.appendChild(div);
36+
tabbable(elem).should.containEql(div);
37+
});
38+
39+
it("includes links with an href", () => {
40+
const a = document.createElement("a");
41+
a.href = "foobar";
42+
a.innerHTML = "link";
43+
elem.appendChild(a);
44+
tabbable(elem).should.containEql(a);
45+
});
46+
47+
it("excludes links without an href or a tabindex", () => {
48+
const a = document.createElement("a");
49+
elem.appendChild(a);
50+
tabbable(elem).should.not.containEql(a);
51+
});
52+
53+
it("excludes descendent inputs if they are not tabbable", () => {
54+
const input = document.createElement("input");
55+
input.tabIndex = -1;
56+
elem.appendChild(input);
57+
tabbable(elem).should.not.containEql(input);
58+
});
59+
60+
it("excludes descendent inputs if they are disabled", () => {
61+
const input = document.createElement("input");
62+
input.disabled = true;
63+
elem.appendChild(input);
64+
tabbable(elem).should.not.containEql(input);
65+
});
66+
67+
it("excludes descendent inputs if they are not displayed", () => {
68+
const input = document.createElement("input");
69+
input.style.display = "none";
70+
elem.appendChild(input);
71+
tabbable(elem).should.not.containEql(input);
72+
});
73+
74+
it("excludes descendent inputs with 0 width and height", () => {
75+
const input = document.createElement("input");
76+
input.style.width = "0";
77+
input.style.height = "0";
78+
input.style.border = "0";
79+
input.style.padding = "0";
80+
elem.appendChild(input);
81+
tabbable(elem).should.not.containEql(input);
82+
});
83+
84+
it("excludes descendents with hidden parents", () => {
85+
const input = document.createElement("input");
86+
elem.style.display = "none";
87+
elem.appendChild(input);
88+
tabbable(elem).should.not.containEql(input);
89+
});
90+
91+
it("excludes inputs with parents that have zero width and height", () => {
92+
const input = document.createElement("input");
93+
elem.style.width = "0";
94+
elem.style.height = "0";
95+
elem.style.overflow = "hidden";
96+
elem.appendChild(input);
97+
tabbable(elem).should.not.containEql(input);
98+
});
99+
100+
it("includes inputs visible because of overflow == visible", () => {
101+
const input = document.createElement("input");
102+
elem.style.width = "0";
103+
elem.style.height = "0";
104+
elem.style.overflow = "visible";
105+
elem.appendChild(input);
106+
tabbable(elem).should.containEql(input);
107+
});
108+
});
109+
});
110+
};

specs/Modal.spec.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/* eslint-env mocha */
2-
import "should";
32
import should from "should";
43
import React, { Component } from "react";
54
import ReactDOM from "react-dom";

specs/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import ModalState from "./Modal.spec";
44
import ModalEvents from "./Modal.events.spec";
55
import ModalStyle from "./Modal.style.spec";
6+
import ModalHelpers from "./Modal.helpers.spec";
67

78
describe("State", ModalState);
89
describe("Style", ModalStyle);
910
describe("Events", ModalEvents);
11+
describe("Helpers", ModalHelpers);

src/helpers/tabbable.js

+12-5
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,24 @@
1212

1313
const tabbableNode = /input|select|textarea|button|object/;
1414

15-
function hidden(el) {
16-
return (
17-
(el.offsetWidth <= 0 && el.offsetHeight <= 0) || el.style.display === "none"
18-
);
15+
function hidesContents(element) {
16+
const zeroSize = element.offsetWidth <= 0 && element.offsetHeight <= 0;
17+
18+
// If the node is empty, this is good enough
19+
if (zeroSize && !element.innerHTML) return true;
20+
21+
// Otherwise we need to check some styles
22+
const style = window.getComputedStyle(element);
23+
return zeroSize
24+
? style.getPropertyValue("overflow") !== "visible"
25+
: style.getPropertyValue("display") == "none";
1926
}
2027

2128
function visible(element) {
2229
let parentElement = element;
2330
while (parentElement) {
2431
if (parentElement === document.body) break;
25-
if (hidden(parentElement)) return false;
32+
if (hidesContents(parentElement)) return false;
2633
parentElement = parentElement.parentNode;
2734
}
2835
return true;

0 commit comments

Comments
 (0)