PageSourceSearch

https://portal.haulerhub.com/assets/js/hhPopover.js

js haulerhub.com collected 2026-09-24 12:35:36 UTC 13,513 bytes, 314 lines download raw bytes

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.