1// File#: _1_responsive-sidebar 2// Usage: codyhouse.co/license 3(function() { 4 var Sidebar = function(element) { 5 this.element = element; 6 this.triggers = document.querySelectorAll('[aria-controls="'+this.element.getAttribute('id')+'"]'); 7 this.firstFocusable = null; 8 this.lastFocusable = null; 9 this.selectedTrigger = null; 10 this.showClass = "sidebar--is-visible"; 11 this.staticClass = "sidebar--static"; 12 this.customStaticClass = ""; 13 this.readyClass = "sidebar--loaded"; 14 this.layout = false; // this will be static or mobile 15 getCustomStaticClass(this); // custom classes for static version 16 initSidebar(this); 17 }; 18 19 function getCustomStaticClass(element) { 20 var customClasses = element.element.getAttribute('data-static-class'); 21 if(customClasses) element.customStaticClass = ' '+customClasses; 22 }; 23 24 function initSidebar(sidebar) { 25 initSidebarResize(sidebar); // handle changes in layout -> mobile to static and viceversa 26 27 if ( sidebar.triggers ) { // open sidebar when clicking on trigger buttons - mobile layout only 28 for(var i = 0; i < sidebar.triggers.length; i++) { 29 sidebar.triggers[i].addEventListener('click', function(event) { 30 event.preventDefault(); 31 if(Util.hasClass(sidebar.element, sidebar.showClass)) { 32 sidebar.selectedTrigger = event.target; 33 closeSidebar(sidebar); 34 return; 35 } 36 sidebar.selectedTrigger = event.target; 37 showSidebar(sidebar); 38 initSidebarEvents(sidebar); 39 }); 40 } 41 } 42 }; 43 44 function showSidebar(sidebar) { // mobile layout only 45 Util.addClass(sidebar.element, sidebar.showClass); 46 getFocusableElements(sidebar); 47 Util.moveFocus(sidebar.element); 48 }; 49 50 function closeSidebar(sidebar) { // mobile layout only 51 Util.removeClass(sidebar.element, sidebar.showClass); 52 sidebar.firstFocusable = null; 53 sidebar.lastFocusable = null; 54 if(sidebar.selectedTrigger) sidebar.selectedTrigger.focus(); 55 sidebar.element.removeAttribute('tabindex'); 56 //remove listeners 57 cancelSidebarEvents(sidebar); 58 }; 59 60 function initSidebarEvents(sidebar) { // mobile layout only 61 //add event listeners 62 sidebar.element.addEventListener('keydown', handleEvent.bind(sidebar)); 63 sidebar.element.addEventListener('click', handleEvent.bind(sidebar)); 64 }; 65 66 function cancelSidebarEvents(sidebar) { // mobile layout only 67 //remove event listeners 68 sidebar.element.removeEventListener('keydown', handleEvent.bind(sidebar)); 69 sidebar.element.removeEventListener('click', handleEvent.bind(sidebar)); 70 }; 71 72 function handleEvent(event) { // mobile layout only 73 switch(event.type) { 74 case 'click': { 75 initClick(this, event); 76 } 77 case 'keydown': { 78 initKeyDown(this, event); 79 } 80 } 81 }; 82 83 function initKeyDown(sidebar, event) { // mobile layout only 84 if( event.keyCode && event.keyCode == 27 || event.key && event.key == 'Escape' ) { 85 //close sidebar window on esc 86 closeSidebar(sidebar); 87 } else if( event.keyCode && event.keyCode == 9 || event.key && event.key == 'Tab' ) { 88 //trap focus inside sidebar 89 trapFocus(sidebar, event); 90 } 91 }; 92 93 function initClick(sidebar, event) { // mobile layout only 94 //close sidebar when clicking on close button or sidebar bg layer 95 if( !event.target.closest('.js-sidebar__close-btn') && !Util.hasClass(event.target, 'js-sidebar') ) return; 96 event.preventDefault(); 97 closeSidebar(sidebar); 98 }; 99 100 function trapFocus(sidebar, event) { // mobile layout only 101 if( sidebar.firstFocusable == document.activeElement && event.shiftKey) { 102 //on Shift+Tab ->
102 focus last focusable element when focus moves out of sidebar 103 event.preventDefault(); 104 sidebar.lastFocusable.focus(); 105 } 106 if( sidebar.lastFocusable == document.activeElement && !event.shiftKey) { 107 //on Tab -> focus first focusable element when focus moves out of sidebar 108 event.preventDefault(); 109 sidebar.firstFocusable.focus(); 110 } 111 }; 112 113 function initSidebarResize(sidebar) { 114 // custom event emitted when window is resized - detect only if the sidebar--static@{breakpoint} class was added 115 var beforeContent = getComputedStyle(sidebar.element, ':before').getPropertyValue('content'); 116 if(beforeContent && beforeContent !='' && beforeContent !='none') { 117 checkSidebarLayout(sidebar); 118 119 sidebar.element.addEventListener('update-sidebar', function(event){ 120 checkSidebarLayout(sidebar); 121 }); 122 } 123 Util.addClass(sidebar.element, sidebar.readyClass); 124 }; 125 126 function checkSidebarLayout(sidebar) { 127 var layout = getComputedStyle(sidebar.element, ':before').getPropertyValue('content').replace(/\'|"/g, ''); 128 if(layout == sidebar.layout) return; 129 sidebar.layout = layout; 130 if(layout != 'static') Util.addClass(sidebar.element, 'is-hidden'); 131 Util.toggleClass(sidebar.element, sidebar.staticClass + sidebar.customStaticClass, layout == 'static'); 132 if(layout != 'static') setTimeout(function(){Util.removeClass(sidebar.element, 'is-hidden')}); 133 // reset element role 134 (layout == 'static') ? sidebar.element.removeAttribute('role', 'alertdialog') : sidebar.element.setAttribute('role', 'alertdialog'); 135 // reset mobile behaviour 136 if(layout == 'static' && Util.hasClass(sidebar.element, sidebar.showClass)) closeSidebar(sidebar); 137 }; 138 139 function getFocusableElements(sidebar) { 140 //get all focusable elements inside the drawer 141 var allFocusable = sidebar.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'); 142 getFirstVisible(sidebar, allFocusable); 143 getLastVisible(sidebar, allFocusable); 144 }; 145 146 function getFirstVisible(sidebar, elements) { 147 //get first visible focusable element inside the sidebar 148 for(var i = 0; i < elements.length; i++) { 149 if( elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length ) { 150 sidebar.firstFocusable = elements[i]; 151 return true; 152 } 153 } 154 }; 155 156 function getLastVisible(sidebar, elements) { 157 //get last visible focusable element inside the sidebar 158 for(var i = elements.length - 1; i >= 0; i--) { 159 if( elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length ) { 160 sidebar.lastFocusable = elements[i]; 161 return true; 162 } 163 } 164 }; 165 166 //initialize the Sidebar objects 167 var sidebar = document.getElementsByClassName('js-sidebar'); 168 if( sidebar.length > 0 ) { 169 for( var i = 0; i < sidebar.length; i++) { 170 (function(i){new Sidebar(sidebar[i]);})(i); 171 } 172 // switch from mobile to static layout 173 var customEvent = new CustomEvent('update-sidebar'); 174 window.addEventListener('resize', function(event){ 175 (!window.requestAnimationFrame) ? setTimeout(function(){resetLayout();}, 250) : window.requestAnimationFrame(resetLayout); 176 }); 177 178 (window.requestAnimationFrame) // init sidebar layout 179 ? window.requestAnimationFrame(resetLayout) 180 : resetLayout(); 181 182 function resetLayout() { 183 for( var i = 0; i < sidebar.length; i++) { 184 (function(i){sidebar[i].dispatchEvent(customEvent)})(i); 185 }; 186 }; 187 } 188}());
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.