// Utility function function Util () {}; /* class manipulation functions */ Util.hasClass = function(el, className) { return el.classList.contains(className); }; Util.addClass = function(el, className) { var classList = className.split(' '); el.classList.add(classList[0]); if (classList.length > 1) Util.addClass(el, classList.slice(1).join(' ')); }; Util.removeClass = function(el, className) { var classList = className.split(' '); el.classList.remove(classList[0]); if (classList.length > 1) Util.removeClass(el, classList.slice(1).join(' ')); }; Util.toggleClass = function(el, className, bool) { if(bool) Util.addClass(el, className); else Util.removeClass(el, className); }; Util.setAttributes = function(el, attrs) { for(var key in attrs) { el.setAttribute(key, attrs[key]); } }; /* DOM manipulation */ Util.getChildrenByClassName = function(el, className) { var children = el.children, childrenByClass = []; for (var i = 0; i < children.length; i++) { if (Util.hasClass(children[i], className)) childrenByClass.push(children[i]); } return childrenByClass; }; Util.is = function(elem, selector) { if(selector.nodeType){ return elem === selector; } var qa = (typeof(selector) === 'string' ? document.querySelectorAll(selector) : selector), length = qa.length; while(length--){ if(qa[length] === elem){ return true; } } return false; }; /* Animate height of an element */ Util.setHeight = function(start, to, element, duration, cb, timeFunction) { var change = to - start, currentTime = null; var animateHeight = function(timestamp){ if (!currentTime) currentTime = timestamp; var progress = timestamp - currentTime; if(progress > duration) progress = duration; var val = parseInt((progress/duration)*change + start); if(timeFunction) { val = Math[timeFunction](progress, start, to - start, duration); } element.style.height = val+"px"; if(progress < duration) { window.requestAnimationFrame(animateHeight); } else { if(cb) cb(); } }; //set the height of the element before starting animation -> fix bug on Safari element.style.height = start+"px"; window.requestAnimationFrame(animateHeight); }; /* Smooth Scroll */ Util.scrollTo = function(final, duration, cb, scrollEl) { var element = scrollEl || window; var start = element.scrollTop || document.documentElement.scrollTop, currentTime = null; if(!scrollEl) start = window.scrollY || document.documentElement.scrollTop; var animateScroll = function(timestamp){ if (!currentTime) currentTime = timestamp; var progress = timestamp - currentTime; if(progress > duration) progress = duration; var val = Math.easeInOutQuad(progress, start, final-start, duration); element.scrollTo(0, val); if(progress < duration) { window.requestAnimationFrame(animateScroll); } else { cb && cb(); } }; window.requestAnimationFrame(animateScroll); }; /* Move Focus */ Util.moveFocus = function (element) { if( !element ) element = document.getElementsByTagName("body")[0]; element.focus(); if (document.activeElement !== element) { element.setAttribute('tabindex','-1'); element.focus(); } }; /* Misc */ Util.getIndexInArray = function(array, el) { return Array.prototype.indexOf.call(array, el); }; Util.cssSupports = function(property, value) { return CSS.supports(property, value); }; // merge a set of user options into plugin defaults // https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/ Util.extend = function() { // Variables var extended = {}; var deep = false; var i = 0; var length = arguments.length; // Check if a deep merge if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { deep = arguments[0]; i++; } // Merge the object into the extended object var merge = function (obj) { for ( var prop in obj ) { if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { // If deep merge and property is an object, merge properties if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { extended[prop] = extend( true, extended[prop], obj[prop] ); } else { extended[prop] = obj[prop]; } } } }; // Loop through each object and conduct a merge for ( ; i < length; i++ ) { var obj = arguments[i]; merge(obj); } return extended; }; // Check if Reduced Motion is enabled Util.osHasReducedMotion = function() { if(!window.matchMedia) return false; var matchMediaObj = window.matchMedia('(prefers-reduced-motion: reduce)'); if(matchMediaObj) return matchMediaObj.matches; return false; // return false if not supported }; /* Animation curves */ Math.easeInOutQuad = function (t, b, c, d) { t /= d/2; if (t < 1) return c/2*t*t + b; t--; return -c/2 * (t*(t-2) - 1) + b; }; Math.easeInQuart = function (t, b, c, d) { t /= d; return c*t*t*t*t + b; }; Math.easeOutQuart = function (t, b, c, d) { t /= d; t--; return -c * (t*t*t*t - 1) + b; }; Math.easeInOutQuart = function (t, b, c, d) { t /= d/2; if (t < 1) return c/2*t*t*t*t + b; t -= 2; return -c/2 * (t*t*t*t - 2) + b; }; Math.easeOutElastic = function (t, b, c, d) { var s=1.70158;var p=d*0.7;var a=c; if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; if (a < Math.abs(c)) { a=c; var s=p/4; } else var s = p/(2*Math.PI) * Math.asin (c/a); return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; }; /* JS Utility Classes */ // make focus ring visible only for keyboard navigation (i.e., tab key) (function() { var focusTab = document.getElementsByClassName('js-tab-focus'), shouldInit = false, outlineStyle = false, eventDetected = false; function detectClick() { if(focusTab.length > 0) { resetFocusStyle(false); window.addEventListener('keydown', detectTab); } window.removeEventListener('mousedown', detectClick); outlineStyle = false; eventDetected = true; }; function detectTab(event) { if(event.keyCode !== 9) return; resetFocusStyle(true); window.removeEventListener('keydown', detectTab); window.addEventListener('mousedown', detectClick); outlineStyle = true; }; function resetFocusStyle(bool) { var outlineStyle = bool ? '' : 'none'; for(var i = 0; i < focusTab.length; i++) { focusTab[i].style.setProperty('outline', outlineStyle); } }; function initFocusTabs() { if(shouldInit) { if(eventDetected) resetFocusStyle(outlineStyle); return; } shouldInit = focusTab.length > 0; window.addEventListener('mousedown', detectClick); }; initFocusTabs(); window.addEventListener('initFocusTabs', initFocusTabs); }()); function resetFocusTabsStyle() { window.dispatchEvent(new CustomEvent('initFocusTabs')); }; (function() { var Masonry = function(element) { this.element = element; this.list = this.element.getElementsByClassName('js-masonry__list')[0]; this.items = this.element.getElementsByClassName('js-masonry__item'); this.activeColumns = 0; this.colStartWidth = 0; this.colWidth = 0; this.colGap = 0; this.colHeights = []; this.colItems = []; getGridLayout(this); setGridLayout(this); initMasonryLayout(this); }; function getGridLayout(grid) { var itemStyle = window.getComputedStyle(grid.items[0]); if(grid.colStartWidth == 0) { grid.colStartWidth = parseFloat(itemStyle.getPropertyValue('width')); } grid.colGap = parseFloat(itemStyle.getPropertyValue('margin-right')); }; function setGridLayout(grid) { var containerWidth = parseFloat(window.getComputedStyle(grid.element).getPropertyValue('width')); grid.activeColumns = parseInt((containerWidth + grid.colGap)/(grid.colStartWidth+grid.colGap)); if(grid.activeColumns == 0) grid.activeColumns = 1; grid.colWidth = parseFloat((containerWidth - (grid.activeColumns - 1)*grid.colGap)/grid.activeColumns); for(var i = 0; i < grid.items.length; i++) { grid.items[i].style.width = grid.colWidth+'px'; } }; function initMasonryLayout(grid) { checkImgLoaded(grid); grid.element.addEventListener('masonry-resize', function(){ getGridLayout(grid); setGridLayout(grid); layItems(grid); }); grid.element.addEventListener('masonry-reset', function(event){ checkImgLoaded(grid); }); }; function layItems(grid) { Util.addClass(grid.element, 'masonry--loaded'); grid.colHeights = []; grid.colItems = []; for(var j = 0; j < grid.activeColumns; j++) { grid.colHeights.push(0); grid.colItems[j] = []; } for(var i = 0; i < grid.items.length; i++) { var minHeight = Math.min.apply(Math, grid.colHeights), index = grid.colHeights.indexOf(minHeight); if(grid.colItems[index]) grid.colItems[index].push(i); var itemHeight = grid.items[i].getBoundingClientRect().height || grid.items[i].offsetHeight || 1; grid.colHeights[index] = grid.colHeights[index] + grid.colGap + itemHeight; } var masonryHeight = Math.max.apply(Math, grid.colHeights) + 5; grid.list.style.cssText = 'height: ' + masonryHeight + 'px;'; grid.element.dispatchEvent(new CustomEvent('masonry-laid')); }; function checkImgLoaded(grid) { var imgs = grid.list.getElementsByTagName('img'); function countLoaded() { var setTimeoutOn = false; for(var i = 0; i < imgs.length; i++) { if(!imgs[i].complete) { setTimeoutOn = true; break; } else if (typeof imgs[i].naturalHeight !== "undefined" && imgs[i].naturalHeight == 0) { setTimeoutOn = true; break; } } if(!setTimeoutOn) { layItems(grid); } else { setTimeout(function(){ countLoaded(); }, 100); } }; if(imgs.length == 0) { layItems(grid); } else { countLoaded(); } }; var masonries = document.getElementsByClassName('js-masonry'), flexSupported = Util.cssSupports('flex-basis', 'auto'), masonriesArray = []; if(masonries.length > 0) { for(var i = 0; i < masonries.length; i++) { (function(i){masonriesArray.push(new Masonry(masonries[i]));})(i); } var resizingId = false, customEvent = new CustomEvent('masonry-resize'); window.addEventListener('resize', function() { clearTimeout(resizingId); resizingId = setTimeout(doneResizing, 500); }); function doneResizing() { for(var i = 0; i < masonriesArray.length; i++) { (function(i){masonriesArray[i].element.dispatchEvent(customEvent)})(i); }; }; }; })(); (function() { var Menu = function(element) { this.element = element; this.elementId = this.element.getAttribute('id'); this.menuItems = this.element.getElementsByClassName('js-menu__content'); this.trigger = document.querySelectorAll('[aria-controls="'+this.elementId+'"]'); this.selectedTrigger = false; this.menuIsOpen = false; this.initMenu(); this.initMenuEvents(); }; Menu.prototype.initMenu = function() { // init aria-labels for(var i = 0; i < this.trigger.length; i++) { Util.setAttributes(this.trigger[i], {'aria-expanded': 'false', 'aria-haspopup': 'true'}); } // init tabindex for(var i = 0; i < this.menuItems.length; i++) { this.menuItems[i].setAttribute('tabindex', '0'); } }; Menu.prototype.initMenuEvents = function() { var self = this; for(var i = 0; i < this.trigger.length; i++) {(function(i){ self.trigger[i].addEventListener('click', function(event){ event.preventDefault(); // if the menu had been previously opened by another trigger element -> close it first and reopen in the right position if(Util.hasClass(self.element, 'menu--is-visible') && self.selectedTrigger != self.trigger[i]) { self.toggleMenu(false, false); // close menu } // toggle menu self.selectedTrigger = self.trigger[i]; self.toggleMenu(!Util.hasClass(self.element, 'menu--is-visible'), true); }); })(i);} // keyboard events this.element.addEventListener('keydown', function(event) { // use up/down arrow to navigate list of menu items if( !Util.hasClass(event.target, 'js-menu__content') ) return; if( (event.keyCode && event.keyCode == 40) || (event.key && event.key.toLowerCase() == 'arrowdown') ) { self.navigateItems(event, 'next'); } else if( (event.keyCode && event.keyCode == 38) || (event.key && event.key.toLowerCase() == 'arrowup') ) { self.navigateItems(event, 'prev'); } }); }; Menu.prototype.toggleMenu = function(bool, moveFocus) { var self = this; // toggle menu visibility Util.toggleClass(this.element, 'menu--is-visible', bool); this.menuIsOpen = bool; if(bool) { this.selectedTrigger.setAttribute('aria-expanded', 'true'); Util.moveFocus(this.menuItems[0]); this.element.addEventListener("transitionend", function(event) {Util.moveFocus(self.menuItems[0]);}, {once: true}); // position the menu element this.positionMenu(); // add class to menu trigger Util.addClass(this.selectedTrigger, 'menu-control--active'); } else if(this.selectedTrigger) { this.selectedTrigger.setAttribute('aria-expanded', 'false'); if(moveFocus) Util.moveFocus(this.selectedTrigger); // remove class from menu trigger Util.removeClass(this.selectedTrigger, 'menu-control--active'); this.selectedTrigger = false; } }; Menu.prototype.positionMenu = function(event, direction) { var selectedTriggerPosition = this.selectedTrigger.getBoundingClientRect(), menuOnTop = (window.innerHeight - selectedTriggerPosition.bottom) < selectedTriggerPosition.top; // menuOnTop = window.innerHeight < selectedTriggerPosition.bottom + this.element.offsetHeight; var left = selectedTriggerPosition.left, right = (window.innerWidth - selectedTriggerPosition.right), isRight = (window.innerWidth < selectedTriggerPosition.left + this.element.offsetWidth); var horizontal = isRight ? 'right: '+right+'px;' : 'left: '+left+'px;', vertical = menuOnTop ? 'bottom: '+(window.innerHeight - selectedTriggerPosition.top)+'px;' : 'top: '+selectedTriggerPosition.bottom+'px;'; // check right position is correct -> otherwise set left to 0 if( isRight && (right + this.element.offsetWidth) > window.innerWidth) horizontal = 'left: '+ parseInt((window.innerWidth - this.element.offsetWidth)/2)+'px;'; var maxHeight = menuOnTop ? selectedTriggerPosition.top - 20 : window.innerHeight - selectedTriggerPosition.bottom - 20; this.element.setAttribute('style', horizontal + vertical +'max-height:'+Math.floor(maxHeight)+'px;'); }; Menu.prototype.navigateItems = function(event, direction) { event.preventDefault(); var index = Util.getIndexInArray(this.menuItems, event.target), nextIndex = direction == 'next' ? index + 1 : index - 1; if(nextIndex < 0) nextIndex = this.menuItems.length - 1; if(nextIndex > this.menuItems.length - 1) nextIndex = 0; Util.moveFocus(this.menuItems[nextIndex]); }; Menu.prototype.checkMenuFocus = function() { var menuParent = document.activeElement.closest('.js-menu'); if (!menuParent || !this.element.contains(menuParent)) this.toggleMenu(false, false); }; Menu.prototype.checkMenuClick = function(target) { if( !this.element.contains(target) && !target.closest('[aria-controls="'+this.elementId+'"]')) this.toggleMenu(false); }; window.Menu = Menu; //initialize the Menu objects var menus = document.getElementsByClassName('js-menu'); if( menus.length > 0 ) { var menusArray = []; var scrollingContainers = []; for( var i = 0; i < menus.length; i++) { (function(i){ menusArray.push(new Menu(menus[i])); var scrollableElement = menus[i].getAttribute('data-scrollable-element'); if(scrollableElement && !scrollingContainers.includes(scrollableElement)) scrollingContainers.push(scrollableElement); })(i); } // listen for key events window.addEventListener('keyup', function(event){ if( event.keyCode && event.keyCode == 9 || event.key && event.key.toLowerCase() == 'tab' ) { //close menu if focus is outside menu element menusArray.forEach(function(element){ element.checkMenuFocus(); }); } else if( event.keyCode && event.keyCode == 27 || event.key && event.key.toLowerCase() == 'escape' ) { // close menu on 'Esc' menusArray.forEach(function(element){ element.toggleMenu(false, false); }); } }); // close menu when clicking outside it window.addEventListener('click', function(event){ menusArray.forEach(function(element){ element.checkMenuClick(event.target); }); }); // on resize -> close all menu elements window.addEventListener('resize', function(event){ menusArray.forEach(function(element){ element.toggleMenu(false, false); }); }); // on scroll -> close all menu elements window.addEventListener('scroll', function(event){ menusArray.forEach(function(element){ if(element.menuIsOpen) element.toggleMenu(false, false); }); }); // take into account additinal scrollable containers for(var j = 0; j < scrollingContainers.length; j++) { var scrollingContainer = document.querySelector(scrollingContainers[j]); if(scrollingContainer) { scrollingContainer.addEventListener('scroll', function(event){ menusArray.forEach(function(element){ if(element.menuIsOpen) element.toggleMenu(false, false); }); }); } } } }()); (function() { var Modal = function(element) { this.element = element; this.triggers = document.querySelectorAll('[aria-controls="'+this.element.getAttribute('id')+'"]'); this.firstFocusable = null; this.lastFocusable = null; this.moveFocusEl = null; // focus will be moved to this element when modal is open this.modalFocus = this.element.getAttribute('data-modal-first-focus') ? this.element.querySelector(this.element.getAttribute('data-modal-first-focus')) : null; this.selectedTrigger = null; this.preventScrollEl = this.getPreventScrollEl(); this.showClass = "modal--is-visible"; this.initModal(); }; Modal.prototype.getPreventScrollEl = function() { var scrollEl = false; var querySelector = this.element.getAttribute('data-modal-prevent-scroll'); if(querySelector) scrollEl = document.querySelector(querySelector); return scrollEl; }; Modal.prototype.initModal = function() { var self = this; //open modal when clicking on trigger buttons if ( this.triggers ) { for(var i = 0; i < this.triggers.length; i++) { this.triggers[i].addEventListener('click', function(event) { event.preventDefault(); if(Util.hasClass(self.element, self.showClass)) { self.closeModal(); return; } self.selectedTrigger = event.currentTarget; self.showModal(); self.initModalEvents(); }); } } // listen to the openModal event -> open modal without a trigger button this.element.addEventListener('openModal', function(event){ if(event.detail) self.selectedTrigger = event.detail; self.showModal(); self.initModalEvents(); }); // listen to the closeModal event -> close modal without a trigger button this.element.addEventListener('closeModal', function(event){ if(event.detail) self.selectedTrigger = event.detail; self.closeModal(); }); // if modal is open by default -> initialise modal events if(Util.hasClass(this.element, this.showClass)) this.initModalEvents(); }; Modal.prototype.showModal = function() { var self = this; Util.addClass(this.element, this.showClass); this.getFocusableElements(); if(this.moveFocusEl) { this.moveFocusEl.focus(); // wait for the end of transitions before moving focus this.element.addEventListener("transitionend", function cb(event) { self.moveFocusEl.focus(); self.element.removeEventListener("transitionend", cb); }); } this.emitModalEvents('modalIsOpen'); // change the overflow of the preventScrollEl if(this.preventScrollEl) this.preventScrollEl.style.overflow = 'hidden'; }; Modal.prototype.closeModal = function() { if(!Util.hasClass(this.element, this.showClass)) return; Util.removeClass(this.element, this.showClass); this.firstFocusable = null; this.lastFocusable = null; this.moveFocusEl = null; if(this.selectedTrigger) this.selectedTrigger.focus(); //remove listeners this.cancelModalEvents(); this.emitModalEvents('modalIsClose'); // change the overflow of the preventScrollEl if(this.preventScrollEl) this.preventScrollEl.style.overflow = ''; }; Modal.prototype.initModalEvents = function() { //add event listeners this.element.addEventListener('keydown', this); this.element.addEventListener('click', this); }; Modal.prototype.cancelModalEvents = function() { //remove event listeners this.element.removeEventListener('keydown', this); this.element.removeEventListener('click', this); }; Modal.prototype.handleEvent = function (event) { switch(event.type) { case 'click': { this.initClick(event); } case 'keydown': { this.initKeyDown(event); } } }; Modal.prototype.initKeyDown = function(event) { if( event.keyCode && event.keyCode == 9 || event.key && event.key == 'Tab' ) { //trap focus inside modal this.trapFocus(event); } else if( (event.keyCode && event.keyCode == 13 || event.key && event.key == 'Enter') && event.target.closest('.js-modal__close')) { event.preventDefault(); this.closeModal(); // close modal when pressing Enter on close button } }; Modal.prototype.initClick = function(event) { //close modal when clicking on close button or modal bg layer if( !event.target.closest('.js-modal__close') && !Util.hasClass(event.target, 'js-modal') ) return; event.preventDefault(); this.closeModal(); }; Modal.prototype.trapFocus = function(event) { if( this.firstFocusable == document.activeElement && event.shiftKey) { //on Shift+Tab -> focus last focusable element when focus moves out of modal event.preventDefault(); this.lastFocusable.focus(); } if( this.lastFocusable == document.activeElement && !event.shiftKey) { //on Tab -> focus first focusable element when focus moves out of modal event.preventDefault(); this.firstFocusable.focus(); } } Modal.prototype.getFocusableElements = function() { //get all focusable elements inside the modal var allFocusable = this.element.querySelectorAll(focusableElString); this.getFirstVisible(allFocusable); this.getLastVisible(allFocusable); this.getFirstFocusable(); }; Modal.prototype.getFirstVisible = function(elements) { //get first visible focusable element inside the modal for(var i = 0; i < elements.length; i++) { if( isVisible(elements[i]) ) { this.firstFocusable = elements[i]; break; } } }; Modal.prototype.getLastVisible = function(elements) { //get last visible focusable element inside the modal for(var i = elements.length - 1; i >= 0; i--) { if( isVisible(elements[i]) ) { this.lastFocusable = elements[i]; break; } } }; Modal.prototype.getFirstFocusable = function() { if(!this.modalFocus || !Element.prototype.matches) { this.moveFocusEl = this.firstFocusable; return; } var containerIsFocusable = this.modalFocus.matches(focusableElString); if(containerIsFocusable) { this.moveFocusEl = this.modalFocus; } else { this.moveFocusEl = false; var elements = this.modalFocus.querySelectorAll(focusableElString); for(var i = 0; i < elements.length; i++) { if( isVisible(elements[i]) ) { this.moveFocusEl = elements[i]; break; } } if(!this.moveFocusEl) this.moveFocusEl = this.firstFocusable; } }; Modal.prototype.emitModalEvents = function(eventName) { var event = new CustomEvent(eventName, {detail: this.selectedTrigger}); this.element.dispatchEvent(event); }; function isVisible(element) { return element.offsetWidth || element.offsetHeight || element.getClientRects().length; }; window.Modal = Modal; //initialize the Modal objects var modals = document.getElementsByClassName('js-modal'); // generic focusable elements string selector var focusableElString = '[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'; if( modals.length > 0 ) { var modalArrays = []; for( var i = 0; i < modals.length; i++) { (function(i){modalArrays.push(new Modal(modals[i]));})(i); } window.addEventListener('keydown', function(event){ //close modal window on esc if(event.keyCode && event.keyCode == 27 || event.key && event.key.toLowerCase() == 'escape') { for( var i = 0; i < modalArrays.length; i++) { (function(i){modalArrays[i].closeModal();})(i); }; } }); } }()); (function() { var SwipeContent = function(element) { this.element = element; this.delta = [false, false]; this.dragging = false; this.intervalId = false; initSwipeContent(this); }; function initSwipeContent(content) { content.element.addEventListener('mousedown', handleEvent.bind(content)); content.element.addEventListener('touchstart', handleEvent.bind(content), {passive: true}); }; function initDragging(content) { //add event listeners content.element.addEventListener('mousemove', handleEvent.bind(content)); content.element.addEventListener('touchmove', handleEvent.bind(content), {passive: true}); content.element.addEventListener('mouseup', handleEvent.bind(content)); content.element.addEventListener('mouseleave', handleEvent.bind(content)); content.element.addEventListener('touchend', handleEvent.bind(content)); }; function cancelDragging(content) { //remove event listeners if(content.intervalId) { (!window.requestAnimationFrame) ? clearInterval(content.intervalId) : window.cancelAnimationFrame(content.intervalId); content.intervalId = false; } content.element.removeEventListener('mousemove', handleEvent.bind(content)); content.element.removeEventListener('touchmove', handleEvent.bind(content)); content.element.removeEventListener('mouseup', handleEvent.bind(content)); content.element.removeEventListener('mouseleave', handleEvent.bind(content)); content.element.removeEventListener('touchend', handleEvent.bind(content)); }; function handleEvent(event) { switch(event.type) { case 'mousedown': case 'touchstart': startDrag(this, event); break; case 'mousemove': case 'touchmove': drag(this, event); break; case 'mouseup': case 'mouseleave': case 'touchend': endDrag(this, event); break; } }; function startDrag(content, event) { content.dragging = true; // listen to drag movements initDragging(content); content.delta = [parseInt(unify(event).clientX), parseInt(unify(event).clientY)]; // emit drag start event emitSwipeEvents(content, 'dragStart', content.delta, event.target); }; function endDrag(content, event) { cancelDragging(content); // credits: https://css-tricks.com/simple-swipe-with-vanilla-javascript/ var dx = parseInt(unify(event).clientX), dy = parseInt(unify(event).clientY); // check if there was a left/right swipe if(content.delta && (content.delta[0] || content.delta[0] === 0)) { var s = getSign(dx - content.delta[0]); if(Math.abs(dx - content.delta[0]) > 30) { (s < 0) ? emitSwipeEvents(content, 'swipeLeft', [dx, dy]) : emitSwipeEvents(content, 'swipeRight', [dx, dy]); } content.delta[0] = false; } // check if there was a top/bottom swipe if(content.delta && (content.delta[1] || content.delta[1] === 0)) { var y = getSign(dy - content.delta[1]); if(Math.abs(dy - content.delta[1]) > 30) { (y < 0) ? emitSwipeEvents(content, 'swipeUp', [dx, dy]) : emitSwipeEvents(content, 'swipeDown', [dx, dy]); } content.delta[1] = false; } // emit drag end event emitSwipeEvents(content, 'dragEnd', [dx, dy]); content.dragging = false; }; function drag(content, event) { if(!content.dragging) return; // emit dragging event with coordinates (!window.requestAnimationFrame) ? content.intervalId = setTimeout(function(){emitDrag.bind(content, event);}, 250) : content.intervalId = window.requestAnimationFrame(emitDrag.bind(content, event)); }; function emitDrag(event) { emitSwipeEvents(this, 'dragging', [parseInt(unify(event).clientX), parseInt(unify(event).clientY)]); }; function unify(event) { // unify mouse and touch events return event.changedTouches ? event.changedTouches[0] : event; }; function emitSwipeEvents(content, eventName, detail, el) { var trigger = false; if(el) trigger = el; // emit event with coordinates var event = new CustomEvent(eventName, {detail: {x: detail[0], y: detail[1], origin: trigger}}); content.element.dispatchEvent(event); }; function getSign(x) { if(!Math.sign) { return ((x > 0) - (x < 0)) || +x; } else { return Math.sign(x); } }; window.SwipeContent = SwipeContent; //initialize the SwipeContent objects var swipe = document.getElementsByClassName('js-swipe-content'); if( swipe.length > 0 ) { for( var i = 0; i < swipe.length; i++) { (function(i){new SwipeContent(swipe[i]);})(i); } } }()); (function() { var Sidebar = function(element) { this.element = element; this.triggers = document.querySelectorAll('[aria-controls="'+this.element.getAttribute('id')+'"]'); this.firstFocusable = null; this.lastFocusable = null; this.selectedTrigger = null; this.showClass = "sidebar--is-visible"; this.staticClass = "sidebar--static"; this.customStaticClass = ""; this.readyClass = "sidebar--loaded"; this.contentReadyClass = "sidebar-loaded:show"; this.layout = false; // this will be static or mobile this.preventScrollEl = getPreventScrollEl(this); getCustomStaticClass(this); // custom classes for static version initSidebar(this); }; function getPreventScrollEl(element) { var scrollEl = false; var querySelector = element.element.getAttribute('data-sidebar-prevent-scroll'); if(querySelector) scrollEl = document.querySelector(querySelector); return scrollEl; }; function getCustomStaticClass(element) { var customClasses = element.element.getAttribute('data-static-class'); if(customClasses) element.customStaticClass = ' '+customClasses; }; function initSidebar(sidebar) { initSidebarResize(sidebar); // handle changes in layout -> mobile to static and viceversa if ( sidebar.triggers ) { // open sidebar when clicking on trigger buttons - mobile layout only for(var i = 0; i < sidebar.triggers.length; i++) { sidebar.triggers[i].addEventListener('click', function(event) { event.preventDefault(); toggleSidebar(sidebar, event.target); }); } } // use the 'openSidebar' event to trigger the sidebar sidebar.element.addEventListener('openSidebar', function(event) { toggleSidebar(sidebar, event.detail); }); }; function toggleSidebar(sidebar, target) { if(Util.hasClass(sidebar.element, sidebar.showClass)) { sidebar.selectedTrigger = target; closeSidebar(sidebar); return; } sidebar.selectedTrigger = target; showSidebar(sidebar); initSidebarEvents(sidebar); }; function showSidebar(sidebar) { // mobile layout only Util.addClass(sidebar.element, sidebar.showClass); getFocusableElements(sidebar); Util.moveFocus(sidebar.element); // change the overflow of the preventScrollEl if(sidebar.preventScrollEl) sidebar.preventScrollEl.style.overflow = 'hidden'; }; function closeSidebar(sidebar) { // mobile layout only Util.removeClass(sidebar.element, sidebar.showClass); sidebar.firstFocusable = null; sidebar.lastFocusable = null; if(sidebar.selectedTrigger) sidebar.selectedTrigger.focus(); sidebar.element.removeAttribute('tabindex'); //remove listeners cancelSidebarEvents(sidebar); // change the overflow of the preventScrollEl if(sidebar.preventScrollEl) sidebar.preventScrollEl.style.overflow = ''; }; function initSidebarEvents(sidebar) { // mobile layout only //add event listeners sidebar.element.addEventListener('keydown', handleEvent.bind(sidebar)); sidebar.element.addEventListener('click', handleEvent.bind(sidebar)); }; function cancelSidebarEvents(sidebar) { // mobile layout only //remove event listeners sidebar.element.removeEventListener('keydown', handleEvent.bind(sidebar)); sidebar.element.removeEventListener('click', handleEvent.bind(sidebar)); }; function handleEvent(event) { // mobile layout only switch(event.type) { case 'click': { initClick(this, event); } case 'keydown': { initKeyDown(this, event); } } }; function initKeyDown(sidebar, event) { // mobile layout only if( event.keyCode && event.keyCode == 27 || event.key && event.key == 'Escape' ) { //close sidebar window on esc closeSidebar(sidebar); } else if( event.keyCode && event.keyCode == 9 || event.key && event.key == 'Tab' ) { //trap focus inside sidebar trapFocus(sidebar, event); } }; function initClick(sidebar, event) { // mobile layout only //close sidebar when clicking on close button or sidebar bg layer if( !event.target.closest('.js-sidebar__close-btn') && !Util.hasClass(event.target, 'js-sidebar') ) return; event.preventDefault(); closeSidebar(sidebar); }; function trapFocus(sidebar, event) { // mobile layout only if( sidebar.firstFocusable == document.activeElement && event.shiftKey) { //on Shift+Tab -> focus last focusable element when focus moves out of sidebar event.preventDefault(); sidebar.lastFocusable.focus(); } if( sidebar.lastFocusable == document.activeElement && !event.shiftKey) { //on Tab -> focus first focusable element when focus moves out of sidebar event.preventDefault(); sidebar.firstFocusable.focus(); } }; function initSidebarResize(sidebar) { // custom event emitted when window is resized - detect only if the sidebar--static@{breakpoint} class was added var beforeContent = getComputedStyle(sidebar.element, ':before').getPropertyValue('content'); if(beforeContent && beforeContent !='' && beforeContent !='none') { checkSidebarLayout(sidebar); sidebar.element.addEventListener('update-sidebar', function(event){ checkSidebarLayout(sidebar); }); } // check if there a main element to show var mainContent = document.getElementsByClassName(sidebar.contentReadyClass); if(mainContent.length > 0) Util.removeClass(mainContent[0], sidebar.contentReadyClass); Util.addClass(sidebar.element, sidebar.readyClass); }; function checkSidebarLayout(sidebar) { var layout = getComputedStyle(sidebar.element, ':before').getPropertyValue('content').replace(/\'|"/g, ''); if(layout == sidebar.layout) return; sidebar.layout = layout; if(layout != 'static') Util.addClass(sidebar.element, 'rz4-hide'); Util.toggleClass(sidebar.element, sidebar.staticClass + sidebar.customStaticClass, layout == 'static'); if(layout != 'static') setTimeout(function(){Util.removeClass(sidebar.element, 'rz4-hide')}); // reset element role (layout == 'static') ? sidebar.element.removeAttribute('role', 'alertdialog') : sidebar.element.setAttribute('role', 'alertdialog'); // reset mobile behaviour if(layout == 'static' && Util.hasClass(sidebar.element, sidebar.showClass)) closeSidebar(sidebar); }; function getFocusableElements(sidebar) { //get all focusable elements inside the drawer 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'); getFirstVisible(sidebar, allFocusable); getLastVisible(sidebar, allFocusable); }; function getFirstVisible(sidebar, elements) { //get first visible focusable element inside the sidebar for(var i = 0; i < elements.length; i++) { if( elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length ) { sidebar.firstFocusable = elements[i]; return true; } } }; function getLastVisible(sidebar, elements) { //get last visible focusable element inside the sidebar for(var i = elements.length - 1; i >= 0; i--) { if( elements[i].offsetWidth || elements[i].offsetHeight || elements[i].getClientRects().length ) { sidebar.lastFocusable = elements[i]; return true; } } }; window.Sidebar = Sidebar; //initialize the Sidebar objects var sidebar = document.getElementsByClassName('js-sidebar'); if( sidebar.length > 0 ) { for( var i = 0; i < sidebar.length; i++) { (function(i){new Sidebar(sidebar[i]);})(i); } // switch from mobile to static layout var customEvent = new CustomEvent('update-sidebar'); window.addEventListener('resize', function(event){ (!window.requestAnimationFrame) ? setTimeout(function(){resetLayout();}, 250) : window.requestAnimationFrame(resetLayout); }); (window.requestAnimationFrame) // init sidebar layout ? window.requestAnimationFrame(resetLayout) : resetLayout(); function resetLayout() { for( var i = 0; i < sidebar.length; i++) { (function(i){sidebar[i].dispatchEvent(customEvent)})(i); }; }; } }()); (function() { var MenuBar = function(element) { this.element = element; this.items = Util.getChildrenByClassName(this.element, 'menu-bar__item'); this.mobHideItems = this.element.getElementsByClassName('menu-bar__item--hide'); this.moreItemsTrigger = this.element.getElementsByClassName('js-menu-bar__trigger'); initMenuBar(this); }; function initMenuBar(menu) { setMenuTabIndex(menu); // set correct tabindexes for menu item initMenuBarMarkup(menu); // create additional markup checkMenuLayout(menu); // set menu layout Util.addClass(menu.element, 'menu-bar--loaded'); // reveal menu // custom event emitted when window is resized menu.element.addEventListener('update-menu-bar', function(event){ checkMenuLayout(menu); if(menu.menuInstance) menu.menuInstance.toggleMenu(false, false); // close dropdown }); // keyboard events // open dropdown when pressing Enter on trigger element if(menu.moreItemsTrigger.length > 0) { menu.moreItemsTrigger[0].addEventListener('keydown', function(event) { if( (event.keyCode && event.keyCode == 13) || (event.key && event.key.toLowerCase() == 'enter') ) { if(!menu.menuInstance) return; menu.menuInstance.selectedTrigger = menu.moreItemsTrigger[0]; menu.menuInstance.toggleMenu(!Util.hasClass(menu.subMenu, 'menu--is-visible'), true); } }); // close dropdown on esc menu.subMenu.addEventListener('keydown', function(event) { if((event.keyCode && event.keyCode == 27) || (event.key && event.key.toLowerCase() == 'escape')) { // close submenu on esc if(menu.menuInstance) menu.menuInstance.toggleMenu(false, true); } }); } // navigate menu items using left/right arrows menu.element.addEventListener('keydown', function(event) { if( (event.keyCode && event.keyCode == 39) || (event.key && event.key.toLowerCase() == 'arrowright') ) { navigateItems(menu.items, event, 'next'); } else if( (event.keyCode && event.keyCode == 37) || (event.key && event.key.toLowerCase() == 'arrowleft') ) { navigateItems(menu.items, event, 'prev'); } }); }; function setMenuTabIndex(menu) { // set tabindexes for the menu items to allow keyboard navigation var nextItem = false; for(var i = 0; i < menu.items.length; i++ ) { if(i == 0 || nextItem) menu.items[i].setAttribute('tabindex', '0'); else menu.items[i].setAttribute('tabindex', '-1'); if(i == 0 && menu.moreItemsTrigger.length > 0) nextItem = true; else nextItem = false; } }; function initMenuBarMarkup(menu) { if(menu.mobHideItems.length == 0 ) { // no items to hide on mobile - remove trigger if(menu.moreItemsTrigger.length > 0) menu.element.removeChild(menu.moreItemsTrigger[0]); return; } if(menu.moreItemsTrigger.length == 0) return; // create the markup for the Menu element var content = ''; menu.menuControlId = 'submenu-bar-'+Date.now(); for(var i = 0; i < menu.mobHideItems.length; i++) { var item = menu.mobHideItems[i].cloneNode(true), svg = item.getElementsByTagName('svg')[0], label = item.getElementsByClassName('menu-bar__label')[0]; svg.setAttribute('class', 'icon menu__icon'); content = content + '