--- /dev/null
+/**
+ * Applies the :focus-visible polyfill at the given scope.
+ * A scope in this case is either the top-level Document or a Shadow Root.
+ *
+ * @param {(Document|ShadowRoot)} scope
+ * @see https://github.com/WICG/focus-visible
+ */
+function applyFocusVisiblePolyfill(scope) {
+ var hadKeyboardEvent = true;
+ var hadFocusVisibleRecently = false;
+ var hadFocusVisibleRecentlyTimeout = null;
+
+ var inputTypesWhitelist = {
+ text: true,
+ search: true,
+ url: true,
+ tel: true,
+ email: true,
+ password: true,
+ number: true,
+ date: true,
+ month: true,
+ week: true,
+ time: true,
+ datetime: true,
+ 'datetime-local': true
+ };
+
+ /**
+ * Helper function for legacy browsers and iframes which sometimes focus
+ * elements like document, body, and non-interactive SVG.
+ * @param {Element} el
+ */
+ function isValidFocusTarget(el) {
+ if (
+ el &&
+ el !== document &&
+ el.nodeName !== 'HTML' &&
+ el.nodeName !== 'BODY' &&
+ 'classList' in el &&
+ 'contains' in el.classList
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Computes whether the given element should automatically trigger the
+ * `focus-visible` class being added, i.e. whether it should always match
+ * `:focus-visible` when focused.
+ * @param {Element} el
+ * @return {boolean}
+ */
+ function focusTriggersKeyboardModality(el) {
+ var type = el.type;
+ var tagName = el.tagName;
+
+ if (tagName == 'INPUT' && inputTypesWhitelist[type] && !el.readOnly) {
+ return true;
+ }
+
+ if (tagName == 'TEXTAREA' && !el.readOnly) {
+ return true;
+ }
+
+ if (el.isContentEditable) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Add the `focus-visible` class to the given element if it was not added by
+ * the author.
+ * @param {Element} el
+ */
+ function addFocusVisibleClass(el) {
+ if (el.classList.contains('focus-visible')) {
+ return;
+ }
+ el.classList.add('focus-visible');
+ el.setAttribute('data-focus-visible-added', '');
+ }
+
+ /**
+ * Remove the `focus-visible` class from the given element if it was not
+ * originally added by the author.
+ * @param {Element} el
+ */
+ function removeFocusVisibleClass(el) {
+ if (!el.hasAttribute('data-focus-visible-added')) {
+ return;
+ }
+ el.classList.remove('focus-visible');
+ el.removeAttribute('data-focus-visible-added');
+ }
+
+ /**
+ * If the most recent user interaction was via the keyboard;
+ * and the key press did not include a meta, alt/option, or control key;
+ * then the modality is keyboard. Otherwise, the modality is not keyboard.
+ * Apply `focus-visible` to any current active element and keep track
+ * of our keyboard modality state with `hadKeyboardEvent`.
+ * @param {KeyboardEvent} e
+ */
+ function onKeyDown(e) {
+ if (e.metaKey || e.altKey || e.ctrlKey) {
+ return;
+ }
+
+ if (isValidFocusTarget(scope.activeElement)) {
+ addFocusVisibleClass(scope.activeElement);
+ }
+
+ hadKeyboardEvent = true;
+ }
+
+ /**
+ * If at any point a user clicks with a pointing device, ensure that we change
+ * the modality away from keyboard.
+ * This avoids the situation where a user presses a key on an already focused
+ * element, and then clicks on a different element, focusing it with a
+ * pointing device, while we still think we're in keyboard modality.
+ * @param {Event} e
+ */
+ function onPointerDown(e) {
+ hadKeyboardEvent = false;
+ }
+
+ /**
+ * On `focus`, add the `focus-visible` class to the target if:
+ * - the target received focus as a result of keyboard navigation, or
+ * - the event target is an element that will likely require interaction
+ * via the keyboard (e.g. a text box)
+ * @param {Event} e
+ */
+ function onFocus(e) {
+ // Prevent IE from focusing the document or HTML element.
+ if (!isValidFocusTarget(e.target)) {
+ return;
+ }
+
+ if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {
+ addFocusVisibleClass(e.target);
+ }
+ }
+
+ /**
+ * On `blur`, remove the `focus-visible` class from the target.
+ * @param {Event} e
+ */
+ function onBlur(e) {
+ if (!isValidFocusTarget(e.target)) {
+ return;
+ }
+
+ if (
+ e.target.classList.contains('focus-visible') ||
+ e.target.hasAttribute('data-focus-visible-added')
+ ) {
+ // To detect a tab/window switch, we look for a blur event followed
+ // rapidly by a visibility change.
+ // If we don't see a visibility change within 100ms, it's probably a
+ // regular focus change.
+ hadFocusVisibleRecently = true;
+ window.clearTimeout(hadFocusVisibleRecentlyTimeout);
+ hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {
+ hadFocusVisibleRecently = false;
+ window.clearTimeout(hadFocusVisibleRecentlyTimeout);
+ }, 100);
+ removeFocusVisibleClass(e.target);
+ }
+ }
+
+ /**
+ * If the user changes tabs, keep track of whether or not the previously
+ * focused element had .focus-visible.
+ * @param {Event} e
+ */
+ function onVisibilityChange(e) {
+ if (document.visibilityState == 'hidden') {
+ // If the tab becomes active again, the browser will handle calling focus
+ // on the element (Safari actually calls it twice).
+ // If this tab change caused a blur on an element with focus-visible,
+ // re-apply the class when the user switches back to the tab.
+ if (hadFocusVisibleRecently) {
+ hadKeyboardEvent = true;
+ }
+ addInitialPointerMoveListeners();
+ }
+ }
+
+ /**
+ * Add a group of listeners to detect usage of any pointing devices.
+ * These listeners will be added when the polyfill first loads, and anytime
+ * the window is blurred, so that they are active when the window regains
+ * focus.
+ */
+ function addInitialPointerMoveListeners() {
+ document.addEventListener('mousemove', onInitialPointerMove);
+ document.addEventListener('mousedown', onInitialPointerMove);
+ document.addEventListener('mouseup', onInitialPointerMove);
+ document.addEventListener('pointermove', onInitialPointerMove);
+ document.addEventListener('pointerdown', onInitialPointerMove);
+ document.addEventListener('pointerup', onInitialPointerMove);
+ document.addEventListener('touchmove', onInitialPointerMove);
+ document.addEventListener('touchstart', onInitialPointerMove);
+ document.addEventListener('touchend', onInitialPointerMove);
+ }
+
+ function removeInitialPointerMoveListeners() {
+ document.removeEventListener('mousemove', onInitialPointerMove);
+ document.removeEventListener('mousedown', onInitialPointerMove);
+ document.removeEventListener('mouseup', onInitialPointerMove);
+ document.removeEventListener('pointermove', onInitialPointerMove);
+ document.removeEventListener('pointerdown', onInitialPointerMove);
+ document.removeEventListener('pointerup', onInitialPointerMove);
+ document.removeEventListener('touchmove', onInitialPointerMove);
+ document.removeEventListener('touchstart', onInitialPointerMove);
+ document.removeEventListener('touchend', onInitialPointerMove);
+ }
+
+ /**
+ * When the polfyill first loads, assume the user is in keyboard modality.
+ * If any event is received from a pointing device (e.g. mouse, pointer,
+ * touch), turn off keyboard modality.
+ * This accounts for situations where focus enters the page from the URL bar.
+ * @param {Event} e
+ */
+ function onInitialPointerMove(e) {
+ // Work around a Safari quirk that fires a mousemove on <html> whenever the
+ // window blurs, even if you're tabbing out of the page. ¯\_(ツ)_/¯
+ if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {
+ return;
+ }
+
+ hadKeyboardEvent = false;
+ removeInitialPointerMoveListeners();
+ }
+
+ // For some kinds of state, we are interested in changes at the global scope
+ // only. For example, global pointer input, global key presses and global
+ // visibility change should affect the state at every scope:
+ document.addEventListener('keydown', onKeyDown, true);
+ document.addEventListener('mousedown', onPointerDown, true);
+ document.addEventListener('pointerdown', onPointerDown, true);
+ document.addEventListener('touchstart', onPointerDown, true);
+ document.addEventListener('visibilitychange', onVisibilityChange, true);
+
+ addInitialPointerMoveListeners();
+
+ // For focus and blur, we specifically care about state changes in the local
+ // scope. This is because focus / blur events that originate from within a
+ // shadow root are not re-dispatched from the host element if it was already
+ // the active element in its own scope:
+ scope.addEventListener('focus', onFocus, true);
+ scope.addEventListener('blur', onBlur, true);
+
+ // We detect that a node is a ShadowRoot by ensuring that it is a
+ // DocumentFragment and also has a host property. This check covers native
+ // implementation and polyfill implementation transparently. If we only cared
+ // about the native implementation, we could just check if the scope was
+ // an instance of a ShadowRoot.
+ if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {
+ // Since a ShadowRoot is a special kind of DocumentFragment, it does not
+ // have a root element to add a class to. So, we add this attribute to the
+ // host element instead:
+ scope.host.setAttribute('data-js-focus-visible', '');
+ } else if (scope.nodeType === Node.DOCUMENT_NODE) {
+ document.documentElement.classList.add('js-focus-visible');
+ }
+}
+
+// It is important to wrap all references to global window and document in
+// these checks to support server-side rendering use cases
+// @see https://github.com/WICG/focus-visible/issues/199
+if (typeof window !== 'undefined' && typeof document !== 'undefined') {
+ // Make the polyfill helper globally available. This can be used as a signal
+ // to interested libraries that wish to coordinate with the polyfill for e.g.,
+ // applying the polyfill to a shadow root:
+ window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;
+
+ // Notify interested libraries of the polyfill's presence, in case the
+ // polyfill was loaded lazily:
+ var event;
+
+ try {
+ event = new CustomEvent('focus-visible-polyfill-ready');
+ } catch (error) {
+ // IE11 does not support using CustomEvent as a constructor directly:
+ event = document.createEvent('CustomEvent');
+ event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});
+ }
+
+ window.dispatchEvent(event);
+}
+
+if (typeof document !== 'undefined') {
+ // Apply the polyfill to the global document, so that no JavaScript
+ // coordination is required to use the polyfill in the top-level document:
+ applyFocusVisiblePolyfill(document);
+}