1export default class AccessibilityHelper { 2 static focusableSelectors = [ 3 'a[href]:not([tabindex="-1"])', 4 'area[href]:not([tabindex="-1"])', 5 'input:not([disabled]):not([tabindex="-1"])', 6 'select:not([disabled]):not([tabindex="-1"])', 7 'textarea:not([disabled]):not([tabindex="-1"])', 8 'button:not([disabled]):not([tabindex="-1"])', 9 'iframe:not([tabindex="-1"])', 10 'object:not([tabindex="-1"])', 11 'embed:not([tabindex="-1"])', 12 '[tabindex]:not([tabindex="-1"])', 13 '[contenteditable]:not([tabindex="-1"])', 14 ]; 15 16 constructor(wrapper, timeout = 0) { 17 this.wrapper = wrapper; 18 this.timeout = timeout; 19 this.handleKeydown = this.handleTrappedKeydown.bind(this); 20 } 21 22 getTrappedElements() { 23 return this.wrapper.querySelectorAll(AccessibilityHelper.focusableSelectors.join(',')); 24 } 25 26 trap() { 27 const elements = this.getTrappedElements(); 28 if (elements.length === 0) return; 29 30 this.focusedElement = document.activeElement; 31 this.focusedElement.blur(); 32 this.wrapper.setAttribute('tabindex', '-1'); 33 this.wrapper.style.outline = 'none'; 34 setTimeout(() => { 35 this.wrapper.focus(); 36 this.wrapper.addEventListener('keydown', this.handleKeydown); 37 }, this.timeout); 38 } 39 40 handleTrappedKeydown(event) { 41 if (event.key !== 'Tab') { 42 return; 43 } 44 45 const elements = this.getTrappedElements(); 46 if (elements.length === 0) { 47 return; 48 } 49 50 51 const first = elements[0]; 52 const last = elements[elements.length - 1]; 53 if (event.shiftKey && document.activeElement === first) { 54 event.preventDefault(); 55 last.focus(); 56 } else if (!event.shiftKey && document.activeElement === last) { 57 event.preventDefault(); 58 first.focus(); 59 } 60 } 61 62 release() { 63 this.wrapper.removeEventListener('keydown', this.handleKeydown); 64 this.wrapper.removeAttribute('tabindex'); 65 this.wrapper.style.outline = ''; 66 clearTimeout(this.delay); 67 this.delay = setTimeout(() => { 68 this.focusedElement.focus(); 69 }, 300); 70 } 71 72 static trapFocus(wrapper, timeout = 0) { 73 if (!wrapper.accessibility) { 74 wrapper.accessibility = new this(wrapper, timeout); 75 } 76 wrapper.accessibility.trap(); 77 } 78 79 static releaseTrap(wrapper) { 80 if (!wrapper.accessibility) { 81 return; 82 } 83 84 setTimeout(() => { 85 wrapper.accessibility.release() 86 }, 100); 87 } 88 89 static isClick(event) { 90 return event.type == "click" || event.type == "touchend" 91 } 92 93 static processAction(event, callback, eventsList) { 94 if (eventsList && eventsList.indexOf(event.type) == -1) { 95 return; 96 } 97 98 if (this.isClick(event) || (event.type == "keyup" && event.key == 'Enter')) { 99 callback.call(); 100 } 101 } 102}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.