1 2// utility functions 3if (!Util) function Util() { }; 4 5Util.hasClass = function (el, className) { 6 return el.classList.contains(className); 7}; 8 9Util.addClass = function (el, className) { 10 var classList = className.split(' '); 11 el.classList.add(classList[0]); 12 if (classList.length > 1) Util.addClass(el, classList.slice(1).join(' ')); 13}; 14 15Util.removeClass = function (el, className) { 16 var classList = className.split(' '); 17 el.classList.remove(classList[0]); 18 if (classList.length > 1) Util.removeClass(el, classList.slice(1).join(' ')); 19}; 20 21Util.toggleClass = function (el, className, bool) { 22 if (bool) Util.addClass(el, className); 23 else Util.removeClass(el, className); 24}; 25 26Util.setAttributes = function (el, attrs) { 27 for (var key in attrs) { 28 el.setAttribute(key, attrs[key]); 29 } 30}; 31 32Util.moveFocus = function (element) { 33 if (!element) element = document.getElementsByTagName('body')[0]; 34 element.focus(); 35 if (document.activeElement !== element) { 36 element.setAttribute('tabindex', '-1'); 37 element.focus(); 38 } 39}; 40(function () { 41 var Popover = function (element) { 42 this.element = element; 43 this.elementId = this.element.getAttribute('id'); 44 this.trigger = document.querySelectorAll('[aria-controls="' + this.elementId + '"]'); 45 this.selectedTrigger = false; 46 this.popoverVisibleClass = 'popover--is-visible'; 47 this.selectedTriggerClass = 'popover-control--active'; 48 this.popoverIsOpen = false; 49 // focusable elements 50 this.firstFocusable = false; 51 this.lastFocusable = false; 52 // position target - position tooltip relative to a specified element 53 this.positionTarget = getPositionTarget(this); 54 // gap between element and viewport - if there's max-height 55 this.viewportGap = parseInt(getComputedStyle(this.element).getPropertyValue('--popover-viewport-gap')) || 20; 56 initPopover(this); 57 initPopoverEvents(this); 58 }; 59 60 // public methods 61 Popover.prototype.togglePopover = function (bool, moveFocus) { 62 togglePopover(this, bool, moveFocus); 63 }; 64 65 Popover.prototype.checkPopoverClick = function (target) { 66 checkPopoverClick(this, target); 67 }; 68 69 Popover.prototype.checkPopoverFocus = function () { 70 checkPopoverFocus(this); 71 }; 72 73 // private methods 74 function getPositionTarget(popover) { 75 // position tooltip relative to a specified element - if provided 76 var positionTargetSelector = popover.element.getAttribute('data-position-target'); 77 if (!positionTargetSelector) return false; 78 var positionTarget = document.querySelector(positionTargetSelector); 79 return positionTarget; 80 console.log("data-position-targeted") 81 }; 82 83 function initPopover(popover) { 84 // reset popover position 85 initPopoverPosition(popover); 86 // init aria-labels 87 for (var i = 0; i < popover.trigger.length; i++) { 88 Util.setAttributes(popover.trigger[i], { 'aria-expanded': 'false', 'aria-haspopup': 'true' }); 89 } 90 console.log("pop over position reset"); 91 }; 92 93 function initPopoverEvents(popover) { 94 for (var i = 0; i < popover.trigger.length; i++) { 95 (function (i) { 96 popover.trigger[i].addEventListener('click', function (event) { 97 event.preventDefault(); 98 console.log("Pop Over triggered on click"); 99 // if the popover had been previously opened by another trigger element -> close it first and reopen in the right position 100 if (Util.hasClass(popover.element, popover.popoverVisibleClass) && popover.selectedTrigger != popover.trigger[i]) { 101 togglePopover(popover, false, false); // close menu 102 } 103 // toggle popover 104 popover.selectedTrigger = popover.trigger[i]; 105 togglePopover(popover, !Util.hasClass(popover.element, popover.popoverVisibleClass), true); 106 }); 107 })(i); 108 } 109 110 // trap focus 111 popover.element.addEventListener('keydown', function (event) { 112 if (event.keyCode && event.keyCode == 9 || event.key && event.key == 'Tab') { 113 //trap focus inside popover 114 trapFocus(popover, event); 115 } 116 }); 117 118 // custom events -> open/close popover 119 popover.element.addEventListener('openPopover', function (event) { 120 togglePopover(popover, true); 121 }); 122 123 popover.element.addEventListener('closePopover', function (event) { 124 togglePopover(popover, false, event.detail); 125 }); 126 }; 127 128 function togglePopover(popover, bool, moveFocus) { 129 // toggle popover visibility 130 Util.toggleClass(popover.element, popover.popoverVisibleClass, bool); 131 popover.popoverIsOpen = bool; 132 if (bool) { 133 popover.selectedTrigger.setAttribute('aria-expanded', 'true'); 134 getFocusableElements(popover); 135 // move focus 136 focusPopover(popover); 137 popover.element.addEventListener("transitionend", function (event) { focusPopover(popover); }, { once: true });
138 // position the popover element 139 positionPopover(popover); 140 // add class to popover trigger 141 Util.addClass(popover.selectedTrigger, popover.selectedTriggerClass); 142 } else if (popover.selectedTrigger) { 143 popover.selectedTrigger.setAttribute('aria-expanded', 'false'); 144 if (moveFocus) Util.moveFocus(popover.selectedTrigger); 145 // remove class from menu trigger 146 Util.removeClass(popover.selectedTrigger, popover.selectedTriggerClass); 147 popover.selectedTrigger = false; 148 } 149 }; 150 151 function focusPopover(popover) { 152 if (popover.firstFocusable) { 153 popover.firstFocusable.focus(); 154 } else { 155 Util.moveFocus(popover.element); 156 } 157 }; 158 159 function positionPopover(popover) { 160 // reset popover position 161 resetPopoverStyle(popover); 162 var selectedTriggerPosition = (popover.positionTarget) ? popover.positionTarget.getBoundingClientRect() : popover.selectedTrigger.getBoundingClientRect(); 163 164 var menuOnTop = (window.innerHeight - selectedTriggerPosition.bottom) < selectedTriggerPosition.top; 165 166 var left = selectedTriggerPosition.left, 167 right = (window.innerWidth - selectedTriggerPosition.right), 168 isRight = (window.innerWidth < selectedTriggerPosition.left + popover.element.offsetWidth); 169 170 var horizontal = isRight ? 'right: ' + right + 'px;' : 'left: ' + left + 'px;', 171 vertical = menuOnTop 172 ? 'bottom: ' + (window.innerHeight - selectedTriggerPosition.top) + 'px;' 173 : 'top: ' + selectedTriggerPosition.bottom + 'px;'; 174 // check right position is correct -> otherwise set left to 0 175 if (isRight && (right + popover.element.offsetWidth) > window.innerWidth) horizontal = 'left: ' + parseInt((window.innerWidth - popover.element.offsetWidth) / 2) + 'px;'; 176 // check if popover needs a max-height (user will scroll inside the popover) 177 var maxHeight = menuOnTop ? selectedTriggerPosition.top - popover.viewportGap : window.innerHeight - selectedTriggerPosition.bottom - popover.viewportGap; 178 179 var initialStyle = popover.element.getAttribute('style'); 180 if (!initialStyle) initialStyle = ''; 181 popover.element.setAttribute('style', initialStyle + horizontal + vertical + 'max-height:' + Math.floor(maxHeight) + 'px;'); 182 }; 183 184 function resetPopoverStyle(popover) { 185 // remove popover inline style before appling new style 186 popover.element.style.maxHeight = ''; 187 popover.element.style.top = ''; 188 popover.element.style.bottom = ''; 189 popover.element.style.left = ''; 190 popover.element.style.right = ''; 191 }; 192 193 function initPopoverPosition(popover) { 194 // make sure the popover does not create any scrollbar 195 popover.element.style.top = '0px'; 196 popover.element.style.left = '0px'; 197 }; 198 199 function checkPopoverClick(popover, target) { 200 // close popover when clicking outside it 201 if (!popover.popoverIsOpen) return; 202 if (!popover.element.contains(target) && !target.closest('[aria-controls="' + popover.elementId + '"]')) togglePopover(popover, false); 203 }; 204 205 function checkPopoverFocus(popover) { 206 // on Esc key -> close popover if open and move focus (if focus was inside popover) 207 if (!popover.popoverIsOpen) return; 208 var popoverParent = document.activeElement.closest('.js-popover'); 209 togglePopover(popover, false, popoverParent); 210 }; 211 212 function getFocusableElements(popover) { 213 //get all focusable elements inside the popover 214 var allFocusable = popover.element.querySelectorAll(focusableElString); 215 getFirstVisible(popover, allFocusable); 216 getLastVisible(popover, allFocusable); 217 }; 218 219 function getFirstVisible(popover, elements) { 220 //get first visible focusable element inside the popover 221 for (var i = 0; i < elements.length; i++) { 222 if (isVisible(elements[i])) { 223 popover.firstFocusable = elements[i]; 224 break; 225 } 226 } 227 }; 228 229 function getLastVisible(popover, elements) { 230 //get last visible focusable element inside the popover 231 for (var i = elements.length - 1; i >= 0; i--) { 232 if (isVisible(elements[i])) { 233 popover.lastFocusable = elements[i]; 234 break; 235 } 236 } 237 }; 238 239 function trapFocus(popover, event) { 240 if (popover.firstFocusable == document.activeElement && event.shiftKey) { 241 //on Shift+Tab ->
241 focus last focusable element when focus moves out of popover 242 event.preventDefault(); 243 popover.lastFocusable.focus(); 244 } 245 if (popover.lastFocusable == document.activeElement && !event.shiftKey) { 246 //on Tab -> focus first focusable element when focus moves out of popover 247 event.preventDefault(); 248 popover.firstFocusable.focus(); 249 } 250 }; 251 252 function isVisible(element) { 253 // check if element is visible 254 return element.offsetWidth || element.offsetHeight || element.getClientRects().length; 255 }; 256 257 window.Popover = Popover; 258 259 //initialize the Popover objects 260 var popovers = document.getElementsByClassName('js-popover'); 261 // generic focusable elements string selector 262 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'; 263 264 if (popovers.length > 0) { 265 var popoversArray = []; 266 var scrollingContainers = []; 267 for (var i = 0; i < popovers.length; i++) { 268 (function (i) { 269 popoversArray.push(new Popover(popovers[i])); 270 var scrollableElement = popovers[i].getAttribute('data-scrollable-element'); 271 if (scrollableElement && !scrollingContainers.includes(scrollableElement)) scrollingContainers.push(scrollableElement); 272 })(i); 273 } 274 275 // listen for key events 276 window.addEventListener('keyup', function (event) { 277 if (event.keyCode && event.keyCode == 27 || event.key && event.key.toLowerCase() == 'escape') { 278 // close popover on 'Esc' 279 popoversArray.forEach(function (element) { 280 element.checkPopoverFocus(); 281 }); 282 } 283 }); 284 // close popover when clicking outside it 285 window.addEventListener('click', function (event) { 286 popoversArray.forEach(function (element) { 287 element.checkPopoverClick(event.target); 288 }); 289 }); 290 // on resize -> close all popover elements 291 window.addEventListener('resize', function (event) { 292 popoversArray.forEach(function (element) { 293 element.togglePopover(false, false); 294 }); 295 }); 296 // on scroll -> close all popover elements 297 window.addEventListener('scroll', function (event) { 298 popoversArray.forEach(function (element) { 299 if (element.popoverIsOpen) element.togglePopover(false, false); 300 }); 301 }); 302 // take into account additinal scrollable containers 303 for (var j = 0; j < scrollingContainers.length; j++) { 304 var scrollingContainer = document.querySelector(scrollingContainers[j]); 305 if (scrollingContainer) { 306 scrollingContainer.addEventListener('scroll', function (event) { 307 popoversArray.forEach(function (element) { 308 if (element.popoverIsOpen) element.togglePopover(false, false); 309 }); 310 }); 311 } 312 } 313 } 314})();
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.