Skip to content

Commit

Permalink
[fixed] Respect overflow css property when determining whether or not…
Browse files Browse the repository at this point in the history
… a tabbable node is hidden
  • Loading branch information
conlanpatrek authored and diasbruno committed Nov 22, 2017
1 parent 4b19b3d commit c434e84
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 6 deletions.
110 changes: 110 additions & 0 deletions specs/Modal.helpers.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-env mocha */
import "should";
import tabbable from "../src/helpers/tabbable";
import "sinon";

export default () => {
describe("tabbable", () => {
describe("without tabbable descendents", () => {
it("returns an empty array", () => {
const elem = document.createElement("div");
tabbable(elem).should.deepEqual([]);
});
});

describe("with tabbable descendents", () => {
let elem;
beforeEach(() => {
elem = document.createElement("div");
document.body.appendChild(elem);
});

afterEach(() => {
document.body.removeChild(elem);
});

it("includes descendent tabbable inputs", () => {
const input = document.createElement("input");
elem.appendChild(input);
tabbable(elem).should.containEql(input);
});

it("includes tabbable non-input elements", () => {
const div = document.createElement("div");
div.tabIndex = 1;
elem.appendChild(div);
tabbable(elem).should.containEql(div);
});

it("includes links with an href", () => {
const a = document.createElement("a");
a.href = "foobar";
a.innerHTML = "link";
elem.appendChild(a);
tabbable(elem).should.containEql(a);
});

it("excludes links without an href or a tabindex", () => {
const a = document.createElement("a");
elem.appendChild(a);
tabbable(elem).should.not.containEql(a);
});

it("excludes descendent inputs if they are not tabbable", () => {
const input = document.createElement("input");
input.tabIndex = -1;
elem.appendChild(input);
tabbable(elem).should.not.containEql(input);
});

it("excludes descendent inputs if they are disabled", () => {
const input = document.createElement("input");
input.disabled = true;
elem.appendChild(input);
tabbable(elem).should.not.containEql(input);
});

it("excludes descendent inputs if they are not displayed", () => {
const input = document.createElement("input");
input.style.display = "none";
elem.appendChild(input);
tabbable(elem).should.not.containEql(input);
});

it("excludes descendent inputs with 0 width and height", () => {
const input = document.createElement("input");
input.style.width = "0";
input.style.height = "0";
input.style.border = "0";
input.style.padding = "0";
elem.appendChild(input);
tabbable(elem).should.not.containEql(input);
});

it("excludes descendents with hidden parents", () => {
const input = document.createElement("input");
elem.style.display = "none";
elem.appendChild(input);
tabbable(elem).should.not.containEql(input);
});

it("excludes inputs with parents that have zero width and height", () => {
const input = document.createElement("input");
elem.style.width = "0";
elem.style.height = "0";
elem.style.overflow = "hidden";
elem.appendChild(input);
tabbable(elem).should.not.containEql(input);
});

it("includes inputs visible because of overflow == visible", () => {
const input = document.createElement("input");
elem.style.width = "0";
elem.style.height = "0";
elem.style.overflow = "visible";
elem.appendChild(input);
tabbable(elem).should.containEql(input);
});
});
});
};
1 change: 0 additions & 1 deletion specs/Modal.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-env mocha */
import "should";
import should from "should";
import React, { Component } from "react";
import ReactDOM from "react-dom";
Expand Down
2 changes: 2 additions & 0 deletions specs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import ModalState from "./Modal.spec";
import ModalEvents from "./Modal.events.spec";
import ModalStyle from "./Modal.style.spec";
import ModalHelpers from "./Modal.helpers.spec";

describe("State", ModalState);
describe("Style", ModalStyle);
describe("Events", ModalEvents);
describe("Helpers", ModalHelpers);
17 changes: 12 additions & 5 deletions src/helpers/tabbable.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,24 @@

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

function hidden(el) {
return (
(el.offsetWidth <= 0 && el.offsetHeight <= 0) || el.style.display === "none"
);
function hidesContents(element) {
const zeroSize = element.offsetWidth <= 0 && element.offsetHeight <= 0;

// If the node is empty, this is good enough
if (zeroSize && !element.innerHTML) return true;

// Otherwise we need to check some styles
const style = window.getComputedStyle(element);
return zeroSize
? style.getPropertyValue("overflow") !== "visible"
: style.getPropertyValue("display") == "none";
}

function visible(element) {
let parentElement = element;
while (parentElement) {
if (parentElement === document.body) break;
if (hidden(parentElement)) return false;
if (hidesContents(parentElement)) return false;
parentElement = parentElement.parentNode;
}
return true;
Expand Down

0 comments on commit c434e84

Please sign in to comment.