UNPKG

ibag

Version:

A visual, fast, and customizable front-end scaffolding.

3,218 lines 100 kB
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

exports.__esModule = true;
exports.default = _default;

var _platform2 = _interopRequireDefault(require("platform"));

var _css = _interopRequireDefault(require("css.escape"));

/* eslint-disable */
// Adapted from https://github.com/medialize/ally.js
// License: MIT
// Copyright (c) 2015 Rodney Rehm
//
// Entrypoint: ally.js/maintain/tab-focus
// 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(_platform2.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,
  // "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="' + (0, _css.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/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="#' + (0, _css.default)(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-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/en-US/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/en-US/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/en-US/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);
      }
    } // remember which host is the child of which other host

  }, {
    key: '_registerHostParent',
    value: function _registerHostParent(host, parent) {
      if (!this.inHost[parent._sortingId]) {
        this.inHost[parent._sortingId] = [];
      }

      this.inHost[parent._sortingId].push(host);
    } // remember which elements a host contains

  }, {
    key: '_registerElement',
    value: function _registerElement(element, host) {
      if (!this.elements[host._sortingId]) {
        this.elements[host._sortingId] = [];
      }

      this.elements[host._sortingId].push(element);
    } // remove shadowed elements from the sequence and register
    // the ShadowHosts they belong to so we know what to sort
    // later on

  }, {
    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);
    } // inject hosts into the sequence, sort everything,
    // and recoursively replace hosts by its descendants

  }, {
    key: 'sort',
    value: function sort(elements) {
      var _elements = this._injectHosts(elements);

      _elements = this._replaceHosts(_elements);

      this._cleanup();

      return _elements;
    } // merge ShadowHosts into the element lists of other ShadowHosts
    // or the document, then sort the individual lists

  }, {
    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/en-US/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/en-US/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/en-US/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({
  context
} = {}) {
  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();
    }
  });
}