UNPKG

next-csrf

Version:

CSRF mitigation library for Next.js

116 lines (96 loc) 4.1 kB
/* add .ally-focus-within class to parents of document.activeElement, to provide the functionality of :focus-within where it's not available see https://dev.w3.org/csswg/selectors-4/#the-focus-within-pseudo USAGE: style/focus-within() */ import { addClass, removeClass } from '../util/toggle-class'; import shadowFocus from '../event/shadow-focus'; import getActiveElements from '../get/active-elements'; import getParents from '../get/parents'; import decorateService from '../util/decorate-service'; import selectInShadows from '../util/select-in-shadows'; import _supports from '../supports/supports'; var supports = void 0; // preferring focusin/out because they are synchronous in IE10+11 var supportsFocusIn = typeof document !== 'undefined' && 'onfocusin' in document; var focusEventName = supportsFocusIn ? 'focusin' : 'focus'; var blurEventName = supportsFocusIn ? 'focusout' : 'blur'; var className = 'ally-focus-within'; // defined in engage(); var selector = void 0; var blurTimer = void 0; var shadowHandle = void 0; function applyFocusWithinClass(active) { var _active = active || getActiveElements(); if (!supports.cssShadowPiercingDeepCombinator) { // no shadow-piercing descendant selector, no joy _active = _active.slice(-1); } // identify the elements that currently have :focus-within var _current = [].slice.call(document.querySelectorAll(selector), 0); // get the path (ancestry) of each ShadowRoot and merge them into a flat list var elements = _active.map(function (context) { return getParents({ context: context }); }).reduce(function (previous, current) { return current.concat(previous); }, []); // remove the class only from elements that would not receive it again (minimize dom action) _current.forEach(function (element) { if (elements.indexOf(element) !== -1) { return; } removeClass(element, className); }); // apply the class only to elements that do not yet have it (minimize dom action) elements.forEach(function (element) { if (_current.indexOf(element) !== -1) { return; } addClass(element, className); }); } function handleDocumentBlurEvent() { // we won't get a focus for <body>, but a delayed blur handler will achieve // the same thing listening for focus would've done, unless we get a focus, of course blurTimer = (window.setImmediate || window.setTimeout)(function () { applyFocusWithinClass(); }); } function handleDocumentFocusEvent() { // abort any handlers that come from document or element blur handlers (window.clearImmediate || window.clearTimeout)(blurTimer); // NOTE: we could overcome Firefox 34 issue of not supporting ShadowRoot.host by // passing event.target (which references the first-level ShadowHost), but that // would require applyFocusWithinClass() to distinguish between the argument and // getActiveElements(). applyFocusWithinClass(); } function handleShadowFocusEvent(event) { applyFocusWithinClass(event.detail.elements); } function disengage() { shadowHandle && shadowHandle.disengage(); (window.clearImmediate || window.clearTimeout)(blurTimer); document.removeEventListener(blurEventName, handleDocumentBlurEvent, true); document.removeEventListener(focusEventName, handleDocumentFocusEvent, true); document.removeEventListener('shadow-focus', handleShadowFocusEvent, true); // remove any remaining ally-within-focus occurrences [].forEach.call(document.querySelectorAll(selector), function (element) { removeClass(element, className); }); } function engage() { if (!supports) { supports = _supports(); selector = selectInShadows('.' + className); } shadowHandle = shadowFocus(); document.addEventListener(blurEventName, handleDocumentBlurEvent, true); document.addEventListener(focusEventName, handleDocumentFocusEvent, true); document.addEventListener('shadow-focus', handleShadowFocusEvent, true); applyFocusWithinClass(); } export default decorateService({ engage: engage, disengage: disengage }); //# sourceMappingURL=focus-within.js.map