2716 lines
106 KiB
Plaintext
2716 lines
106 KiB
Plaintext
/* eslint-disable */ // @ts-nocheck
|
|
// Copied from https://github.com/medialize/ally.js
|
|
// License: MIT
|
|
// Copyright (c) 2015 Rodney Rehm
|
|
//
|
|
// Entrypoint: ally.js/maintain/tab-focus
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
Object.defineProperty(exports, "default", {
|
|
enumerable: true,
|
|
get: function() {
|
|
return _default;
|
|
}
|
|
});
|
|
const _interop_require_default = require("@swc/helpers/_/_interop_require_default");
|
|
const _platform = /*#__PURE__*/ _interop_require_default._(require("next/dist/compiled/platform"));
|
|
const _cssescape = /*#__PURE__*/ _interop_require_default._(require("next/dist/compiled/css.escape"));
|
|
// input may be undefined, selector-tring, Node, NodeList, HTMLCollection, array of Nodes
|
|
// yes, to some extent this is a bad replica of jQuery's constructor function
|
|
function nodeArray(input) {
|
|
if (!input) {
|
|
return [];
|
|
}
|
|
if (Array.isArray(input)) {
|
|
return input;
|
|
}
|
|
// instanceof Node - does not work with iframes
|
|
if (input.nodeType !== undefined) {
|
|
return [
|
|
input
|
|
];
|
|
}
|
|
if (typeof input === "string") {
|
|
input = document.querySelectorAll(input);
|
|
}
|
|
if (input.length !== undefined) {
|
|
return [].slice.call(input, 0);
|
|
}
|
|
throw new TypeError("unexpected input " + String(input));
|
|
}
|
|
function contextToElement(_ref) {
|
|
var context = _ref.context, _ref$label = _ref.label, label = _ref$label === undefined ? "context-to-element" : _ref$label, resolveDocument = _ref.resolveDocument, defaultToDocument = _ref.defaultToDocument;
|
|
var element = nodeArray(context)[0];
|
|
if (resolveDocument && element && element.nodeType === Node.DOCUMENT_NODE) {
|
|
element = element.documentElement;
|
|
}
|
|
if (!element && defaultToDocument) {
|
|
return document.documentElement;
|
|
}
|
|
if (!element) {
|
|
throw new TypeError(label + " requires valid options.context");
|
|
}
|
|
if (element.nodeType !== Node.ELEMENT_NODE && element.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
|
throw new TypeError(label + " requires options.context to be an Element");
|
|
}
|
|
return element;
|
|
}
|
|
function getShadowHost() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context;
|
|
var element = contextToElement({
|
|
label: "get/shadow-host",
|
|
context: context
|
|
});
|
|
// walk up to the root
|
|
var container = null;
|
|
while(element){
|
|
container = element;
|
|
element = element.parentNode;
|
|
}
|
|
// https://developer.mozilla.org/docs/Web/API/Node.nodeType
|
|
// NOTE: Firefox 34 does not expose ShadowRoot.host (but 37 does)
|
|
if (container.nodeType === container.DOCUMENT_FRAGMENT_NODE && container.host) {
|
|
// the root is attached to a fragment node that has a host
|
|
return container.host;
|
|
}
|
|
return null;
|
|
}
|
|
function getDocument(node) {
|
|
if (!node) {
|
|
return document;
|
|
}
|
|
if (node.nodeType === Node.DOCUMENT_NODE) {
|
|
return node;
|
|
}
|
|
return node.ownerDocument || document;
|
|
}
|
|
function isActiveElement(context) {
|
|
var element = contextToElement({
|
|
label: "is/active-element",
|
|
resolveDocument: true,
|
|
context: context
|
|
});
|
|
var _document = getDocument(element);
|
|
if (_document.activeElement === element) {
|
|
return true;
|
|
}
|
|
var shadowHost = getShadowHost({
|
|
context: element
|
|
});
|
|
if (shadowHost && shadowHost.shadowRoot.activeElement === element) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
// [elem, elem.parent, elem.parent.parent, …, html]
|
|
// will not contain the shadowRoot (DOCUMENT_FRAGMENT_NODE) and shadowHost
|
|
function getParents() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context;
|
|
var list = [];
|
|
var element = contextToElement({
|
|
label: "get/parents",
|
|
context: context
|
|
});
|
|
while(element){
|
|
list.push(element);
|
|
// IE does know support parentElement on SVGElement
|
|
element = element.parentNode;
|
|
if (element && element.nodeType !== Node.ELEMENT_NODE) {
|
|
element = null;
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
// Element.prototype.matches may be available at a different name
|
|
// https://developer.mozilla.org/en/docs/Web/API/Element/matches
|
|
var names = [
|
|
"matches",
|
|
"webkitMatchesSelector",
|
|
"mozMatchesSelector",
|
|
"msMatchesSelector"
|
|
];
|
|
var name = null;
|
|
function findMethodName(element) {
|
|
names.some(function(_name) {
|
|
if (!element[_name]) {
|
|
return false;
|
|
}
|
|
name = _name;
|
|
return true;
|
|
});
|
|
}
|
|
function elementMatches(element, selector) {
|
|
if (!name) {
|
|
findMethodName(element);
|
|
}
|
|
return element[name](selector);
|
|
}
|
|
// deep clone of original platform
|
|
var platform = JSON.parse(JSON.stringify(_platform.default));
|
|
// operating system
|
|
var os = platform.os.family || "";
|
|
var ANDROID = os === "Android";
|
|
var WINDOWS = os.slice(0, 7) === "Windows";
|
|
var OSX = os === "OS X";
|
|
var IOS = os === "iOS";
|
|
// layout
|
|
var BLINK = platform.layout === "Blink";
|
|
var GECKO = platform.layout === "Gecko";
|
|
var TRIDENT = platform.layout === "Trident";
|
|
var EDGE = platform.layout === "EdgeHTML";
|
|
var WEBKIT = platform.layout === "WebKit";
|
|
// browser version (not layout engine version!)
|
|
var version = parseFloat(platform.version);
|
|
var majorVersion = Math.floor(version);
|
|
platform.majorVersion = majorVersion;
|
|
platform.is = {
|
|
// operating system
|
|
ANDROID: ANDROID,
|
|
WINDOWS: WINDOWS,
|
|
OSX: OSX,
|
|
IOS: IOS,
|
|
// layout
|
|
BLINK: BLINK,
|
|
GECKO: GECKO,
|
|
TRIDENT: TRIDENT,
|
|
EDGE: EDGE,
|
|
WEBKIT: WEBKIT,
|
|
// INTERNET EXPLORERS
|
|
IE9: TRIDENT && majorVersion === 9,
|
|
IE10: TRIDENT && majorVersion === 10,
|
|
IE11: TRIDENT && majorVersion === 11
|
|
};
|
|
function before() {
|
|
var data = {
|
|
// remember what had focus to restore after test
|
|
activeElement: document.activeElement,
|
|
// remember scroll positions to restore after test
|
|
windowScrollTop: window.scrollTop,
|
|
windowScrollLeft: window.scrollLeft,
|
|
bodyScrollTop: document.body.scrollTop,
|
|
bodyScrollLeft: document.body.scrollLeft
|
|
};
|
|
// wrap tests in an element hidden from screen readers to prevent them
|
|
// from announcing focus, which can be quite irritating to the user
|
|
var iframe = document.createElement("iframe");
|
|
iframe.setAttribute("style", "position:absolute; position:fixed; top:0; left:-2px; width:1px; height:1px; overflow:hidden;");
|
|
iframe.setAttribute("aria-live", "off");
|
|
iframe.setAttribute("aria-busy", "true");
|
|
iframe.setAttribute("aria-hidden", "true");
|
|
document.body.appendChild(iframe);
|
|
var _window = iframe.contentWindow;
|
|
var _document = _window.document;
|
|
_document.open();
|
|
_document.close();
|
|
var wrapper = _document.createElement("div");
|
|
_document.body.appendChild(wrapper);
|
|
data.iframe = iframe;
|
|
data.wrapper = wrapper;
|
|
data.window = _window;
|
|
data.document = _document;
|
|
return data;
|
|
}
|
|
// options.element:
|
|
// {string} element name
|
|
// {function} callback(wrapper, document) to generate an element
|
|
// options.mutate: (optional)
|
|
// {function} callback(element, wrapper, document) to manipulate element prior to focus-test.
|
|
// Can return DOMElement to define focus target (default: element)
|
|
// options.validate: (optional)
|
|
// {function} callback(element, focusTarget, document) to manipulate test-result
|
|
function test(data, options) {
|
|
// make sure we operate on a clean slate
|
|
data.wrapper.innerHTML = "";
|
|
// create dummy element to test focusability of
|
|
var element = typeof options.element === "string" ? data.document.createElement(options.element) : options.element(data.wrapper, data.document);
|
|
// allow callback to further specify dummy element
|
|
// and optionally define element to focus
|
|
var focus = options.mutate && options.mutate(element, data.wrapper, data.document);
|
|
if (!focus && focus !== false) {
|
|
focus = element;
|
|
}
|
|
// element needs to be part of the DOM to be focusable
|
|
!element.parentNode && data.wrapper.appendChild(element);
|
|
// test if the element with invalid tabindex can be focused
|
|
focus && focus.focus && focus.focus();
|
|
// validate test's result
|
|
return options.validate ? options.validate(element, focus, data.document) : data.document.activeElement === focus;
|
|
}
|
|
function after(data) {
|
|
// restore focus to what it was before test and cleanup
|
|
if (data.activeElement === document.body) {
|
|
document.activeElement && document.activeElement.blur && document.activeElement.blur();
|
|
if (platform.is.IE10) {
|
|
// IE10 does not redirect focus to <body> when the activeElement is removed
|
|
document.body.focus();
|
|
}
|
|
} else {
|
|
data.activeElement && data.activeElement.focus && data.activeElement.focus();
|
|
}
|
|
document.body.removeChild(data.iframe);
|
|
// restore scroll position
|
|
window.scrollTop = data.windowScrollTop;
|
|
window.scrollLeft = data.windowScrollLeft;
|
|
document.body.scrollTop = data.bodyScrollTop;
|
|
document.body.scrollLeft = data.bodyScrollLeft;
|
|
}
|
|
function detectFocus(tests) {
|
|
var data = before();
|
|
var results = {};
|
|
Object.keys(tests).map(function(key) {
|
|
results[key] = test(data, tests[key]);
|
|
});
|
|
after(data);
|
|
return results;
|
|
}
|
|
// this file is overwritten by `npm run build:pre`
|
|
var version$1 = "1.4.1";
|
|
/*
|
|
Facility to cache test results in localStorage.
|
|
|
|
USAGE:
|
|
cache.get('key');
|
|
cache.set('key', 'value');
|
|
*/ function readLocalStorage(key) {
|
|
// allow reading from storage to retrieve previous support results
|
|
// even while the document does not have focus
|
|
var data = void 0;
|
|
try {
|
|
data = window.localStorage && window.localStorage.getItem(key);
|
|
data = data ? JSON.parse(data) : {};
|
|
} catch (e) {
|
|
data = {};
|
|
}
|
|
return data;
|
|
}
|
|
function writeLocalStorage(key, value) {
|
|
if (!document.hasFocus()) {
|
|
// if the document does not have focus when tests are executed, focus() may
|
|
// not be handled properly and events may not be dispatched immediately.
|
|
// This can happen when a document is reloaded while Developer Tools have focus.
|
|
try {
|
|
window.localStorage && window.localStorage.removeItem(key);
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
return;
|
|
}
|
|
try {
|
|
window.localStorage && window.localStorage.setItem(key, JSON.stringify(value));
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
}
|
|
var userAgent = typeof window !== "undefined" && window.navigator.userAgent || "";
|
|
var cacheKey = "ally-supports-cache";
|
|
var cache = readLocalStorage(cacheKey);
|
|
// update the cache if ally or the user agent changed (newer version, etc)
|
|
if (cache.userAgent !== userAgent || cache.version !== version$1) {
|
|
cache = {};
|
|
}
|
|
cache.userAgent = userAgent;
|
|
cache.version = version$1;
|
|
var cache$1 = {
|
|
get: function get() {
|
|
return cache;
|
|
},
|
|
set: function set(values) {
|
|
Object.keys(values).forEach(function(key) {
|
|
cache[key] = values[key];
|
|
});
|
|
cache.time = new Date().toISOString();
|
|
writeLocalStorage(cacheKey, cache);
|
|
}
|
|
};
|
|
function cssShadowPiercingDeepCombinator() {
|
|
var combinator = void 0;
|
|
// see https://dev.w3.org/csswg/css-scoping-1/#deep-combinator
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1117572
|
|
// https://code.google.com/p/chromium/issues/detail?id=446051
|
|
try {
|
|
document.querySelector("html >>> :first-child");
|
|
combinator = ">>>";
|
|
} catch (noArrowArrowArrow) {
|
|
try {
|
|
// old syntax supported at least up to Chrome 41
|
|
// https://code.google.com/p/chromium/issues/detail?id=446051
|
|
document.querySelector("html /deep/ :first-child");
|
|
combinator = "/deep/";
|
|
} catch (noDeep) {
|
|
combinator = "";
|
|
}
|
|
}
|
|
return combinator;
|
|
}
|
|
var gif = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-usemap
|
|
var focusAreaImgTabindex = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = '<map name="image-map-tabindex-test">' + '<area shape="rect" coords="63,19,144,45"></map>' + '<img usemap="#image-map-tabindex-test" tabindex="-1" alt="" src="' + gif + '">';
|
|
return element.querySelector("area");
|
|
}
|
|
};
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-usemap
|
|
var focusAreaTabindex = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = '<map name="image-map-tabindex-test">' + '<area href="#void" tabindex="-1" shape="rect" coords="63,19,144,45"></map>' + '<img usemap="#image-map-tabindex-test" alt="" src="' + gif + '">';
|
|
return false;
|
|
},
|
|
validate: function validate(element, focusTarget, _document) {
|
|
if (platform.is.GECKO) {
|
|
// fixes https://github.com/medialize/ally.js/issues/35
|
|
// Firefox loads the DataURI asynchronously, causing a false-negative
|
|
return true;
|
|
}
|
|
var focus = element.querySelector("area");
|
|
focus.focus();
|
|
return _document.activeElement === focus;
|
|
}
|
|
};
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-usemap
|
|
var focusAreaWithoutHref = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = '<map name="image-map-area-href-test">' + '<area shape="rect" coords="63,19,144,45"></map>' + '<img usemap="#image-map-area-href-test" alt="" src="' + gif + '">';
|
|
return element.querySelector("area");
|
|
},
|
|
validate: function validate(element, focusTarget, _document) {
|
|
if (platform.is.GECKO) {
|
|
// fixes https://github.com/medialize/ally.js/issues/35
|
|
// Firefox loads the DataURI asynchronously, causing a false-negative
|
|
return true;
|
|
}
|
|
return _document.activeElement === focusTarget;
|
|
}
|
|
};
|
|
var focusAudioWithoutControls = {
|
|
name: "can-focus-audio-without-controls",
|
|
element: "audio",
|
|
mutate: function mutate(element) {
|
|
try {
|
|
// invalid media file can trigger warning in console, data-uri to prevent HTTP request
|
|
element.setAttribute("src", gif);
|
|
} catch (e) {
|
|
// IE9 may throw "Error: Not implemented"
|
|
}
|
|
}
|
|
};
|
|
var invalidGif = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
|
|
// NOTE: https://github.com/medialize/ally.js/issues/35
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-usemap
|
|
var focusBrokenImageMap = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = '<map name="broken-image-map-test"><area href="#void" shape="rect" coords="63,19,144,45"></map>' + '<img usemap="#broken-image-map-test" alt="" src="' + invalidGif + '">';
|
|
return element.querySelector("area");
|
|
}
|
|
};
|
|
// Children of focusable elements with display:flex are focusable in IE10-11
|
|
var focusChildrenOfFocusableFlexbox = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("tabindex", "-1");
|
|
element.setAttribute("style", "display: -webkit-flex; display: -ms-flexbox; display: flex;");
|
|
element.innerHTML = '<span style="display: block;">hello</span>';
|
|
return element.querySelector("span");
|
|
}
|
|
};
|
|
// fieldset[tabindex=0][disabled] should not be focusable, but Blink and WebKit disagree
|
|
// @specification https://www.w3.org/TR/html5/disabled-elements.html#concept-element-disabled
|
|
// @browser-issue Chromium https://crbug.com/453847
|
|
// @browser-issue WebKit https://bugs.webkit.org/show_bug.cgi?id=141086
|
|
var focusFieldsetDisabled = {
|
|
element: "fieldset",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("tabindex", 0);
|
|
element.setAttribute("disabled", "disabled");
|
|
}
|
|
};
|
|
var focusFieldset = {
|
|
element: "fieldset",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = "<legend>legend</legend><p>content</p>";
|
|
}
|
|
};
|
|
// elements with display:flex are focusable in IE10-11
|
|
var focusFlexboxContainer = {
|
|
element: "span",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("style", "display: -webkit-flex; display: -ms-flexbox; display: flex;");
|
|
element.innerHTML = '<span style="display: block;">hello</span>';
|
|
}
|
|
};
|
|
// form[tabindex=0][disabled] should be focusable as the
|
|
// specification doesn't know the disabled attribute on the form element
|
|
// @specification https://www.w3.org/TR/html5/forms.html#the-form-element
|
|
var focusFormDisabled = {
|
|
element: "form",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("tabindex", 0);
|
|
element.setAttribute("disabled", "disabled");
|
|
}
|
|
};
|
|
// NOTE: https://github.com/medialize/ally.js/issues/35
|
|
// fixes https://github.com/medialize/ally.js/issues/20
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-ismap
|
|
var focusImgIsmap = {
|
|
element: "a",
|
|
mutate: function mutate(element) {
|
|
element.href = "#void";
|
|
element.innerHTML = '<img ismap src="' + gif + '" alt="">';
|
|
return element.querySelector("img");
|
|
}
|
|
};
|
|
// NOTE: https://github.com/medialize/ally.js/issues/35
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-usemap
|
|
var focusImgUsemapTabindex = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = '<map name="image-map-tabindex-test"><area href="#void" shape="rect" coords="63,19,144,45"></map>' + '<img usemap="#image-map-tabindex-test" tabindex="-1" alt="" ' + 'src="' + gif + '">';
|
|
return element.querySelector("img");
|
|
}
|
|
};
|
|
var focusInHiddenIframe = {
|
|
element: function element(wrapper, _document) {
|
|
var iframe = _document.createElement("iframe");
|
|
// iframe must be part of the DOM before accessing the contentWindow is possible
|
|
wrapper.appendChild(iframe);
|
|
// create the iframe's default document (<html><head></head><body></body></html>)
|
|
var iframeDocument = iframe.contentWindow.document;
|
|
iframeDocument.open();
|
|
iframeDocument.close();
|
|
return iframe;
|
|
},
|
|
mutate: function mutate(iframe) {
|
|
iframe.style.visibility = "hidden";
|
|
var iframeDocument = iframe.contentWindow.document;
|
|
var input = iframeDocument.createElement("input");
|
|
iframeDocument.body.appendChild(input);
|
|
return input;
|
|
},
|
|
validate: function validate(iframe) {
|
|
var iframeDocument = iframe.contentWindow.document;
|
|
var focus = iframeDocument.querySelector("input");
|
|
return iframeDocument.activeElement === focus;
|
|
}
|
|
};
|
|
var result = !platform.is.WEBKIT;
|
|
function focusInZeroDimensionObject() {
|
|
return result;
|
|
}
|
|
// Firefox allows *any* value and treats invalid values like tabindex="-1"
|
|
// @browser-issue Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
|
|
var focusInvalidTabindex = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("tabindex", "invalid-value");
|
|
}
|
|
};
|
|
var focusLabelTabindex = {
|
|
element: "label",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("tabindex", "-1");
|
|
},
|
|
validate: function validate(element, focusTarget, _document) {
|
|
// force layout in Chrome 49, otherwise the element won't be focusable
|
|
/* eslint-disable no-unused-vars */ var variableToPreventDeadCodeElimination = element.offsetHeight;
|
|
/* eslint-enable no-unused-vars */ element.focus();
|
|
return _document.activeElement === element;
|
|
}
|
|
};
|
|
var svg = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtb" + "G5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBpZD0ic3ZnIj48dGV4dCB4PSIxMCIgeT0iMjAiIGlkPSJ" + "zdmctbGluay10ZXh0Ij50ZXh0PC90ZXh0Pjwvc3ZnPg==";
|
|
// Note: IE10 on BrowserStack does not like this test
|
|
var focusObjectSvgHidden = {
|
|
element: "object",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("type", "image/svg+xml");
|
|
element.setAttribute("data", svg);
|
|
element.setAttribute("width", "200");
|
|
element.setAttribute("height", "50");
|
|
element.style.visibility = "hidden";
|
|
}
|
|
};
|
|
// Note: IE10 on BrowserStack does not like this test
|
|
var focusObjectSvg = {
|
|
name: "can-focus-object-svg",
|
|
element: "object",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("type", "image/svg+xml");
|
|
element.setAttribute("data", svg);
|
|
element.setAttribute("width", "200");
|
|
element.setAttribute("height", "50");
|
|
},
|
|
validate: function validate(element, focusTarget, _document) {
|
|
if (platform.is.GECKO) {
|
|
// Firefox seems to be handling the object creation asynchronously and thereby produces a false negative test result.
|
|
// Because we know Firefox is able to focus object elements referencing SVGs, we simply cheat by sniffing the user agent string
|
|
return true;
|
|
}
|
|
return _document.activeElement === element;
|
|
}
|
|
};
|
|
// Every Environment except IE9 considers SWF objects focusable
|
|
var result$1 = !platform.is.IE9;
|
|
function focusObjectSwf() {
|
|
return result$1;
|
|
}
|
|
var focusRedirectImgUsemap = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = '<map name="focus-redirect-img-usemap"><area href="#void" shape="rect" coords="63,19,144,45"></map>' + '<img usemap="#focus-redirect-img-usemap" alt="" ' + 'src="' + gif + '">';
|
|
// focus the <img>, not the <div>
|
|
return element.querySelector("img");
|
|
},
|
|
validate: function validate(element, focusTarget, _document) {
|
|
var target = element.querySelector("area");
|
|
return _document.activeElement === target;
|
|
}
|
|
};
|
|
// see https://jsbin.com/nenirisage/edit?html,js,console,output
|
|
var focusRedirectLegend = {
|
|
element: "fieldset",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = '<legend>legend</legend><input tabindex="-1"><input tabindex="0">';
|
|
// take care of focus in validate();
|
|
return false;
|
|
},
|
|
validate: function validate(element, focusTarget, _document) {
|
|
var focusable = element.querySelector('input[tabindex="-1"]');
|
|
var tabbable = element.querySelector('input[tabindex="0"]');
|
|
// Firefox requires this test to focus the <fieldset> first, while this is not necessary in
|
|
// https://jsbin.com/nenirisage/edit?html,js,console,output
|
|
element.focus();
|
|
element.querySelector("legend").focus();
|
|
return _document.activeElement === focusable && "focusable" || _document.activeElement === tabbable && "tabbable" || "";
|
|
}
|
|
};
|
|
// https://github.com/medialize/ally.js/issues/21
|
|
var focusScrollBody = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("style", "width: 100px; height: 50px; overflow: auto;");
|
|
element.innerHTML = '<div style="width: 500px; height: 40px;">scrollable content</div>';
|
|
return element.querySelector("div");
|
|
}
|
|
};
|
|
// https://github.com/medialize/ally.js/issues/21
|
|
var focusScrollContainerWithoutOverflow = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("style", "width: 100px; height: 50px;");
|
|
element.innerHTML = '<div style="width: 500px; height: 40px;">scrollable content</div>';
|
|
}
|
|
};
|
|
// https://github.com/medialize/ally.js/issues/21
|
|
var focusScrollContainer = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("style", "width: 100px; height: 50px; overflow: auto;");
|
|
element.innerHTML = '<div style="width: 500px; height: 40px;">scrollable content</div>';
|
|
}
|
|
};
|
|
var focusSummary = {
|
|
element: "details",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = "<summary>foo</summary><p>content</p>";
|
|
return element.firstElementChild;
|
|
}
|
|
};
|
|
function makeFocusableForeignObject() {
|
|
// Constructs <foreignObject width="30" height="30"><input type="text"/></foreignObject>
|
|
// without raising a Trusted Types violation
|
|
var foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
|
|
foreignObject.width.baseVal.value = 30;
|
|
foreignObject.height.baseVal.value = 30;
|
|
foreignObject.appendChild(document.createElement("input"));
|
|
foreignObject.lastChild.type = "text";
|
|
return foreignObject;
|
|
}
|
|
function focusSvgForeignObjectHack(element) {
|
|
// Edge13, Edge14: foreignObject focus hack
|
|
// https://jsbin.com/kunehinugi/edit?html,js,output
|
|
// https://jsbin.com/fajagi/3/edit?html,js,output
|
|
var isSvgElement = element.ownerSVGElement || element.nodeName.toLowerCase() === "svg";
|
|
if (!isSvgElement) {
|
|
return false;
|
|
}
|
|
// inject and focus an <input> element into the SVG element to receive focus
|
|
var foreignObject = makeFocusableForeignObject();
|
|
element.appendChild(foreignObject);
|
|
var input = foreignObject.querySelector("input");
|
|
input.focus();
|
|
// upon disabling the activeElement, IE and Edge
|
|
// will not shift focus to <body> like all the other
|
|
// browsers, but instead find the first focusable
|
|
// ancestor and shift focus to that
|
|
input.disabled = true;
|
|
// clean up
|
|
element.removeChild(foreignObject);
|
|
return true;
|
|
}
|
|
function generate(element) {
|
|
return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">' + element + "</svg>";
|
|
}
|
|
function focus(element) {
|
|
if (element.focus) {
|
|
return;
|
|
}
|
|
try {
|
|
HTMLElement.prototype.focus.call(element);
|
|
} catch (e) {
|
|
focusSvgForeignObjectHack(element);
|
|
}
|
|
}
|
|
function validate(element, focusTarget, _document) {
|
|
focus(focusTarget);
|
|
return _document.activeElement === focusTarget;
|
|
}
|
|
var focusSvgFocusableAttribute = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = generate('<text focusable="true">a</text>');
|
|
return element.querySelector("text");
|
|
},
|
|
validate: validate
|
|
};
|
|
var focusSvgTabindexAttribute = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = generate('<text tabindex="0">a</text>');
|
|
return element.querySelector("text");
|
|
},
|
|
validate: validate
|
|
};
|
|
var focusSvgNegativeTabindexAttribute = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = generate('<text tabindex="-1">a</text>');
|
|
return element.querySelector("text");
|
|
},
|
|
validate: validate
|
|
};
|
|
var focusSvgUseTabindex = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = generate([
|
|
'<g id="ally-test-target"><a xlink:href="#void"><text>link</text></a></g>',
|
|
'<use xlink:href="#ally-test-target" x="0" y="0" tabindex="-1" />'
|
|
].join(""));
|
|
return element.querySelector("use");
|
|
},
|
|
validate: validate
|
|
};
|
|
var focusSvgForeignobjectTabindex = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = generate('<foreignObject tabindex="-1"><input type="text" /></foreignObject>');
|
|
// Safari 8's quersSelector() can't identify foreignObject, but getElementyByTagName() can
|
|
return element.querySelector("foreignObject") || element.getElementsByTagName("foreignObject")[0];
|
|
},
|
|
validate: validate
|
|
};
|
|
// Firefox seems to be handling the SVG-document-in-iframe creation asynchronously
|
|
// and thereby produces a false negative test result. Thus the test is pointless
|
|
// and we resort to UA sniffing once again.
|
|
// see http://jsbin.com/vunadohoko/1/edit?js,console,output
|
|
var result$2 = Boolean(platform.is.GECKO && typeof SVGElement !== "undefined" && SVGElement.prototype.focus);
|
|
function focusSvgInIframe() {
|
|
return result$2;
|
|
}
|
|
var focusSvg = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.innerHTML = generate("");
|
|
return element.firstChild;
|
|
},
|
|
validate: validate
|
|
};
|
|
// Firefox allows *any* value and treats invalid values like tabindex="-1"
|
|
// @browser-issue Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
|
|
var focusTabindexTrailingCharacters = {
|
|
element: "div",
|
|
mutate: function mutate(element) {
|
|
element.setAttribute("tabindex", "3x");
|
|
}
|
|
};
|
|
var focusTable = {
|
|
element: "table",
|
|
mutate: function mutate(element, wrapper, _document) {
|
|
// IE9 has a problem replacing TBODY contents with innerHTML.
|
|
// https://stackoverflow.com/a/8097055/515124
|
|
// element.innerHTML = '<tr><td>cell</td></tr>';
|
|
var fragment = _document.createDocumentFragment();
|
|
fragment.innerHTML = "<tr><td>cell</td></tr>";
|
|
element.appendChild(fragment);
|
|
}
|
|
};
|
|
var focusVideoWithoutControls = {
|
|
element: "video",
|
|
mutate: function mutate(element) {
|
|
try {
|
|
// invalid media file can trigger warning in console, data-uri to prevent HTTP request
|
|
element.setAttribute("src", gif);
|
|
} catch (e) {
|
|
// IE9 may throw "Error: Not implemented"
|
|
}
|
|
}
|
|
};
|
|
// https://jsbin.com/vafaba/3/edit?html,js,console,output
|
|
var result$3 = platform.is.GECKO || platform.is.TRIDENT || platform.is.EDGE;
|
|
function tabsequenceAreaAtImgPosition() {
|
|
return result$3;
|
|
}
|
|
var testCallbacks = {
|
|
cssShadowPiercingDeepCombinator: cssShadowPiercingDeepCombinator,
|
|
focusInZeroDimensionObject: focusInZeroDimensionObject,
|
|
focusObjectSwf: focusObjectSwf,
|
|
focusSvgInIframe: focusSvgInIframe,
|
|
tabsequenceAreaAtImgPosition: tabsequenceAreaAtImgPosition
|
|
};
|
|
var testDescriptions = {
|
|
focusAreaImgTabindex: focusAreaImgTabindex,
|
|
focusAreaTabindex: focusAreaTabindex,
|
|
focusAreaWithoutHref: focusAreaWithoutHref,
|
|
focusAudioWithoutControls: focusAudioWithoutControls,
|
|
focusBrokenImageMap: focusBrokenImageMap,
|
|
focusChildrenOfFocusableFlexbox: focusChildrenOfFocusableFlexbox,
|
|
focusFieldsetDisabled: focusFieldsetDisabled,
|
|
focusFieldset: focusFieldset,
|
|
focusFlexboxContainer: focusFlexboxContainer,
|
|
focusFormDisabled: focusFormDisabled,
|
|
focusImgIsmap: focusImgIsmap,
|
|
focusImgUsemapTabindex: focusImgUsemapTabindex,
|
|
focusInHiddenIframe: focusInHiddenIframe,
|
|
focusInvalidTabindex: focusInvalidTabindex,
|
|
focusLabelTabindex: focusLabelTabindex,
|
|
focusObjectSvg: focusObjectSvg,
|
|
focusObjectSvgHidden: focusObjectSvgHidden,
|
|
focusRedirectImgUsemap: focusRedirectImgUsemap,
|
|
focusRedirectLegend: focusRedirectLegend,
|
|
focusScrollBody: focusScrollBody,
|
|
focusScrollContainerWithoutOverflow: focusScrollContainerWithoutOverflow,
|
|
focusScrollContainer: focusScrollContainer,
|
|
focusSummary: focusSummary,
|
|
focusSvgFocusableAttribute: focusSvgFocusableAttribute,
|
|
focusSvgTabindexAttribute: focusSvgTabindexAttribute,
|
|
focusSvgNegativeTabindexAttribute: focusSvgNegativeTabindexAttribute,
|
|
focusSvgUseTabindex: focusSvgUseTabindex,
|
|
focusSvgForeignobjectTabindex: focusSvgForeignobjectTabindex,
|
|
focusSvg: focusSvg,
|
|
focusTabindexTrailingCharacters: focusTabindexTrailingCharacters,
|
|
focusTable: focusTable,
|
|
focusVideoWithoutControls: focusVideoWithoutControls
|
|
};
|
|
function executeTests() {
|
|
var results = detectFocus(testDescriptions);
|
|
Object.keys(testCallbacks).forEach(function(key) {
|
|
results[key] = testCallbacks[key]();
|
|
});
|
|
return results;
|
|
}
|
|
var supportsCache = null;
|
|
function _supports() {
|
|
if (supportsCache) {
|
|
return supportsCache;
|
|
}
|
|
supportsCache = cache$1.get();
|
|
if (!supportsCache.time) {
|
|
cache$1.set(executeTests());
|
|
supportsCache = cache$1.get();
|
|
}
|
|
return supportsCache;
|
|
}
|
|
var supports = void 0;
|
|
// https://www.w3.org/TR/html5/infrastructure.html#rules-for-parsing-integers
|
|
// NOTE: all browsers agree to allow trailing spaces as well
|
|
var validIntegerPatternNoTrailing = /^\s*(-|\+)?[0-9]+\s*$/;
|
|
var validIntegerPatternWithTrailing = /^\s*(-|\+)?[0-9]+.*$/;
|
|
function isValidTabindex(context) {
|
|
if (!supports) {
|
|
supports = _supports();
|
|
}
|
|
var validIntegerPattern = supports.focusTabindexTrailingCharacters ? validIntegerPatternWithTrailing : validIntegerPatternNoTrailing;
|
|
var element = contextToElement({
|
|
label: "is/valid-tabindex",
|
|
resolveDocument: true,
|
|
context: context
|
|
});
|
|
// Edge 14 has a capitalization problem on SVG elements,
|
|
// see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9282058/
|
|
var hasTabindex = element.hasAttribute("tabindex");
|
|
var hasTabIndex = element.hasAttribute("tabIndex");
|
|
if (!hasTabindex && !hasTabIndex) {
|
|
return false;
|
|
}
|
|
// older Firefox and Internet Explorer don't support tabindex on SVG elements
|
|
var isSvgElement = element.ownerSVGElement || element.nodeName.toLowerCase() === "svg";
|
|
if (isSvgElement && !supports.focusSvgTabindexAttribute) {
|
|
return false;
|
|
}
|
|
// @browser-issue Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
|
|
if (supports.focusInvalidTabindex) {
|
|
return true;
|
|
}
|
|
// an element matches the tabindex selector even if its value is invalid
|
|
var tabindex = element.getAttribute(hasTabindex ? "tabindex" : "tabIndex");
|
|
// IE11 parses tabindex="" as the value "-32768"
|
|
// @browser-issue Trident https://connect.microsoft.com/IE/feedback/details/1072965
|
|
if (tabindex === "-32768") {
|
|
return false;
|
|
}
|
|
return Boolean(tabindex && validIntegerPattern.test(tabindex));
|
|
}
|
|
function tabindexValue(element) {
|
|
if (!isValidTabindex(element)) {
|
|
return null;
|
|
}
|
|
// Edge 14 has a capitalization problem on SVG elements,
|
|
// see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9282058/
|
|
var hasTabindex = element.hasAttribute("tabindex");
|
|
var attributeName = hasTabindex ? "tabindex" : "tabIndex";
|
|
// @browser-issue Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
|
|
var tabindex = parseInt(element.getAttribute(attributeName), 10);
|
|
return isNaN(tabindex) ? -1 : tabindex;
|
|
}
|
|
// this is a shared utility file for focus-relevant.js and tabbable.js
|
|
// separate testing of this file's functions is not necessary,
|
|
// as they're implicitly tested by way of the consumers
|
|
function isUserModifyWritable(style) {
|
|
// https://www.w3.org/TR/1999/WD-css3-userint-19990916#user-modify
|
|
// https://github.com/medialize/ally.js/issues/17
|
|
var userModify = style.webkitUserModify || "";
|
|
return Boolean(userModify && userModify.indexOf("write") !== -1);
|
|
}
|
|
function hasCssOverflowScroll(style) {
|
|
return [
|
|
style.getPropertyValue("overflow"),
|
|
style.getPropertyValue("overflow-x"),
|
|
style.getPropertyValue("overflow-y")
|
|
].some(function(overflow) {
|
|
return overflow === "auto" || overflow === "scroll";
|
|
});
|
|
}
|
|
function hasCssDisplayFlex(style) {
|
|
return style.display.indexOf("flex") > -1;
|
|
}
|
|
function isScrollableContainer(element, nodeName, parentNodeName, parentStyle) {
|
|
if (nodeName !== "div" && nodeName !== "span") {
|
|
// Internet Explorer advances scrollable containers and bodies to focusable
|
|
// only if the scrollable container is <div> or <span> - this does *not*
|
|
// happen for <section>, <article>, …
|
|
return false;
|
|
}
|
|
if (parentNodeName && parentNodeName !== "div" && parentNodeName !== "span" && !hasCssOverflowScroll(parentStyle)) {
|
|
return false;
|
|
}
|
|
return element.offsetHeight < element.scrollHeight || element.offsetWidth < element.scrollWidth;
|
|
}
|
|
var supports$1 = void 0;
|
|
function isFocusRelevantRules() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, _ref$except = _ref.except, except = _ref$except === undefined ? {
|
|
flexbox: false,
|
|
scrollable: false,
|
|
shadow: false
|
|
} : _ref$except;
|
|
if (!supports$1) {
|
|
supports$1 = _supports();
|
|
}
|
|
var element = contextToElement({
|
|
label: "is/focus-relevant",
|
|
resolveDocument: true,
|
|
context: context
|
|
});
|
|
if (!except.shadow && element.shadowRoot) {
|
|
// a ShadowDOM host receives focus when the focus moves to its content
|
|
return true;
|
|
}
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
if (nodeName === "input" && element.type === "hidden") {
|
|
// input[type="hidden"] supports.cannot be focused
|
|
return false;
|
|
}
|
|
if (nodeName === "input" || nodeName === "select" || nodeName === "button" || nodeName === "textarea") {
|
|
return true;
|
|
}
|
|
if (nodeName === "legend" && supports$1.focusRedirectLegend) {
|
|
// specifics filtered in is/focusable
|
|
return true;
|
|
}
|
|
if (nodeName === "label") {
|
|
// specifics filtered in is/focusable
|
|
return true;
|
|
}
|
|
if (nodeName === "area") {
|
|
// specifics filtered in is/focusable
|
|
return true;
|
|
}
|
|
if (nodeName === "a" && element.hasAttribute("href")) {
|
|
return true;
|
|
}
|
|
if (nodeName === "object" && element.hasAttribute("usemap")) {
|
|
// object[usemap] is not focusable in any browser
|
|
return false;
|
|
}
|
|
if (nodeName === "object") {
|
|
var svgType = element.getAttribute("type");
|
|
if (!supports$1.focusObjectSvg && svgType === "image/svg+xml") {
|
|
// object[type="image/svg+xml"] is not focusable in Internet Explorer
|
|
return false;
|
|
} else if (!supports$1.focusObjectSwf && svgType === "application/x-shockwave-flash") {
|
|
// object[type="application/x-shockwave-flash"] is not focusable in Internet Explorer 9
|
|
return false;
|
|
}
|
|
}
|
|
if (nodeName === "iframe" || nodeName === "object") {
|
|
// browsing context containers
|
|
return true;
|
|
}
|
|
if (nodeName === "embed" || nodeName === "keygen") {
|
|
// embed is considered focus-relevant but not focusable
|
|
// see https://github.com/medialize/ally.js/issues/82
|
|
return true;
|
|
}
|
|
if (element.hasAttribute("contenteditable")) {
|
|
// also see CSS property user-modify below
|
|
return true;
|
|
}
|
|
if (nodeName === "audio" && (supports$1.focusAudioWithoutControls || element.hasAttribute("controls"))) {
|
|
return true;
|
|
}
|
|
if (nodeName === "video" && (supports$1.focusVideoWithoutControls || element.hasAttribute("controls"))) {
|
|
return true;
|
|
}
|
|
if (supports$1.focusSummary && nodeName === "summary") {
|
|
return true;
|
|
}
|
|
var validTabindex = isValidTabindex(element);
|
|
if (nodeName === "img" && element.hasAttribute("usemap")) {
|
|
// Gecko, Trident and Edge do not allow an image with an image map and tabindex to be focused,
|
|
// it appears the tabindex is overruled so focus is still forwarded to the <map>
|
|
return validTabindex && supports$1.focusImgUsemapTabindex || supports$1.focusRedirectImgUsemap;
|
|
}
|
|
if (supports$1.focusTable && (nodeName === "table" || nodeName === "td")) {
|
|
// IE10-11 supports.can focus <table> and <td>
|
|
return true;
|
|
}
|
|
if (supports$1.focusFieldset && nodeName === "fieldset") {
|
|
// IE10-11 supports.can focus <fieldset>
|
|
return true;
|
|
}
|
|
var isSvgElement = nodeName === "svg";
|
|
var isSvgContent = element.ownerSVGElement;
|
|
var focusableAttribute = element.getAttribute("focusable");
|
|
var tabindex = tabindexValue(element);
|
|
if (nodeName === "use" && tabindex !== null && !supports$1.focusSvgUseTabindex) {
|
|
// <use> cannot be made focusable by adding a tabindex attribute anywhere but Blink and WebKit
|
|
return false;
|
|
}
|
|
if (nodeName === "foreignobject") {
|
|
// <use> can only be made focusable in Blink and WebKit
|
|
return tabindex !== null && supports$1.focusSvgForeignobjectTabindex;
|
|
}
|
|
if (elementMatches(element, "svg a") && element.hasAttribute("xlink:href")) {
|
|
return true;
|
|
}
|
|
if ((isSvgElement || isSvgContent) && element.focus && !supports$1.focusSvgNegativeTabindexAttribute && tabindex < 0) {
|
|
// Firefox 51 and 52 treat any natively tabbable SVG element with
|
|
// tabindex="-1" as tabbable and everything else as inert
|
|
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1302340
|
|
return false;
|
|
}
|
|
if (isSvgElement) {
|
|
return validTabindex || supports$1.focusSvg || supports$1.focusSvgInIframe || // Internet Explorer understands the focusable attribute introduced in SVG Tiny 1.2
|
|
Boolean(supports$1.focusSvgFocusableAttribute && focusableAttribute && focusableAttribute === "true");
|
|
}
|
|
if (isSvgContent) {
|
|
if (supports$1.focusSvgTabindexAttribute && validTabindex) {
|
|
return true;
|
|
}
|
|
if (supports$1.focusSvgFocusableAttribute) {
|
|
// Internet Explorer understands the focusable attribute introduced in SVG Tiny 1.2
|
|
return focusableAttribute === "true";
|
|
}
|
|
}
|
|
// https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute
|
|
if (validTabindex) {
|
|
return true;
|
|
}
|
|
var style = window.getComputedStyle(element, null);
|
|
if (isUserModifyWritable(style)) {
|
|
return true;
|
|
}
|
|
if (supports$1.focusImgIsmap && nodeName === "img" && element.hasAttribute("ismap")) {
|
|
// IE10-11 considers the <img> in <a href><img ismap> focusable
|
|
// https://github.com/medialize/ally.js/issues/20
|
|
var hasLinkParent = getParents({
|
|
context: element
|
|
}).some(function(parent) {
|
|
return parent.nodeName.toLowerCase() === "a" && parent.hasAttribute("href");
|
|
});
|
|
if (hasLinkParent) {
|
|
return true;
|
|
}
|
|
}
|
|
// https://github.com/medialize/ally.js/issues/21
|
|
if (!except.scrollable && supports$1.focusScrollContainer) {
|
|
if (supports$1.focusScrollContainerWithoutOverflow) {
|
|
// Internet Explorer does will consider the scrollable area focusable
|
|
// if the element is a <div> or a <span> and it is in fact scrollable,
|
|
// regardless of the CSS overflow property
|
|
if (isScrollableContainer(element, nodeName)) {
|
|
return true;
|
|
}
|
|
} else if (hasCssOverflowScroll(style)) {
|
|
// Firefox requires proper overflow setting, IE does not necessarily
|
|
// https://developer.mozilla.org/docs/Web/CSS/overflow
|
|
return true;
|
|
}
|
|
}
|
|
if (!except.flexbox && supports$1.focusFlexboxContainer && hasCssDisplayFlex(style)) {
|
|
// elements with display:flex are focusable in IE10-11
|
|
return true;
|
|
}
|
|
var parent = element.parentElement;
|
|
if (!except.scrollable && parent) {
|
|
var parentNodeName = parent.nodeName.toLowerCase();
|
|
var parentStyle = window.getComputedStyle(parent, null);
|
|
if (supports$1.focusScrollBody && isScrollableContainer(parent, nodeName, parentNodeName, parentStyle)) {
|
|
// scrollable bodies are focusable Internet Explorer
|
|
// https://github.com/medialize/ally.js/issues/21
|
|
return true;
|
|
}
|
|
// Children of focusable elements with display:flex are focusable in IE10-11
|
|
if (supports$1.focusChildrenOfFocusableFlexbox) {
|
|
if (hasCssDisplayFlex(parentStyle)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// NOTE: elements marked as inert are not focusable,
|
|
// but that property is not exposed to the DOM
|
|
// https://www.w3.org/TR/html5/editing.html#inert
|
|
return false;
|
|
}
|
|
// bind exceptions to an iterator callback
|
|
isFocusRelevantRules.except = function() {
|
|
var except = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var isFocusRelevant = function isFocusRelevant(context) {
|
|
return isFocusRelevantRules({
|
|
context: context,
|
|
except: except
|
|
});
|
|
};
|
|
isFocusRelevant.rules = isFocusRelevantRules;
|
|
return isFocusRelevant;
|
|
};
|
|
// provide isFocusRelevant(context) as default iterator callback
|
|
var isFocusRelevant = isFocusRelevantRules.except({});
|
|
function findIndex(array, callback) {
|
|
// attempt to use native or polyfilled Array#findIndex first
|
|
if (array.findIndex) {
|
|
return array.findIndex(callback);
|
|
}
|
|
var length = array.length;
|
|
// shortcut if the array is empty
|
|
if (length === 0) {
|
|
return -1;
|
|
}
|
|
// otherwise loop over array
|
|
for(var i = 0; i < length; i++){
|
|
if (callback(array[i], i, array)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
function getContentDocument(node) {
|
|
try {
|
|
// works on <object> and <iframe>
|
|
return node.contentDocument || // works on <object> and <iframe>
|
|
node.contentWindow && node.contentWindow.document || // works on <object> and <iframe> that contain SVG
|
|
node.getSVGDocument && node.getSVGDocument() || null;
|
|
} catch (e) {
|
|
// SecurityError: Failed to read the 'contentDocument' property from 'HTMLObjectElement'
|
|
// also IE may throw member not found exception e.g. on <object type="image/png">
|
|
return null;
|
|
}
|
|
}
|
|
function getWindow(node) {
|
|
var _document = getDocument(node);
|
|
return _document.defaultView || window;
|
|
}
|
|
var shadowPrefix = void 0;
|
|
function selectInShadows(selector) {
|
|
if (typeof shadowPrefix !== "string") {
|
|
var operator = cssShadowPiercingDeepCombinator();
|
|
if (operator) {
|
|
shadowPrefix = ", html " + operator + " ";
|
|
}
|
|
}
|
|
if (!shadowPrefix) {
|
|
return selector;
|
|
}
|
|
return selector + shadowPrefix + selector.replace(/\s*,\s*/g, ",").split(",").join(shadowPrefix);
|
|
}
|
|
var selector = void 0;
|
|
function findDocumentHostElement(_window) {
|
|
if (!selector) {
|
|
selector = selectInShadows("object, iframe");
|
|
}
|
|
if (_window._frameElement !== undefined) {
|
|
return _window._frameElement;
|
|
}
|
|
_window._frameElement = null;
|
|
var potentialHosts = _window.parent.document.querySelectorAll(selector);
|
|
[].some.call(potentialHosts, function(element) {
|
|
var _document = getContentDocument(element);
|
|
if (_document !== _window.document) {
|
|
return false;
|
|
}
|
|
_window._frameElement = element;
|
|
return true;
|
|
});
|
|
return _window._frameElement;
|
|
}
|
|
function getFrameElement(element) {
|
|
var _window = getWindow(element);
|
|
if (!_window.parent || _window.parent === _window) {
|
|
// if there is no parent browsing context,
|
|
// we're not going to get a frameElement either way
|
|
return null;
|
|
}
|
|
try {
|
|
// see https://developer.mozilla.org/docs/Web/API/Window/frameElement
|
|
// does not work within <embed> anywhere, and not within in <object> in IE
|
|
return _window.frameElement || findDocumentHostElement(_window);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
// https://www.w3.org/TR/html5/rendering.html#being-rendered
|
|
// <area> is not rendered, but we *consider* it visible to simplfiy this function's usage
|
|
var notRenderedElementsPattern = /^(area)$/;
|
|
function computedStyle(element, property) {
|
|
return window.getComputedStyle(element, null).getPropertyValue(property);
|
|
}
|
|
function notDisplayed(_path) {
|
|
return _path.some(function(element) {
|
|
// display:none is not visible (optimized away at layout)
|
|
return computedStyle(element, "display") === "none";
|
|
});
|
|
}
|
|
function notVisible(_path) {
|
|
// https://github.com/jquery/jquery-ui/blob/master/ui/core.js#L109-L114
|
|
// NOTE: a nested element can reverse visibility:hidden|collapse by explicitly setting visibility:visible
|
|
// NOTE: visibility can be ["", "visible", "hidden", "collapse"]
|
|
var hidden = findIndex(_path, function(element) {
|
|
var visibility = computedStyle(element, "visibility");
|
|
return visibility === "hidden" || visibility === "collapse";
|
|
});
|
|
if (hidden === -1) {
|
|
// there is no hidden element
|
|
return false;
|
|
}
|
|
var visible = findIndex(_path, function(element) {
|
|
return computedStyle(element, "visibility") === "visible";
|
|
});
|
|
if (visible === -1) {
|
|
// there is no visible element (but a hidden element)
|
|
return true;
|
|
}
|
|
if (hidden < visible) {
|
|
// there is a hidden element and it's closer than the first visible element
|
|
return true;
|
|
}
|
|
// there may be a hidden element, but the closest element is visible
|
|
return false;
|
|
}
|
|
function collapsedParent(_path) {
|
|
var offset = 1;
|
|
if (_path[0].nodeName.toLowerCase() === "summary") {
|
|
offset = 2;
|
|
}
|
|
return _path.slice(offset).some(function(element) {
|
|
// "content children" of a closed details element are not visible
|
|
return element.nodeName.toLowerCase() === "details" && element.open === false;
|
|
});
|
|
}
|
|
function isVisibleRules() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, _ref$except = _ref.except, except = _ref$except === undefined ? {
|
|
notRendered: false,
|
|
cssDisplay: false,
|
|
cssVisibility: false,
|
|
detailsElement: false,
|
|
browsingContext: false
|
|
} : _ref$except;
|
|
var element = contextToElement({
|
|
label: "is/visible",
|
|
resolveDocument: true,
|
|
context: context
|
|
});
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
if (!except.notRendered && notRenderedElementsPattern.test(nodeName)) {
|
|
return true;
|
|
}
|
|
var _path = getParents({
|
|
context: element
|
|
});
|
|
// in Internet Explorer <audio> has a default display: none, where others have display: inline
|
|
// but IE allows focusing <audio style="display:none">, but not <div display:none><audio>
|
|
// this is irrelevant to other browsers, as the controls attribute is required to make <audio> focusable
|
|
var isAudioWithoutControls = nodeName === "audio" && !element.hasAttribute("controls");
|
|
if (!except.cssDisplay && notDisplayed(isAudioWithoutControls ? _path.slice(1) : _path)) {
|
|
return false;
|
|
}
|
|
if (!except.cssVisibility && notVisible(_path)) {
|
|
return false;
|
|
}
|
|
if (!except.detailsElement && collapsedParent(_path)) {
|
|
return false;
|
|
}
|
|
if (!except.browsingContext) {
|
|
// elements within a browsing context are affected by the
|
|
// browsing context host element's visibility and tabindex
|
|
var frameElement = getFrameElement(element);
|
|
var _isVisible = isVisibleRules.except(except);
|
|
if (frameElement && !_isVisible(frameElement)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
// bind exceptions to an iterator callback
|
|
isVisibleRules.except = function() {
|
|
var except = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var isVisible = function isVisible(context) {
|
|
return isVisibleRules({
|
|
context: context,
|
|
except: except
|
|
});
|
|
};
|
|
isVisible.rules = isVisibleRules;
|
|
return isVisible;
|
|
};
|
|
// provide isVisible(context) as default iterator callback
|
|
var isVisible = isVisibleRules.except({});
|
|
function getMapByName(name, _document) {
|
|
// apparently getElementsByName() also considers id attribute in IE & opera
|
|
// https://developer.mozilla.org/docs/Web/API/Document/getElementsByName
|
|
var map = _document.querySelector('map[name="' + (0, _cssescape.default)(name) + '"]');
|
|
return map || null;
|
|
}
|
|
function getImageOfArea(element) {
|
|
var map = element.parentElement;
|
|
if (!map.name || map.nodeName.toLowerCase() !== "map") {
|
|
return null;
|
|
}
|
|
// NOTE: image maps can also be applied to <object> with image content,
|
|
// but no browser supports this at the moment
|
|
// HTML5 specifies HTMLMapElement.images to be an HTMLCollection of all
|
|
// <img> and <object> referencing the <map> element, but no browser implements this
|
|
// https://www.w3.org/TR/html5/embedded-content-0.html#the-map-element
|
|
// https://developer.mozilla.org/docs/Web/API/HTMLMapElement
|
|
// the image must be valid and loaded for the map to take effect
|
|
var _document = getDocument(element);
|
|
return _document.querySelector('img[usemap="#' + (0, _cssescape.default)(map.name) + '"]') || null;
|
|
}
|
|
var supports$2 = void 0;
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/map
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-usemap
|
|
// https://github.com/jquery/jquery-ui/blob/master/ui/core.js#L88-L107
|
|
function isValidArea(context) {
|
|
if (!supports$2) {
|
|
supports$2 = _supports();
|
|
}
|
|
var element = contextToElement({
|
|
label: "is/valid-area",
|
|
context: context
|
|
});
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
if (nodeName !== "area") {
|
|
return false;
|
|
}
|
|
var hasTabindex = element.hasAttribute("tabindex");
|
|
if (!supports$2.focusAreaTabindex && hasTabindex) {
|
|
// Blink and WebKit do not consider <area tabindex="-1" href="#void"> focusable
|
|
return false;
|
|
}
|
|
var img = getImageOfArea(element);
|
|
if (!img || !isVisible(img)) {
|
|
return false;
|
|
}
|
|
// Firefox only allows fully loaded images to reference image maps
|
|
// https://stereochro.me/ideas/detecting-broken-images-js
|
|
if (!supports$2.focusBrokenImageMap && (!img.complete || !img.naturalHeight || img.offsetWidth <= 0 || img.offsetHeight <= 0)) {
|
|
return false;
|
|
}
|
|
// Firefox supports.can focus area elements even if they don't have an href attribute
|
|
if (!supports$2.focusAreaWithoutHref && !element.href) {
|
|
// Internet explorer supports.can focus area elements without href if either
|
|
// the area element or the image element has a tabindex attribute
|
|
return supports$2.focusAreaTabindex && hasTabindex || supports$2.focusAreaImgTabindex && img.hasAttribute("tabindex");
|
|
}
|
|
// https://developer.mozilla.org/docs/Web/HTML/Element/img#attr-usemap
|
|
var childOfInteractive = getParents({
|
|
context: img
|
|
}).slice(1).some(function(_element) {
|
|
var name = _element.nodeName.toLowerCase();
|
|
return name === "button" || name === "a";
|
|
});
|
|
if (childOfInteractive) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
var supports$3 = void 0;
|
|
// https://www.w3.org/TR/html5/disabled-elements.html#concept-element-disabled
|
|
var disabledElementsPattern = void 0;
|
|
var disabledElements = {
|
|
input: true,
|
|
select: true,
|
|
textarea: true,
|
|
button: true,
|
|
fieldset: true,
|
|
form: true
|
|
};
|
|
function isNativeDisabledSupported(context) {
|
|
if (!supports$3) {
|
|
supports$3 = _supports();
|
|
if (supports$3.focusFieldsetDisabled) {
|
|
delete disabledElements.fieldset;
|
|
}
|
|
if (supports$3.focusFormDisabled) {
|
|
delete disabledElements.form;
|
|
}
|
|
disabledElementsPattern = new RegExp("^(" + Object.keys(disabledElements).join("|") + ")$");
|
|
}
|
|
var element = contextToElement({
|
|
label: "is/native-disabled-supported",
|
|
context: context
|
|
});
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
return Boolean(disabledElementsPattern.test(nodeName));
|
|
}
|
|
var supports$4 = void 0;
|
|
function isDisabledFieldset(element) {
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
return nodeName === "fieldset" && element.disabled;
|
|
}
|
|
function isDisabledForm(element) {
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
return nodeName === "form" && element.disabled;
|
|
}
|
|
function isDisabled(context) {
|
|
if (!supports$4) {
|
|
supports$4 = _supports();
|
|
}
|
|
var element = contextToElement({
|
|
label: "is/disabled",
|
|
context: context
|
|
});
|
|
if (element.hasAttribute("data-ally-disabled")) {
|
|
// treat ally's element/disabled like the DOM native element.disabled
|
|
return true;
|
|
}
|
|
if (!isNativeDisabledSupported(element)) {
|
|
// non-form elements do not support the disabled attribute
|
|
return false;
|
|
}
|
|
if (element.disabled) {
|
|
// the element itself is disabled
|
|
return true;
|
|
}
|
|
var parents = getParents({
|
|
context: element
|
|
});
|
|
if (parents.some(isDisabledFieldset)) {
|
|
// a parental <fieldset> is disabld and inherits the state onto this element
|
|
return true;
|
|
}
|
|
if (!supports$4.focusFormDisabled && parents.some(isDisabledForm)) {
|
|
// a parental <form> is disabld and inherits the state onto this element
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function isOnlyTabbableRules() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, _ref$except = _ref.except, except = _ref$except === undefined ? {
|
|
onlyFocusableBrowsingContext: false,
|
|
visible: false
|
|
} : _ref$except;
|
|
var element = contextToElement({
|
|
label: "is/only-tabbable",
|
|
resolveDocument: true,
|
|
context: context
|
|
});
|
|
if (!except.visible && !isVisible(element)) {
|
|
return false;
|
|
}
|
|
if (!except.onlyFocusableBrowsingContext && (platform.is.GECKO || platform.is.TRIDENT || platform.is.EDGE)) {
|
|
var frameElement = getFrameElement(element);
|
|
if (frameElement) {
|
|
if (tabindexValue(frameElement) < 0) {
|
|
// iframe[tabindex="-1"] and object[tabindex="-1"] inherit the
|
|
// tabbable demotion onto elements of their browsing contexts
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
var tabindex = tabindexValue(element);
|
|
if (nodeName === "label" && platform.is.GECKO) {
|
|
// Firefox cannot focus, but tab to: label[tabindex=0]
|
|
return tabindex !== null && tabindex >= 0;
|
|
}
|
|
// SVG Elements were keyboard focusable but not script focusable before Firefox 51.
|
|
// Firefox 51 added the focus management DOM API (.focus and .blur) to SVGElement,
|
|
// see https://bugzilla.mozilla.org/show_bug.cgi?id=778654
|
|
if (platform.is.GECKO && element.ownerSVGElement && !element.focus) {
|
|
if (nodeName === "a" && element.hasAttribute("xlink:href")) {
|
|
// any focusable child of <svg> cannot be focused, but tabbed to
|
|
if (platform.is.GECKO) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// bind exceptions to an iterator callback
|
|
isOnlyTabbableRules.except = function() {
|
|
var except = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var isOnlyTabbable = function isOnlyTabbable(context) {
|
|
return isOnlyTabbableRules({
|
|
context: context,
|
|
except: except
|
|
});
|
|
};
|
|
isOnlyTabbable.rules = isOnlyTabbableRules;
|
|
return isOnlyTabbable;
|
|
};
|
|
// provide isOnlyTabbable(context) as default iterator callback
|
|
var isOnlyTabbable = isOnlyTabbableRules.except({});
|
|
var supports$5 = void 0;
|
|
function isOnlyFocusRelevant(element) {
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
if (nodeName === "embed" || nodeName === "keygen") {
|
|
// embed is considered focus-relevant but not focusable
|
|
// see https://github.com/medialize/ally.js/issues/82
|
|
return true;
|
|
}
|
|
var _tabindex = tabindexValue(element);
|
|
if (element.shadowRoot && _tabindex === null) {
|
|
// ShadowDOM host elements *may* receive focus
|
|
// even though they are not considered focuable
|
|
return true;
|
|
}
|
|
if (nodeName === "label") {
|
|
// <label tabindex="0"> is only tabbable in Firefox, not script-focusable
|
|
// there's no way to make an element focusable other than by adding a tabindex,
|
|
// and focus behavior of the label element seems hard-wired to ignore tabindex
|
|
// in some browsers (like Gecko, Blink and WebKit)
|
|
return !supports$5.focusLabelTabindex || _tabindex === null;
|
|
}
|
|
if (nodeName === "legend") {
|
|
return _tabindex === null;
|
|
}
|
|
if (supports$5.focusSvgFocusableAttribute && (element.ownerSVGElement || nodeName === "svg")) {
|
|
// Internet Explorer understands the focusable attribute introduced in SVG Tiny 1.2
|
|
var focusableAttribute = element.getAttribute("focusable");
|
|
return focusableAttribute && focusableAttribute === "false";
|
|
}
|
|
if (nodeName === "img" && element.hasAttribute("usemap")) {
|
|
// Gecko, Trident and Edge do not allow an image with an image map and tabindex to be focused,
|
|
// it appears the tabindex is overruled so focus is still forwarded to the <map>
|
|
return _tabindex === null || !supports$5.focusImgUsemapTabindex;
|
|
}
|
|
if (nodeName === "area") {
|
|
// all <area>s are considered relevant,
|
|
// but only the valid <area>s are focusable
|
|
return !isValidArea(element);
|
|
}
|
|
return false;
|
|
}
|
|
function isFocusableRules() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, _ref$except = _ref.except, except = _ref$except === undefined ? {
|
|
disabled: false,
|
|
visible: false,
|
|
onlyTabbable: false
|
|
} : _ref$except;
|
|
if (!supports$5) {
|
|
supports$5 = _supports();
|
|
}
|
|
var _isOnlyTabbable = isOnlyTabbable.rules.except({
|
|
onlyFocusableBrowsingContext: true,
|
|
visible: except.visible
|
|
});
|
|
var element = contextToElement({
|
|
label: "is/focusable",
|
|
resolveDocument: true,
|
|
context: context
|
|
});
|
|
var focusRelevant = isFocusRelevant.rules({
|
|
context: element,
|
|
except: except
|
|
});
|
|
if (!focusRelevant || isOnlyFocusRelevant(element)) {
|
|
return false;
|
|
}
|
|
if (!except.disabled && isDisabled(element)) {
|
|
return false;
|
|
}
|
|
if (!except.onlyTabbable && _isOnlyTabbable(element)) {
|
|
// some elements may be keyboard focusable, but not script focusable
|
|
return false;
|
|
}
|
|
// elements that are not rendered, cannot be focused
|
|
if (!except.visible) {
|
|
var visibilityOptions = {
|
|
context: element,
|
|
except: {}
|
|
};
|
|
if (supports$5.focusInHiddenIframe) {
|
|
// WebKit and Blink can focus content in hidden <iframe> and <object>
|
|
visibilityOptions.except.browsingContext = true;
|
|
}
|
|
if (supports$5.focusObjectSvgHidden) {
|
|
// Blink allows focusing the object element, even if it has visibility: hidden;
|
|
// @browser-issue Blink https://code.google.com/p/chromium/issues/detail?id=586191
|
|
var _nodeName2 = element.nodeName.toLowerCase();
|
|
if (_nodeName2 === "object") {
|
|
visibilityOptions.except.cssVisibility = true;
|
|
}
|
|
}
|
|
if (!isVisible.rules(visibilityOptions)) {
|
|
return false;
|
|
}
|
|
}
|
|
var frameElement = getFrameElement(element);
|
|
if (frameElement) {
|
|
var _nodeName = frameElement.nodeName.toLowerCase();
|
|
if (_nodeName === "object" && !supports$5.focusInZeroDimensionObject) {
|
|
if (!frameElement.offsetWidth || !frameElement.offsetHeight) {
|
|
// WebKit can not focus content in <object> if it doesn't have dimensions
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
if (nodeName === "svg" && supports$5.focusSvgInIframe && !frameElement && element.getAttribute("tabindex") === null) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
// bind exceptions to an iterator callback
|
|
isFocusableRules.except = function() {
|
|
var except = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var isFocusable = function isFocusable(context) {
|
|
return isFocusableRules({
|
|
context: context,
|
|
except: except
|
|
});
|
|
};
|
|
isFocusable.rules = isFocusableRules;
|
|
return isFocusable;
|
|
};
|
|
// provide isFocusRelevant(context) as default iterator callback
|
|
var isFocusable = isFocusableRules.except({});
|
|
function createFilter(condition) {
|
|
// see https://developer.mozilla.org/docs/Web/API/NodeFilter
|
|
var filter = function filter(node) {
|
|
if (node.shadowRoot) {
|
|
// return ShadowRoot elements regardless of them being focusable,
|
|
// so they can be walked recursively later
|
|
return NodeFilter.FILTER_ACCEPT;
|
|
}
|
|
if (condition(node)) {
|
|
// finds elements that could have been found by document.querySelectorAll()
|
|
return NodeFilter.FILTER_ACCEPT;
|
|
}
|
|
return NodeFilter.FILTER_SKIP;
|
|
};
|
|
// IE requires a function, Browsers require {acceptNode: function}
|
|
// see http://www.bennadel.com/blog/2607-finding-html-comment-nodes-in-the-dom-using-treewalker.htm
|
|
filter.acceptNode = filter;
|
|
return filter;
|
|
}
|
|
var PossiblyFocusableFilter = createFilter(isFocusRelevant);
|
|
function queryFocusableStrict() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, includeContext = _ref.includeContext, includeOnlyTabbable = _ref.includeOnlyTabbable, strategy = _ref.strategy;
|
|
if (!context) {
|
|
context = document.documentElement;
|
|
}
|
|
var _isFocusable = isFocusable.rules.except({
|
|
onlyTabbable: includeOnlyTabbable
|
|
});
|
|
var _document = getDocument(context);
|
|
// see https://developer.mozilla.org/docs/Web/API/Document/createTreeWalker
|
|
var walker = _document.createTreeWalker(// root element to start search in
|
|
context, // element type filter
|
|
NodeFilter.SHOW_ELEMENT, // custom NodeFilter filter
|
|
strategy === "all" ? PossiblyFocusableFilter : createFilter(_isFocusable), // deprecated, but IE requires it
|
|
false);
|
|
var list = [];
|
|
while(walker.nextNode()){
|
|
if (walker.currentNode.shadowRoot) {
|
|
if (_isFocusable(walker.currentNode)) {
|
|
list.push(walker.currentNode);
|
|
}
|
|
list = list.concat(queryFocusableStrict({
|
|
context: walker.currentNode.shadowRoot,
|
|
includeOnlyTabbable: includeOnlyTabbable,
|
|
strategy: strategy
|
|
}));
|
|
} else {
|
|
list.push(walker.currentNode);
|
|
}
|
|
}
|
|
// add context if requested and focusable
|
|
if (includeContext) {
|
|
if (strategy === "all") {
|
|
if (isFocusRelevant(context)) {
|
|
list.unshift(context);
|
|
}
|
|
} else if (_isFocusable(context)) {
|
|
list.unshift(context);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
// NOTE: this selector MUST *never* be used directly,
|
|
var supports$6 = void 0;
|
|
var selector$1 = void 0;
|
|
function selector$2() {
|
|
if (!supports$6) {
|
|
supports$6 = _supports();
|
|
}
|
|
if (typeof selector$1 === "string") {
|
|
return selector$1;
|
|
}
|
|
// https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute
|
|
selector$1 = "" + // IE11 supports.can focus <table> and <td>
|
|
(supports$6.focusTable ? "table, td," : "") + // IE11 supports.can focus <fieldset>
|
|
(supports$6.focusFieldset ? "fieldset," : "") + // Namespace problems of [xlink:href] explained in https://stackoverflow.com/a/23047888/515124
|
|
// svg a[*|href] does not match in IE9, but since we're filtering
|
|
// through is/focusable we can include all <a> from SVG
|
|
"svg a," + // may behave as 'svg, svg *,' in chrome as *every* svg element with a focus event listener is focusable
|
|
// navigational elements
|
|
"a[href]," + // validity determined by is/valid-area.js
|
|
"area[href]," + // validity determined by is/disabled.js
|
|
"input, select, textarea, button," + // browsing context containers
|
|
"iframe, object, embed," + // interactive content
|
|
"keygen," + (supports$6.focusAudioWithoutControls ? "audio," : "audio[controls],") + (supports$6.focusVideoWithoutControls ? "video," : "video[controls],") + (supports$6.focusSummary ? "summary," : "") + // validity determined by is/valid-tabindex.js
|
|
"[tabindex]," + // editing hosts
|
|
"[contenteditable]";
|
|
// where ShadowDOM is supported, we also want the shadowed focusable elements (via ">>>" or "/deep/")
|
|
selector$1 = selectInShadows(selector$1);
|
|
return selector$1;
|
|
}
|
|
function queryFocusableQuick() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, includeContext = _ref.includeContext, includeOnlyTabbable = _ref.includeOnlyTabbable;
|
|
var _selector = selector$2();
|
|
var elements = context.querySelectorAll(_selector);
|
|
// the selector potentially matches more than really is focusable
|
|
var _isFocusable = isFocusable.rules.except({
|
|
onlyTabbable: includeOnlyTabbable
|
|
});
|
|
var result = [].filter.call(elements, _isFocusable);
|
|
// add context if requested and focusable
|
|
if (includeContext && _isFocusable(context)) {
|
|
result.unshift(context);
|
|
}
|
|
return result;
|
|
}
|
|
function queryFocusable() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, includeContext = _ref.includeContext, includeOnlyTabbable = _ref.includeOnlyTabbable, _ref$strategy = _ref.strategy, strategy = _ref$strategy === undefined ? "quick" : _ref$strategy;
|
|
var element = contextToElement({
|
|
label: "query/focusable",
|
|
resolveDocument: true,
|
|
defaultToDocument: true,
|
|
context: context
|
|
});
|
|
var options = {
|
|
context: element,
|
|
includeContext: includeContext,
|
|
includeOnlyTabbable: includeOnlyTabbable,
|
|
strategy: strategy
|
|
};
|
|
if (strategy === "quick") {
|
|
return queryFocusableQuick(options);
|
|
} else if (strategy === "strict" || strategy === "all") {
|
|
return queryFocusableStrict(options);
|
|
}
|
|
throw new TypeError('query/focusable requires option.strategy to be one of ["quick", "strict", "all"]');
|
|
}
|
|
var supports$7 = void 0;
|
|
// Internet Explorer 11 considers fieldset, table, td focusable, but not tabbable
|
|
// Internet Explorer 11 considers body to have [tabindex=0], but does not allow tabbing to it
|
|
var focusableElementsPattern = /^(fieldset|table|td|body)$/;
|
|
function isTabbableRules() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, _ref$except = _ref.except, except = _ref$except === undefined ? {
|
|
flexbox: false,
|
|
scrollable: false,
|
|
shadow: false,
|
|
visible: false,
|
|
onlyTabbable: false
|
|
} : _ref$except;
|
|
if (!supports$7) {
|
|
supports$7 = _supports();
|
|
}
|
|
var element = contextToElement({
|
|
label: "is/tabbable",
|
|
resolveDocument: true,
|
|
context: context
|
|
});
|
|
if (platform.is.BLINK && platform.is.ANDROID && platform.majorVersion > 42) {
|
|
// External keyboard support worked fine in CHrome 42, but stopped working in Chrome 45.
|
|
// The on-screen keyboard does not provide a way to focus the next input element (like iOS does).
|
|
// That leaves us with no option to advance focus by keyboard, ergo nothing is tabbable (keyboard focusable).
|
|
return false;
|
|
}
|
|
var frameElement = getFrameElement(element);
|
|
if (frameElement) {
|
|
if (platform.is.WEBKIT && platform.is.IOS) {
|
|
// iOS only does not consider anything from another browsing context keyboard focusable
|
|
return false;
|
|
}
|
|
// iframe[tabindex="-1"] and object[tabindex="-1"] inherit the
|
|
// tabbable demotion onto elements of their browsing contexts
|
|
if (tabindexValue(frameElement) < 0) {
|
|
return false;
|
|
}
|
|
if (!except.visible && (platform.is.BLINK || platform.is.WEBKIT) && !isVisible(frameElement)) {
|
|
// Blink and WebKit consider elements in hidden browsing contexts focusable, but not tabbable
|
|
return false;
|
|
}
|
|
// Webkit and Blink don't consider anything in <object> tabbable
|
|
// Blink fixed that fixed in Chrome 54, Opera 41
|
|
var frameNodeName = frameElement.nodeName.toLowerCase();
|
|
if (frameNodeName === "object") {
|
|
var isFixedBlink = platform.name === "Chrome" && platform.majorVersion >= 54 || platform.name === "Opera" && platform.majorVersion >= 41;
|
|
if (platform.is.WEBKIT || platform.is.BLINK && !isFixedBlink) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
var _tabindex = tabindexValue(element);
|
|
var tabindex = _tabindex === null ? null : _tabindex >= 0;
|
|
if (platform.is.EDGE && platform.majorVersion >= 14 && frameElement && element.ownerSVGElement && _tabindex < 0) {
|
|
// Edge 14+ considers <a xlink:href="…" tabindex="-1"> keyboard focusable
|
|
// if the element is in a nested browsing context
|
|
return true;
|
|
}
|
|
var hasTabbableTabindexOrNone = tabindex !== false;
|
|
var hasTabbableTabindex = _tabindex !== null && _tabindex >= 0;
|
|
// NOTE: Firefox 31 considers [contenteditable] to have [tabindex=-1], but allows tabbing to it
|
|
// fixed in Firefox 40 the latest - https://bugzilla.mozilla.org/show_bug.cgi?id=1185657
|
|
if (element.hasAttribute("contenteditable")) {
|
|
// tabbing can still be disabled by explicitly providing [tabindex="-1"]
|
|
return hasTabbableTabindexOrNone;
|
|
}
|
|
if (focusableElementsPattern.test(nodeName) && tabindex !== true) {
|
|
return false;
|
|
}
|
|
if (platform.is.WEBKIT && platform.is.IOS) {
|
|
// iOS only considers a hand full of elements tabbable (keyboard focusable)
|
|
// this holds true even with external keyboards
|
|
var potentiallyTabbable = nodeName === "input" && element.type === "text" || element.type === "password" || nodeName === "select" || nodeName === "textarea" || element.hasAttribute("contenteditable");
|
|
if (!potentiallyTabbable) {
|
|
var style = window.getComputedStyle(element, null);
|
|
potentiallyTabbable = isUserModifyWritable(style);
|
|
}
|
|
if (!potentiallyTabbable) {
|
|
return false;
|
|
}
|
|
}
|
|
if (nodeName === "use" && _tabindex !== null) {
|
|
if (platform.is.BLINK || platform.is.WEBKIT && platform.majorVersion === 9) {
|
|
// In Chrome and Safari 9 the <use> element is keyboard focusable even for tabindex="-1"
|
|
return true;
|
|
}
|
|
}
|
|
if (elementMatches(element, "svg a") && element.hasAttribute("xlink:href")) {
|
|
if (hasTabbableTabindexOrNone) {
|
|
// in Trident and Gecko SVGElement does not handle the tabIndex property properly
|
|
return true;
|
|
}
|
|
if (element.focus && !supports$7.focusSvgNegativeTabindexAttribute) {
|
|
// Firefox 51 and 52 treat any natively tabbable SVG element with
|
|
// tabindex="-1" as tabbable and everything else as inert
|
|
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1302340
|
|
return true;
|
|
}
|
|
}
|
|
if (nodeName === "svg" && supports$7.focusSvgInIframe && hasTabbableTabindexOrNone) {
|
|
return true;
|
|
}
|
|
if (platform.is.TRIDENT || platform.is.EDGE) {
|
|
if (nodeName === "svg") {
|
|
if (supports$7.focusSvg) {
|
|
// older Internet Explorers consider <svg> keyboard focusable
|
|
// unless they have focsable="false", but then they wouldn't
|
|
// be focusable and thus not even reach this filter
|
|
return true;
|
|
}
|
|
// elements that have [focusable] are automatically keyboard focusable regardless of the attribute's value
|
|
return element.hasAttribute("focusable") || hasTabbableTabindex;
|
|
}
|
|
if (element.ownerSVGElement) {
|
|
if (supports$7.focusSvgTabindexAttribute && hasTabbableTabindex) {
|
|
return true;
|
|
}
|
|
// elements that have [focusable] are automatically keyboard focusable regardless of the attribute's value
|
|
return element.hasAttribute("focusable");
|
|
}
|
|
}
|
|
if (element.tabIndex === undefined) {
|
|
return Boolean(except.onlyTabbable);
|
|
}
|
|
if (nodeName === "audio") {
|
|
if (!element.hasAttribute("controls")) {
|
|
// In Internet Explorer the <audio> element is focusable, but not tabbable, and tabIndex property is wrong
|
|
return false;
|
|
} else if (platform.is.BLINK) {
|
|
// In Chrome <audio controls tabindex="-1"> remains keyboard focusable
|
|
return true;
|
|
}
|
|
}
|
|
if (nodeName === "video") {
|
|
if (!element.hasAttribute("controls")) {
|
|
if (platform.is.TRIDENT || platform.is.EDGE) {
|
|
// In Internet Explorer and Edge the <video> element is focusable, but not tabbable, and tabIndex property is wrong
|
|
return false;
|
|
}
|
|
} else if (platform.is.BLINK || platform.is.GECKO) {
|
|
// In Chrome and Firefox <video controls tabindex="-1"> remains keyboard focusable
|
|
return true;
|
|
}
|
|
}
|
|
if (nodeName === "object") {
|
|
if (platform.is.BLINK || platform.is.WEBKIT) {
|
|
// In all Blink and WebKit based browsers <embed> and <object> are never keyboard focusable, even with tabindex="0" set
|
|
return false;
|
|
}
|
|
}
|
|
if (nodeName === "iframe") {
|
|
// In Internet Explorer all iframes are only focusable
|
|
// In WebKit, Blink and Gecko iframes may be tabbable depending on content.
|
|
// Since we can't reliably investigate iframe documents because of the
|
|
// SameOriginPolicy, we're declaring everything only focusable.
|
|
return false;
|
|
}
|
|
if (!except.scrollable && platform.is.GECKO) {
|
|
// Firefox considers scrollable containers keyboard focusable,
|
|
// even though their tabIndex property is -1
|
|
var _style = window.getComputedStyle(element, null);
|
|
if (hasCssOverflowScroll(_style)) {
|
|
return hasTabbableTabindexOrNone;
|
|
}
|
|
}
|
|
if (platform.is.TRIDENT || platform.is.EDGE) {
|
|
// IE and Edge degrade <area> to script focusable, if the image
|
|
// using the <map> has been given tabindex="-1"
|
|
if (nodeName === "area") {
|
|
var img = getImageOfArea(element);
|
|
if (img && tabindexValue(img) < 0) {
|
|
return false;
|
|
}
|
|
}
|
|
var _style2 = window.getComputedStyle(element, null);
|
|
if (isUserModifyWritable(_style2)) {
|
|
// prevent being swallowed by the overzealous isScrollableContainer() below
|
|
return element.tabIndex >= 0;
|
|
}
|
|
if (!except.flexbox && hasCssDisplayFlex(_style2)) {
|
|
if (_tabindex !== null) {
|
|
return hasTabbableTabindex;
|
|
}
|
|
return isFocusRelevantWithoutFlexbox(element) && isTabbableWithoutFlexbox(element);
|
|
}
|
|
// IE considers scrollable containers script focusable only,
|
|
// even though their tabIndex property is 0
|
|
if (isScrollableContainer(element, nodeName)) {
|
|
return false;
|
|
}
|
|
var parent = element.parentElement;
|
|
if (parent) {
|
|
var parentNodeName = parent.nodeName.toLowerCase();
|
|
var parentStyle = window.getComputedStyle(parent, null);
|
|
// IE considers scrollable bodies script focusable only,
|
|
if (isScrollableContainer(parent, nodeName, parentNodeName, parentStyle)) {
|
|
return false;
|
|
}
|
|
// Children of focusable elements with display:flex are focusable in IE10-11,
|
|
// even though their tabIndex property suggests otherwise
|
|
if (hasCssDisplayFlex(parentStyle)) {
|
|
// value of tabindex takes precedence
|
|
return hasTabbableTabindex;
|
|
}
|
|
}
|
|
}
|
|
// https://www.w3.org/WAI/PF/aria-practices/#focus_tabindex
|
|
return element.tabIndex >= 0;
|
|
}
|
|
// bind exceptions to an iterator callback
|
|
isTabbableRules.except = function() {
|
|
var except = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var isTabbable = function isTabbable(context) {
|
|
return isTabbableRules({
|
|
context: context,
|
|
except: except
|
|
});
|
|
};
|
|
isTabbable.rules = isTabbableRules;
|
|
return isTabbable;
|
|
};
|
|
var isFocusRelevantWithoutFlexbox = isFocusRelevant.rules.except({
|
|
flexbox: true
|
|
});
|
|
var isTabbableWithoutFlexbox = isTabbableRules.except({
|
|
flexbox: true
|
|
});
|
|
// provide isTabbable(context) as default iterator callback
|
|
var isTabbable = isTabbableRules.except({});
|
|
function queryTabbable() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, includeContext = _ref.includeContext, includeOnlyTabbable = _ref.includeOnlyTabbable, strategy = _ref.strategy;
|
|
var _isTabbable = isTabbable.rules.except({
|
|
onlyTabbable: includeOnlyTabbable
|
|
});
|
|
return queryFocusable({
|
|
context: context,
|
|
includeContext: includeContext,
|
|
includeOnlyTabbable: includeOnlyTabbable,
|
|
strategy: strategy
|
|
}).filter(_isTabbable);
|
|
}
|
|
// sorts a list of elements according to their order in the DOM
|
|
function compareDomPosition(a, b) {
|
|
return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
}
|
|
function sortDomOrder(elements) {
|
|
return elements.sort(compareDomPosition);
|
|
}
|
|
function getFirstSuccessorOffset(list, target) {
|
|
// find the first element that comes AFTER the target element
|
|
return findIndex(list, function(element) {
|
|
return target.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_FOLLOWING;
|
|
});
|
|
}
|
|
function findInsertionOffsets(list, elements, resolveElement) {
|
|
// instead of mutating the elements list directly, remember position and map
|
|
// to inject later, when we can do this more efficiently
|
|
var insertions = [];
|
|
elements.forEach(function(element) {
|
|
var replace = true;
|
|
var offset = list.indexOf(element);
|
|
if (offset === -1) {
|
|
// element is not in target list
|
|
offset = getFirstSuccessorOffset(list, element);
|
|
replace = false;
|
|
}
|
|
if (offset === -1) {
|
|
// there is no successor in the tabsequence,
|
|
// meaning the image must be the last element
|
|
offset = list.length;
|
|
}
|
|
// allow the consumer to replace the injected element
|
|
var injections = nodeArray(resolveElement ? resolveElement(element) : element);
|
|
if (!injections.length) {
|
|
// we can't inject zero elements
|
|
return;
|
|
}
|
|
insertions.push({
|
|
offset: offset,
|
|
replace: replace,
|
|
elements: injections
|
|
});
|
|
});
|
|
return insertions;
|
|
}
|
|
function insertElementsAtOffsets(list, insertions) {
|
|
// remember the number of elements we have already injected
|
|
// so we account for the caused index offset
|
|
var inserted = 0;
|
|
// make sure that we insert the elements in sequence,
|
|
// otherwise the offset compensation won't work
|
|
insertions.sort(function(a, b) {
|
|
return a.offset - b.offset;
|
|
});
|
|
insertions.forEach(function(insertion) {
|
|
// array.splice has an annoying function signature :(
|
|
var remove = insertion.replace ? 1 : 0;
|
|
var args = [
|
|
insertion.offset + inserted,
|
|
remove
|
|
].concat(insertion.elements);
|
|
list.splice.apply(list, args);
|
|
inserted += insertion.elements.length - remove;
|
|
});
|
|
}
|
|
function mergeInDomOrder() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, list = _ref.list, elements = _ref.elements, resolveElement = _ref.resolveElement;
|
|
// operate on a copy so we don't mutate the original array
|
|
var _list = list.slice(0);
|
|
// make sure the elements we're injecting are provided in DOM order
|
|
var _elements = nodeArray(elements).slice(0);
|
|
sortDomOrder(_elements);
|
|
// find the offsets within the target array (list) at which to inject
|
|
// each individual element (from elements)
|
|
var insertions = findInsertionOffsets(_list, _elements, resolveElement);
|
|
// actually inject the elements into the target array at the identified positions
|
|
insertElementsAtOffsets(_list, insertions);
|
|
return _list;
|
|
}
|
|
var _createClass = function() {
|
|
function defineProperties(target, props) {
|
|
for(var i = 0; i < props.length; i++){
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
return function(Constructor, protoProps, staticProps) {
|
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
};
|
|
}();
|
|
function _classCallCheck(instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
}
|
|
var Maps = function() {
|
|
function Maps(context) {
|
|
_classCallCheck(this, Maps);
|
|
this._document = getDocument(context);
|
|
this.maps = {};
|
|
}
|
|
_createClass(Maps, [
|
|
{
|
|
key: "getAreasFor",
|
|
value: function getAreasFor(name) {
|
|
if (!this.maps[name]) {
|
|
// the map is not defined within the context, so we
|
|
// have to go find it elsewhere in the document
|
|
this.addMapByName(name);
|
|
}
|
|
return this.maps[name];
|
|
}
|
|
},
|
|
{
|
|
key: "addMapByName",
|
|
value: function addMapByName(name) {
|
|
var map = getMapByName(name, this._document);
|
|
if (!map) {
|
|
// if there is no map, the img[usemap] wasn't doing anything anyway
|
|
return;
|
|
}
|
|
this.maps[map.name] = queryTabbable({
|
|
context: map
|
|
});
|
|
}
|
|
},
|
|
{
|
|
key: "extractAreasFromList",
|
|
value: function extractAreasFromList(elements) {
|
|
// remove all <area> elements from the elements list,
|
|
// but put them the map for later retrieval
|
|
return elements.filter(function(element) {
|
|
var nodeName = element.nodeName.toLowerCase();
|
|
if (nodeName !== "area") {
|
|
return true;
|
|
}
|
|
var map = element.parentNode;
|
|
if (!this.maps[map.name]) {
|
|
this.maps[map.name] = [];
|
|
}
|
|
this.maps[map.name].push(element);
|
|
return false;
|
|
}, this);
|
|
}
|
|
}
|
|
]);
|
|
return Maps;
|
|
}();
|
|
function sortArea(elements, context) {
|
|
// images - unless they are focusable themselves, likely not
|
|
// part of the elements list, so we'll have to find them and
|
|
// sort them into the elements list manually
|
|
var usemaps = context.querySelectorAll("img[usemap]");
|
|
var maps = new Maps(context);
|
|
// remove all <area> elements from the elements list,
|
|
// but put them the map for later retrieval
|
|
var _elements = maps.extractAreasFromList(elements);
|
|
if (!usemaps.length) {
|
|
// the context does not contain any <area>s so no need
|
|
// to replace anything, just remove any maps
|
|
return _elements;
|
|
}
|
|
return mergeInDomOrder({
|
|
list: _elements,
|
|
elements: usemaps,
|
|
resolveElement: function resolveElement(image) {
|
|
var name = image.getAttribute("usemap").slice(1);
|
|
return maps.getAreasFor(name);
|
|
}
|
|
});
|
|
}
|
|
var _createClass$1 = function() {
|
|
function defineProperties(target, props) {
|
|
for(var i = 0; i < props.length; i++){
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
return function(Constructor, protoProps, staticProps) {
|
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
};
|
|
}();
|
|
function _classCallCheck$1(instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
}
|
|
var Shadows = function() {
|
|
function Shadows(context, sortElements) {
|
|
_classCallCheck$1(this, Shadows);
|
|
// document context we're working with
|
|
this.context = context;
|
|
// callback that sorts an array of elements
|
|
this.sortElements = sortElements;
|
|
// reference to create unique IDs for each ShadowHost
|
|
this.hostCounter = 1;
|
|
// reference map for child-ShadowHosts of a ShadowHost
|
|
this.inHost = {};
|
|
// reference map for child-ShadowHost of the document
|
|
this.inDocument = [];
|
|
// reference map for ShadowHosts
|
|
this.hosts = {};
|
|
// reference map for tabbable elements of a ShadowHost
|
|
this.elements = {};
|
|
}
|
|
// remember which hosts we have to sort within later
|
|
_createClass$1(Shadows, [
|
|
{
|
|
key: "_registerHost",
|
|
value: function _registerHost(host) {
|
|
if (host._sortingId) {
|
|
return;
|
|
}
|
|
// make the ShadowHost identifiable (see cleanup() for undo)
|
|
host._sortingId = "shadow-" + this.hostCounter++;
|
|
this.hosts[host._sortingId] = host;
|
|
// hosts may contain other hosts
|
|
var parentHost = getShadowHost({
|
|
context: host
|
|
});
|
|
if (parentHost) {
|
|
this._registerHost(parentHost);
|
|
this._registerHostParent(host, parentHost);
|
|
} else {
|
|
this.inDocument.push(host);
|
|
}
|
|
}
|
|
},
|
|
{
|
|
key: "_registerHostParent",
|
|
value: function _registerHostParent(host, parent) {
|
|
if (!this.inHost[parent._sortingId]) {
|
|
this.inHost[parent._sortingId] = [];
|
|
}
|
|
this.inHost[parent._sortingId].push(host);
|
|
}
|
|
},
|
|
{
|
|
key: "_registerElement",
|
|
value: function _registerElement(element, host) {
|
|
if (!this.elements[host._sortingId]) {
|
|
this.elements[host._sortingId] = [];
|
|
}
|
|
this.elements[host._sortingId].push(element);
|
|
}
|
|
},
|
|
{
|
|
key: "extractElements",
|
|
value: function extractElements(elements) {
|
|
return elements.filter(function(element) {
|
|
var host = getShadowHost({
|
|
context: element
|
|
});
|
|
if (!host) {
|
|
return true;
|
|
}
|
|
this._registerHost(host);
|
|
this._registerElement(element, host);
|
|
return false;
|
|
}, this);
|
|
}
|
|
},
|
|
{
|
|
key: "sort",
|
|
value: function sort(elements) {
|
|
var _elements = this._injectHosts(elements);
|
|
_elements = this._replaceHosts(_elements);
|
|
this._cleanup();
|
|
return _elements;
|
|
}
|
|
},
|
|
{
|
|
key: "_injectHosts",
|
|
value: function _injectHosts(elements) {
|
|
Object.keys(this.hosts).forEach(function(_sortingId) {
|
|
var _list = this.elements[_sortingId];
|
|
var _elements = this.inHost[_sortingId];
|
|
var _context = this.hosts[_sortingId].shadowRoot;
|
|
this.elements[_sortingId] = this._merge(_list, _elements, _context);
|
|
}, this);
|
|
return this._merge(elements, this.inDocument, this.context);
|
|
}
|
|
},
|
|
{
|
|
key: "_merge",
|
|
value: function _merge(list, elements, context) {
|
|
var merged = mergeInDomOrder({
|
|
list: list,
|
|
elements: elements
|
|
});
|
|
return this.sortElements(merged, context);
|
|
}
|
|
},
|
|
{
|
|
key: "_replaceHosts",
|
|
value: function _replaceHosts(elements) {
|
|
return mergeInDomOrder({
|
|
list: elements,
|
|
elements: this.inDocument,
|
|
resolveElement: this._resolveHostElement.bind(this)
|
|
});
|
|
}
|
|
},
|
|
{
|
|
key: "_resolveHostElement",
|
|
value: function _resolveHostElement(host) {
|
|
var merged = mergeInDomOrder({
|
|
list: this.elements[host._sortingId],
|
|
elements: this.inHost[host._sortingId],
|
|
resolveElement: this._resolveHostElement.bind(this)
|
|
});
|
|
var _tabindex = tabindexValue(host);
|
|
if (_tabindex !== null && _tabindex > -1) {
|
|
return [
|
|
host
|
|
].concat(merged);
|
|
}
|
|
return merged;
|
|
}
|
|
},
|
|
{
|
|
key: "_cleanup",
|
|
value: function _cleanup() {
|
|
// remove those identifers we put on the ShadowHost to avoid using Map()
|
|
Object.keys(this.hosts).forEach(function(key) {
|
|
delete this.hosts[key]._sortingId;
|
|
}, this);
|
|
}
|
|
}
|
|
]);
|
|
return Shadows;
|
|
}();
|
|
function sortShadowed(elements, context, sortElements) {
|
|
var shadows = new Shadows(context, sortElements);
|
|
var _elements = shadows.extractElements(elements);
|
|
if (_elements.length === elements.length) {
|
|
// no shadowed content found, no need to continue
|
|
return sortElements(elements);
|
|
}
|
|
return shadows.sort(_elements);
|
|
}
|
|
function sortTabindex(elements) {
|
|
// https://developer.mozilla.org/docs/Web/API/HTMLElement.tabIndex
|
|
// elements with tabIndex "0" (including tabbableElements without tabIndex) should be navigated in the order they appear.
|
|
// elements with a positive tabIndex:
|
|
// Elements that have identical tabIndexes should be navigated in the order they appear.
|
|
// Navigation proceeds from the lowest tabIndex to the highest tabIndex.
|
|
// NOTE: sort implementation may be unstable and thus mess up DOM order,
|
|
// that's why we build a map that's being sorted instead. If we were able to rely
|
|
// on a stable sorting algorithm, sortTabindex() could be as simple as
|
|
// elements.sort(function(a, b) { return a.tabIndex - b.tabIndex; });
|
|
// at this time Chrome does not use a stable sorting algorithm
|
|
// see http://blog.rodneyrehm.de/archives/14-Sorting-Were-Doing-It-Wrong.html#stability
|
|
// NOTE: compareDocumentPosition seemed like more overhead than just sorting this with buckets
|
|
// https://developer.mozilla.org/docs/Web/API/Node.compareDocumentPosition
|
|
var map = {};
|
|
var indexes = [];
|
|
var normal = elements.filter(function(element) {
|
|
// in Trident and Gecko SVGElement does not know about the tabIndex property
|
|
var tabIndex = element.tabIndex;
|
|
if (tabIndex === undefined) {
|
|
tabIndex = tabindexValue(element);
|
|
}
|
|
// extract elements that don't need sorting
|
|
if (tabIndex <= 0 || tabIndex === null || tabIndex === undefined) {
|
|
return true;
|
|
}
|
|
if (!map[tabIndex]) {
|
|
// create sortable bucket for dom-order-preservation of elements with the same tabIndex
|
|
map[tabIndex] = [];
|
|
// maintain a list of unique tabIndexes
|
|
indexes.push(tabIndex);
|
|
}
|
|
// sort element into the proper bucket
|
|
map[tabIndex].push(element);
|
|
// element moved to sorting map, so not "normal" anymore
|
|
return false;
|
|
});
|
|
// sort the tabindex ascending,
|
|
// then resolve them to their appropriate buckets,
|
|
// then flatten the array of arrays to an array
|
|
var _elements = indexes.sort().map(function(tabIndex) {
|
|
return map[tabIndex];
|
|
}).reduceRight(function(previous, current) {
|
|
return current.concat(previous);
|
|
}, normal);
|
|
return _elements;
|
|
}
|
|
var supports$8 = void 0;
|
|
function moveContextToBeginning(elements, context) {
|
|
var pos = elements.indexOf(context);
|
|
if (pos > 0) {
|
|
var tmp = elements.splice(pos, 1);
|
|
return tmp.concat(elements);
|
|
}
|
|
return elements;
|
|
}
|
|
function sortElements(elements, _context) {
|
|
if (supports$8.tabsequenceAreaAtImgPosition) {
|
|
// Some browsers sort <area> in DOM order, some place the <area>s
|
|
// where the <img> referecing them would've been in DOM order.
|
|
// https://github.com/medialize/ally.js/issues/5
|
|
elements = sortArea(elements, _context);
|
|
}
|
|
elements = sortTabindex(elements);
|
|
return elements;
|
|
}
|
|
function queryTabsequence() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, context = _ref.context, includeContext = _ref.includeContext, includeOnlyTabbable = _ref.includeOnlyTabbable, strategy = _ref.strategy;
|
|
if (!supports$8) {
|
|
supports$8 = _supports();
|
|
}
|
|
var _context = nodeArray(context)[0] || document.documentElement;
|
|
var elements = queryTabbable({
|
|
context: _context,
|
|
includeContext: includeContext,
|
|
includeOnlyTabbable: includeOnlyTabbable,
|
|
strategy: strategy
|
|
});
|
|
if (document.body.createShadowRoot && platform.is.BLINK) {
|
|
// sort tabindex localized to shadow dom
|
|
// see https://github.com/medialize/ally.js/issues/6
|
|
elements = sortShadowed(elements, _context, sortElements);
|
|
} else {
|
|
elements = sortElements(elements, _context);
|
|
}
|
|
if (includeContext) {
|
|
// if we include the context itself, it has to be the first
|
|
// element of the sequence
|
|
elements = moveContextToBeginning(elements, _context);
|
|
}
|
|
return elements;
|
|
}
|
|
// codes mostly cloned from https://github.com/keithamus/jwerty/blob/master/jwerty.js
|
|
// deliberately not exposing characters like <,.-#* because they vary *wildly*
|
|
// across keyboard layouts and may cause various problems
|
|
// (e.g. "*" is "Shift +" on a German Mac keyboard)
|
|
// (e.g. "@" is "Alt L" on a German Mac keyboard)
|
|
var keycode = {
|
|
// Element Focus
|
|
tab: 9,
|
|
// Navigation
|
|
left: 37,
|
|
up: 38,
|
|
right: 39,
|
|
down: 40,
|
|
pageUp: 33,
|
|
"page-up": 33,
|
|
pageDown: 34,
|
|
"page-down": 34,
|
|
end: 35,
|
|
home: 36,
|
|
// Action
|
|
enter: 13,
|
|
escape: 27,
|
|
space: 32,
|
|
// Modifier
|
|
shift: 16,
|
|
capsLock: 20,
|
|
"caps-lock": 20,
|
|
ctrl: 17,
|
|
alt: 18,
|
|
meta: 91,
|
|
// in firefox: 224
|
|
// on mac (chrome): meta-left=91, meta-right=93
|
|
// on win (IE11): meta-left=91, meta-right=92
|
|
pause: 19,
|
|
// Content Manipulation
|
|
insert: 45,
|
|
delete: 46,
|
|
backspace: 8,
|
|
// the same logical key may be identified through different keyCodes
|
|
_alias: {
|
|
91: [
|
|
92,
|
|
93,
|
|
224
|
|
]
|
|
}
|
|
};
|
|
// Function keys (112 - 137)
|
|
// NOTE: not every keyboard knows F13+
|
|
for(var n = 1; n < 26; n++){
|
|
keycode["f" + n] = n + 111;
|
|
}
|
|
// Number keys (48-57, numpad 96-105)
|
|
// NOTE: not every keyboard knows num-0+
|
|
for(var _n = 0; _n < 10; _n++){
|
|
var code = _n + 48;
|
|
var numCode = _n + 96;
|
|
keycode[_n] = code;
|
|
keycode["num-" + _n] = numCode;
|
|
keycode._alias[code] = [
|
|
numCode
|
|
];
|
|
}
|
|
// Latin characters (65 - 90)
|
|
for(var _n2 = 0; _n2 < 26; _n2++){
|
|
var _code = _n2 + 65;
|
|
var name$1 = String.fromCharCode(_code).toLowerCase();
|
|
keycode[name$1] = _code;
|
|
}
|
|
var modifier = {
|
|
alt: "altKey",
|
|
ctrl: "ctrlKey",
|
|
meta: "metaKey",
|
|
shift: "shiftKey"
|
|
};
|
|
var modifierSequence = Object.keys(modifier).map(function(name) {
|
|
return modifier[name];
|
|
});
|
|
function createExpectedModifiers(ignoreModifiers) {
|
|
var value = ignoreModifiers ? null : false;
|
|
return {
|
|
altKey: value,
|
|
ctrlKey: value,
|
|
metaKey: value,
|
|
shiftKey: value
|
|
};
|
|
}
|
|
function resolveModifiers(modifiers) {
|
|
var ignoreModifiers = modifiers.indexOf("*") !== -1;
|
|
var expected = createExpectedModifiers(ignoreModifiers);
|
|
modifiers.forEach(function(token) {
|
|
if (token === "*") {
|
|
// we've already covered the all-in operator
|
|
return;
|
|
}
|
|
// we want the modifier pressed
|
|
var value = true;
|
|
var operator = token.slice(0, 1);
|
|
if (operator === "?") {
|
|
// we don't care if the modifier is pressed
|
|
value = null;
|
|
} else if (operator === "!") {
|
|
// we do not want the modifier pressed
|
|
value = false;
|
|
}
|
|
if (value !== true) {
|
|
// compensate for the modifier's operator
|
|
token = token.slice(1);
|
|
}
|
|
var propertyName = modifier[token];
|
|
if (!propertyName) {
|
|
throw new TypeError('Unknown modifier "' + token + '"');
|
|
}
|
|
expected[propertyName] = value;
|
|
});
|
|
return expected;
|
|
}
|
|
function resolveKey(key) {
|
|
var code = keycode[key] || parseInt(key, 10);
|
|
if (!code || typeof code !== "number" || isNaN(code)) {
|
|
throw new TypeError('Unknown key "' + key + '"');
|
|
}
|
|
return [
|
|
code
|
|
].concat(keycode._alias[code] || []);
|
|
}
|
|
function matchModifiers(expected, event) {
|
|
// returns true on match
|
|
return !modifierSequence.some(function(prop) {
|
|
// returns true on mismatch
|
|
return typeof expected[prop] === "boolean" && Boolean(event[prop]) !== expected[prop];
|
|
});
|
|
}
|
|
function keyBinding(text) {
|
|
return text.split(/\s+/).map(function(_text) {
|
|
var tokens = _text.split("+");
|
|
var _modifiers = resolveModifiers(tokens.slice(0, -1));
|
|
var _keyCodes = resolveKey(tokens.slice(-1));
|
|
return {
|
|
keyCodes: _keyCodes,
|
|
modifiers: _modifiers,
|
|
matchModifiers: matchModifiers.bind(null, _modifiers)
|
|
};
|
|
});
|
|
}
|
|
// Node.compareDocumentPosition is available since IE9
|
|
// see https://developer.mozilla.org/docs/Web/API/Node.compareDocumentPosition
|
|
// callback returns true when element is contained by parent or is the parent suited for use with Array.some()
|
|
/*
|
|
USAGE:
|
|
var isChildOf = getParentComparator({parent: someNode});
|
|
listOfElements.some(isChildOf)
|
|
*/ function getParentComparator() {
|
|
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, parent = _ref.parent, element = _ref.element, includeSelf = _ref.includeSelf;
|
|
if (parent) {
|
|
return function isChildOf(node) {
|
|
return Boolean(includeSelf && node === parent || parent.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINED_BY);
|
|
};
|
|
} else if (element) {
|
|
return function isParentOf(node) {
|
|
return Boolean(includeSelf && element === node || node.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_CONTAINED_BY);
|
|
};
|
|
}
|
|
throw new TypeError("util/compare-position#getParentComparator required either options.parent or options.element");
|
|
}
|
|
// Bug 286933 - Key events in the autocomplete popup should be hidden from page scripts
|
|
// @browser-issue Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=286933
|
|
function whenKey() {
|
|
var map = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
var bindings = {};
|
|
var context = nodeArray(map.context)[0] || document.documentElement;
|
|
delete map.context;
|
|
var filter = nodeArray(map.filter);
|
|
delete map.filter;
|
|
var mapKeys = Object.keys(map);
|
|
if (!mapKeys.length) {
|
|
throw new TypeError("when/key requires at least one option key");
|
|
}
|
|
var registerBinding = function registerBinding(event) {
|
|
event.keyCodes.forEach(function(code) {
|
|
if (!bindings[code]) {
|
|
bindings[code] = [];
|
|
}
|
|
bindings[code].push(event);
|
|
});
|
|
};
|
|
mapKeys.forEach(function(text) {
|
|
if (typeof map[text] !== "function") {
|
|
throw new TypeError('when/key requires option["' + text + '"] to be a function');
|
|
}
|
|
var addCallback = function addCallback(event) {
|
|
event.callback = map[text];
|
|
return event;
|
|
};
|
|
keyBinding(text).map(addCallback).forEach(registerBinding);
|
|
});
|
|
var handleKeyDown = function handleKeyDown(event) {
|
|
if (event.defaultPrevented) {
|
|
return;
|
|
}
|
|
if (filter.length) {
|
|
// ignore elements within the exempted sub-trees
|
|
var isParentOfElement = getParentComparator({
|
|
element: event.target,
|
|
includeSelf: true
|
|
});
|
|
if (filter.some(isParentOfElement)) {
|
|
return;
|
|
}
|
|
}
|
|
var key = event.keyCode || event.which;
|
|
if (!bindings[key]) {
|
|
return;
|
|
}
|
|
bindings[key].forEach(function(_event) {
|
|
if (!_event.matchModifiers(event)) {
|
|
return;
|
|
}
|
|
_event.callback.call(context, event, disengage);
|
|
});
|
|
};
|
|
context.addEventListener("keydown", handleKeyDown, false);
|
|
var disengage = function disengage() {
|
|
context.removeEventListener("keydown", handleKeyDown, false);
|
|
};
|
|
return {
|
|
disengage: disengage
|
|
};
|
|
}
|
|
function _default(param) {
|
|
let { context } = param === void 0 ? {} : param;
|
|
if (!context) {
|
|
context = document.documentElement;
|
|
}
|
|
// Make sure the supports tests are run before intercepting the Tab key,
|
|
// or IE10 and IE11 will fail to process the first Tab key event. Not
|
|
// limiting this warm-up to IE because it may be a problem elsewhere, too.
|
|
queryTabsequence();
|
|
return whenKey({
|
|
// Safari on OSX may require ALT+TAB to reach links,
|
|
// see https://github.com/medialize/ally.js/issues/146
|
|
"?alt+?shift+tab": function altShiftTab(event) {
|
|
// we're completely taking over the Tab key handling
|
|
event.preventDefault();
|
|
var sequence = queryTabsequence({
|
|
context: context
|
|
});
|
|
var backward = event.shiftKey;
|
|
var first = sequence[0];
|
|
var last = sequence[sequence.length - 1];
|
|
// wrap around first to last, last to first
|
|
var source = backward ? first : last;
|
|
var target = backward ? last : first;
|
|
if (isActiveElement(source)) {
|
|
target.focus();
|
|
return;
|
|
}
|
|
// find current position in tabsequence
|
|
var currentIndex = void 0;
|
|
var found = sequence.some(function(element, index) {
|
|
if (!isActiveElement(element)) {
|
|
return false;
|
|
}
|
|
currentIndex = index;
|
|
return true;
|
|
});
|
|
if (!found) {
|
|
// redirect to first as we're not in our tabsequence
|
|
first.focus();
|
|
return;
|
|
}
|
|
// shift focus to previous/next element in the sequence
|
|
var offset = backward ? -1 : 1;
|
|
sequence[currentIndex + offset].focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
|
|
Object.defineProperty(exports.default, '__esModule', { value: true });
|
|
Object.assign(exports.default, exports);
|
|
module.exports = exports.default;
|
|
}
|
|
|
|
//# sourceMappingURL=maintain--tab-focus.js.map |