1// File#: _1_off-canvas-content 2// Usage: codyhouse.co/license 3(function() { 4 var OffCanvas = function(element) { 5 this.element = element; 6 this.wrapper = document.getElementsByClassName('js-off-canvas')[0]; 7 this.main = document.getElementsByClassName('off-canvas__main')[0]; 8 this.triggers = document.querySelectorAll('[aria-controls="'+this.element.getAttribute('id')+'"]'); 9 this.closeBtn = this.element.getElementsByClassName('js-off-canvas__close-btn'); 10 this.selectedTrigger = false; 11 this.firstFocusable = null; 12 this.lastFocusable = null; 13 this.animating = false; 14 initOffCanvas(this); 15 }; 16 17 function initOffCanvas(panel) { 18 panel.element.setAttribute('aria-hidden', 'true'); 19 for(var i = 0 ; i < panel.triggers.length; i++) { // listen to the click on off-canvas content triggers 20 panel.triggers[i].addEventListener('click', function(event){ 21 panel.selectedTrigger = event.currentTarget; 22 event.preventDefault(); 23 togglePanel(panel); 24 }); 25 } 26 27 // listen to the triggerOpenPanel event -> open panel without a trigger button 28 panel.element.addEventListener('triggerOpenPanel', function(event){ 29 if(event.detail) panel.selectedTrigger = event.detail; 30 openPanel(panel); 31 }); 32 // listen to the triggerClosePanel event -> open panel without a trigger button 33 panel.element.addEventListener('triggerClosePanel', function(event){ 34 closePanel(panel); 35 }); 36 }; 37 38 function togglePanel(panel) { 39 var status = (panel.element.getAttribute('aria-hidden') == 'true') ? 'close' : 'open'; 40 if(status == 'close') openPanel(panel); 41 else closePanel(panel); 42 }; 43 44 function openPanel(panel) { 45 if(panel.animating) return; // already animating 46 emitPanelEvents(panel, 'openPanel', ''); 47 panel.animating = true; 48 panel.element.setAttribute('aria-hidden', 'false'); 49 Util.addClass(panel.wrapper, 'off-canvas--visible'); 50 getFocusableElements(panel); 51 var transitionEl = panel.element; 52 if(panel.closeBtn.length > 0 && !Util.hasClass(panel.closeBtn[0], 'js-off-canvas__a11y-close-btn')) transitionEl = panel.closeBtn[0]; 53 transitionEl.addEventListener('transitionend', function cb(){ 54 // wait for the end of transition to move focus and update the animating property 55 panel.animating = false; 56 Util.moveFocus(panel.element); 57 transitionEl.removeEventListener('transitionend', cb); 58 }); 59 if(!transitionSupported) panel.animating = false; 60 initPanelEvents(panel); 61 }; 62 63 function closePanel(panel, bool) { 64 if(panel.animating) return; 65 panel.animating = true; 66 panel.element.setAttribute('aria-hidden', 'true'); 67 Util.removeClass(panel.wrapper, 'off-canvas--visible'); 68 panel.main.addEventListener('transitionend', function cb(){ 69 panel.animating = false; 70 if(panel.selectedTrigger) panel.selectedTrigger.focus(); 71 setTimeout(function(){panel.selectedTrigger = false;}, 10); 72 panel.main.removeEventListener('transitionend', cb); 73 }); 74 if(!transitionSupported) panel.animating = false; 75 cancelPanelEvents(panel); 76 emitPanelEvents(panel, 'closePanel', bool); 77 }; 78 79 function initPanelEvents(panel) { //add event listeners 80 panel.element.addEventListener('keydown', handleEvent.bind(panel)); 81 panel.element.addEventListener('click', handleEvent.bind(panel)); 82 }; 83 84 function cancelPanelEvents(panel) { //remove event listeners 85 panel.element.removeEventListener('keydown', handleEvent.bind(panel)); 86 panel.element.removeEventListener('click', handleEvent.bind(panel)); 87 }; 88 89 function handleEvent(event) { 90 switch(event.type) { 91 case 'keydown': 92 initKeyDown(this, event); 93 break; 94 case 'click': 95 initClick(this, event); 96 break; 97 } 98 }; 99 100 function initClick(panel, event) { // close panel when clicking on close button 101 if( !event.target.closest('.js-off-canvas__close-btn')) return; 102 event.preventDefault(); 103 closePanel(panel, 'close-btn'); 104 }; 105 106 function initKeyDown(panel, event) { 107 if( event.keyCode && event.keyCode == 27 || event.key && event.key == 'Escape' ) { 108 //close off-canvas panel on esc 109 closePanel(panel, 'key'); 110 } else if( event.keyCode && event.keyCode == 9 || event.key && event.key == 'Tab' ) { 111 //trap focus inside panel 112 trapFocus(panel, event); 113 } 114 }; 115 116 function trapFocus(panel, event) { 117 if( panel.firstFocusable == document.activeElement && event.shiftKey) { 118 //on Shift+Tab ->
118 focus last focusable element when focus moves out of panel 119 event.preventDefault(); 120 panel.lastFocusable.focus(); 121 } 122 if( panel.lastFocusable == document.activeElement && !event.shiftKey) { 123 //on Tab -> focus first focusable element when focus moves out of panel 124 event.preventDefault(); 125 panel.firstFocusable.focus(); 126 } 127 }; 128 129 function getFocusableElements(panel) { //get all focusable elements inside the off-canvas content 130 var allFocusable = panel.element.querySelectorAll('[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex="-1"]), [contenteditable], audio[controls], video[controls], summary'); 131 getFirstVisible(panel, allFocusable); 132 getLastVisible(panel, allFocusable); 133 }; 134 135 function getFirstVisible(panel, elements) { //get first visible focusable element inside the off-canvas content 136 for(var i = 0; i < elements.length; i++) { 137 if( elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length ) { 138 panel.firstFocusable = elements[i]; 139 return true; 140 } 141 } 142 }; 143 144 function getLastVisible(panel, elements) { //get last visible focusable element inside the off-canvas content 145 for(var i = elements.length - 1; i >= 0; i--) { 146 if( elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length ) { 147 panel.lastFocusable = elements[i]; 148 return true; 149 } 150 } 151 }; 152 153 function emitPanelEvents(panel, eventName, target) { // emit custom event 154 var event = new CustomEvent(eventName, {detail: target}); 155 panel.element.dispatchEvent(event); 156 }; 157 158 //initialize the OffCanvas objects 159 var offCanvas = document.getElementsByClassName('js-off-canvas__panel'), 160 transitionSupported = Util.cssSupports('transition'); 161 if( offCanvas.length > 0 ) { 162 for( var i = 0; i < offCanvas.length; i++) { 163 (function(i){new OffCanvas(offCanvas[i]);})(i); 164 } 165 } 166}());
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.