UNPKG

gatsby

Version:
1,761 lines (1,491 loc) 102 kB
/* eslint-disable */ // Adapted from https://github.com/medialize/ally.js // License: MIT // Copyright (c) 2015 Rodney Rehm // // Entrypoint: ally.js/maintain/tab-focus import _platform from 'platform' import cssEscape from '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/en-US/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)) // 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, // "Chrome", "Chrome Mobile", "Opera" GECKO: GECKO, // "Firefox" TRIDENT: TRIDENT, // "Internet Explorer" EDGE: EDGE, // "Microsoft Edge" WEBKIT: WEBKIT, // "Safari" // 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/en-US/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/en-US/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/en-US/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/en-US/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/en-US/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/en-US/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() { var fragment = document.createElement('div') fragment.innerHTML = '<svg><foreignObject width="30" height="30">\n <input type="text"/>\n </foreignObject></svg>' return fragment.firstChild.firstChild } 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/en-US/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/en-US/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/en-US/docs/Web/API/Document/getElementsByName var map = _document.querySelector('map[name="' + cssEscape(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/en-US/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="#' + cssEscape(map.name) + '"]') || null ) } var supports$2 = void 0 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-us