1/**! 2 * @fileOverview Kickass library to create and place poppers near their reference elements. 3 * @version 1.0.4 4 * @license 5 * Copyright (c) 2016 Federico Zivolo and contributors 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a copy 8 * of this software and associated documentation files (the "Software"), to deal 9 * in the Software without restriction, including without limitation the rights 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 * copies of the Software, and to permit persons to whom the Software is 12 * furnished to do so, subject to the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included in all 15 * copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 * SOFTWARE. 24 */ 25(function (global, factory) { 26 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 27 typeof define === 'function' && define.amd ? define(factory) : 28 (global.Popper = factory()); 29}(this, (function () { 'use strict'; 30 31/** 32 * Returns the offset parent of the given element 33 * @method 34 * @memberof Popper.Utils 35 * @argument {Element} element 36 * @returns {Element} offset parent 37 */ 38function getOffsetParent(element) { 39 // NOTE: 1 DOM access here 40 const offsetParent = element.offsetParent; 41 const nodeName = offsetParent && offsetParent.nodeName; 42 43 if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') { 44 return window.document.documentElement; 45 } 46 47 return offsetParent; 48} 49 50/** 51 * Get CSS computed property of the given element 52 * @method 53 * @memberof Popper.Utils 54 * @argument {Eement} element 55 * @argument {String} property 56 */ 57function getStyleComputedProperty(element, property) { 58 if (element.nodeType !== 1) { 59 return []; 60 } 61 // NOTE: 1 DOM access here 62 const css = window.getComputedStyle(element, null); 63 return property ? css[property] : css; 64} 65 66/** 67 * Returns the parentNode or the host of the element 68 * @method 69 * @memberof Popper.Utils 70 * @argument {Element} element 71 * @returns {Element} parent 72 */ 73function getParentNode(element) { 74 if (element.nodeName === 'HTML') { 75 return element; 76 } 77 return element.parentNode || element.host; 78} 79 80/** 81 * Returns the scrolling parent of the given element 82 * @method 83 * @memberof Popper.Utils 84 * @argument {Element} element 85 * @returns {Element} offset parent 86 */ 87function getScrollParent(element) { 88 // Return body, `getScroll` will take care to get the correct `scrollTop` from it 89 if (!element || ['HTML', 'BODY', '#document'].indexOf(element.nodeName) !== -1) { 90 return window.document.body; 91 } 92 93 // Firefox want us to check `-x` and `-y` variations as well 94 const { overflow, overflowX, overflowY } = getStyleComputedProperty(element); 95 if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) { 96 return element; 97 } 98 99 return getScrollParent(getParentNode(element)); 100} 101 102function getWindowSizes() { 103 const body = window.document.body; 104 const html = window.document.documentElement; 105 return { 106 height: Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight), 107 width: Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth) 108 }; 109} 110 111/** 112 * Get the position of the given element, relative to its offset parent 113 * @method 114 * @memberof Popper.Utils 115 * @param {Element} element 116 * @return {Object} position - Coordinates of the element and its `scrollTop` 117 */ 118function getOffsetRect(element) { 119 let elementRect; 120 if (element.nodeName === 'HTML') { 121 const { width, height } = getWindowSizes(); 122 elementRect = { 123 width, 124 height, 125 left: 0, 126 top: 0 127 }; 128 } else { 129 elementRect = { 130 width: element.offsetWidth, 131 height: element.offsetHeight, 132 left: element.offsetLeft, 133 top: element.offsetTop 134 }; 135 } 136 137 elementRect.right = elementRect.left + elementRect.width; 138 elementRect.bottom = elementRect.top + elementRect.height; 139 140 // position 141 return elementRect; 142} 143
144/** 145 * Check if the given element is fixed or is inside a fixed parent 146 * @method 147 * @memberof Popper.Utils 148 * @argument {Element} element 149 * @argument {Element} customContainer 150 * @returns {Boolean} answer to "isFixed?" 151 */ 152function isFixed(element) { 153 const nodeName = element.nodeName; 154 if (nodeName === 'BODY' || nodeName === 'HTML') { 155 return false; 156 } 157 if (getStyleComputedProperty(element, 'position') === 'fixed') { 158 return true; 159 } 160 return isFixed(getParentNode(element)); 161} 162 163/** 164 * Helper used to get the position which will be applied to the popper 165 * @method 166 * @memberof Popper.Utils 167 * @param config {HTMLElement} popper element 168 * @returns {HTMLElement} reference element 169 */ 170function getPosition(element) { 171 const container = getOffsetParent(element); 172 173 // Decide if the popper will be fixed 174 // If the reference element is inside a fixed context, the popper will be fixed as well to allow them to scroll together 175 const isParentFixed = isFixed(container); 176 return isParentFixed ? 'fixed' : 'absolute'; 177} 178 179/** 180 * Get bounding client rect of given element 181 * @method 182 * @memberof Popper.Utils 183 * @param {HTMLElement} element 184 * @return {Object} client rect 185 */ 186function getBoundingClientRect(element) { 187 const isIE10 = navigator.appVersion.indexOf('MSIE 10') !== -1; 188 let rect; 189 190 // IE10 10 FIX: Please, don't ask, the element isn't 191 // considered in DOM in some circumstances... 192 // This isn't reproducible in IE10 compatibility mode of IE11 193 if (isIE10) { 194 try { 195 rect = element.getBoundingClientRect(); 196 } catch (err) { 197 rect = {}; 198 } 199 } else { 200 rect = element.getBoundingClientRect(); 201 } 202 203 const result = { 204 left: rect.left, 205 top: rect.top, 206 right: rect.right, 207 bottom: rect.bottom, 208 width: rect.right - rect.left, 209 height: rect.bottom - rect.top 210 }; 211 212 // IE10 FIX: `getBoundingClientRect`, when executed on `documentElement` 213 // will not take in account the `scrollTop` and `scrollLeft` 214 if (element.nodeName === 'HTML' && isIE10) { 215 const { scrollTop, scrollLeft } = window.document.documentElement; 216 result.top -= scrollTop; 217 result.bottom -= scrollTop; 218 result.left -= scrollLeft; 219 result.right -= scrollLeft; 220 } 221 222 // subtract scrollbar size from sizes 223 let horizScrollbar = rect.width - (element.clientWidth || rect.right - rect.left); 224 let vertScrollbar = rect.height - (element.clientHeight || rect.bottom - rect.top); 225 226 // if an hypothetical scrollbar is detected, we must be sure it's not a `border` 227 // we make this check conditional for performance reasons 228 if (horizScrollbar || vertScrollbar) { 229 const styles = getStyleComputedProperty(element); 230 horizScrollbar -= Number(styles.borderLeftWidth.split('px')[0]) + Number(styles.borderRightWidth.split('px')[0]); 231 vertScrollbar -= Number(styles.borderTopWidth.split('px')[0]) + Number(styles.borderBottomWidth.split('px')[0]); 232 } 233 234 result.right -= horizScrollbar; 235 result.width -= horizScrollbar; 236 result.bottom -= vertScrollbar; 237 result.height -= vertScrollbar; 238 239 return result; 240} 241 242function getScroll(element, side = 'top') { 243 const upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft'; 244 const nodeName = element.nodeName; 245 246 if (nodeName === 'BODY' || nodeName === 'HTML') { 247 const html = window.document.documentElement; 248 const scrollingElement = window.document.scrollingElement || html; 249 return scrollingElement[upperSide]; 250 } 251 252 return element[upperSide]; 253} 254 255/** 256 * Given an element and one of its parents, return the offset 257 * @method 258 * @memberof Popper.Utils 259 * @param {HTMLElement} element 260 * @param {HTMLElement} parent 261 * @return {Object} rect 262 */ 263function getOffsetRectRelativeToCustomParent(element, parent, fixed = false, transformed = false) { 264 const scrollParent = getScrollParent(parent); 265 const elementRect = getBoundingClientRect(element); 266 const parentRect = getBoundingClientRect(parent); 267 268 const rect = { 269 top: elementRect.top - parentRect.top, 270 left: elementRect.left - parentRect.left, 271 bottom: elementRect.top - parentRect.top + elementRect.height, 272 right: elementRect.left - parentRect.left + elementRect.width, 273 width: elementRect.width, 274 height: elementRect.height 275 }; 276 277 if (fixed && !transformed) { 278 const scrollTop = getScroll(scrollParent, 'top'); 279 const scrollLeft = getScroll(scrollParent, 'left'); 280 rect.top -= scrollTop; 281 rect.bottom -= scrollTop; 282 rect.left -= scrollLeft; 283 rect.right -= scrollLeft; 284 } 285 // When a popper doesn't have any positioned or scrollable parents, `offsetParent.contains(scrollParent)`
286 // will return a "false positive". This is happening because `getOffsetParent` returns `html` node, 287 // and `scrollParent` is the `body` node. Hence the additional check. 288 else if (getOffsetParent(element).contains(scrollParent) && scrollParent.nodeName !== 'BODY') { 289 const scrollTop = getScroll(parent, 'top'); 290 const scrollLeft = getScroll(parent, 'left'); 291 rect.top += scrollTop; 292 rect.bottom += scrollTop; 293 rect.left += scrollLeft; 294 rect.right += scrollLeft; 295 } 296 297 // subtract borderTopWidth and borderTopWidth from final result 298 const styles = getStyleComputedProperty(parent); 299 const borderTopWidth = Number(styles.borderTopWidth.split('px')[0]); 300 const borderLeftWidth = Number(styles.borderLeftWidth.split('px')[0]); 301 302 rect.top -= borderTopWidth; 303 rect.bottom -= borderTopWidth; 304 rect.left -= borderLeftWidth; 305 rect.right -= borderLeftWidth; 306 307 return rect; 308} 309 310function getTotalScroll(element, side = 'top') { 311 const scrollParent = getScrollParent(element); 312 const scroll = getScroll(scrollParent, side); 313 314 if (['BODY', 'HTML'].indexOf(scrollParent.nodeName) === -1) { 315 return scroll + getTotalScroll(getParentNode(scrollParent), side); 316 } 317 return scroll; 318} 319 320/** 321 * Computed the boundaries limits and return them 322 * @method 323 * @memberof Popper.Utils 324 * @param {Object} data - Object containing the property "offsets" generated by `_getOffsets` 325 * @param {Number} padding - Boundaries padding 326 * @param {Element} boundariesElement - Element used to define the boundaries 327 * @returns {Object} Coordinates of the boundaries 328 */ 329function getBoundaries(popper, padding, boundariesElement) { 330 // NOTE: 1 DOM access here 331 let boundaries = { top: 0, left: 0 }; 332 const offsetParent = getOffsetParent(popper); 333 334 // Handle viewport case 335 if (boundariesElement === 'viewport') { 336 const { left, top } = getOffsetRect(offsetParent); 337 const { clientWidth: width, clientHeight: height } = window.document.documentElement; 338 339 if (getPosition(popper) === 'fixed') { 340 boundaries.right = width; 341 boundaries.bottom = height; 342 } else { 343 const scrollLeft = getTotalScroll(popper, 'left'); 344 const scrollTop = getTotalScroll(popper, 'top'); 345 346 boundaries = { 347 top: 0 - top, 348 right: width - left + scrollLeft, 349 bottom: height - top + scrollTop, 350 left: 0 - left 351 }; 352 } 353 } 354 // Handle other cases based on DOM element used as boundaries 355 else { 356 let boundariesNode; 357 if (boundariesElement === 'scrollParent') { 358 boundariesNode = getScrollParent(getParentNode(popper)); 359 } else if (boundariesElement === 'window') { 360 boundariesNode = window.document.body; 361 } else { 362 boundariesNode = boundariesElement; 363 } 364 365 // In case of BODY, we need a different computation 366 if (boundariesNode.nodeName === 'BODY') { 367 const { height, width } = getWindowSizes(); 368 boundaries.right = width; 369 boundaries.bottom = height; 370 } 371 // for all the other DOM elements, this one is good 372 else { 373 boundaries = getOffsetRectRelativeToCustomParent(boundariesNode, offsetParent, isFixed(popper)); 374 } 375 } 376 377 // Add paddings 378 boundaries.left += padding; 379 boundaries.top += padding; 380 boundaries.right -= padding; 381 boundaries.bottom -= padding; 382 383 return boundaries; 384} 385 386/** 387 * Utility used to transform the `auto` placement to the placement with more 388 * available space. 389 * @method 390 * @memberof Popper.Utils 391 * @argument {Object} data - The data object generated by update method 392 * @argument {Object} options - Modifiers configuration and options 393 * @returns {Object} The data object, properly modified 394 */ 395function computeAutoPlacement(placement, refRect, popper) { 396 if (placement.indexOf('auto') === -1) { 397 return placement; 398 } 399 400 const boundaries = getBoundaries(popper, 0, 'scrollParent'); 401 402 const sides = { 403 top: refRect.top - boundaries.top, 404 right: boundaries.right - refRect.right, 405 bottom: boundaries.bottom - refRect.bottom, 406 left: refRect.left - boundaries.left 407 }; 408 409 const computedPlacement = Object.keys(sides).sort((a, b) => sides[b] - sides[a])[0]; 410 const variation = placement.split('-')[1]; 411 412 return computedPlacement + (variation ? `-${ variation }` : ''); 413} 414 415const nativeHints = ['native code', '[object MutationObserverConstructor]' // for mobile safari iOS 9.0 416]; 417 418/** 419 * Determine if a function is implemented natively (as opposed to a polyfill). 420 * @argument {Function | undefined} fn the function to check 421 * @returns {boolean} 422 */ 423var isNative = (fn => nativeHints.some(hint => (fn || '').toString().indexOf(hint) > -1)); 424 425const longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox']; 426let timeoutDuration = 0; 427for (let i = 0; i < longerTimeoutBrowsers.length; i += 1) { 428 if (navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) { 429 timeoutDuration = 1; 430 break; 431 } 432} 433 434function microtaskDebounce(fn) { 435 let scheduled = false;
436 let i = 0; 437 let elem = document.createElement('span'); 438 439 // MutationObserver provides a mechanism for scheduling microtasks, which 440 // are scheduled *before* the next task. This gives us a way to debounce 441 // a function but ensure it's called *before* the next paint. 442 const observer = new MutationObserver(() => { 443 fn(); 444 scheduled = false; 445 }); 446 447 observer.observe(elem, { attributes: true }); 448 449 return () => { 450 if (!scheduled) { 451 scheduled = true; 452 elem.setAttribute('x-index', i); 453 i = i + 1; // don't use compund (+=) because it doesn't get optimized in V8 454 } 455 }; 456} 457 458function taskDebounce(fn) { 459 let scheduled = false; 460 return () => { 461 if (!scheduled) { 462 scheduled = true; 463 setTimeout(() => { 464 scheduled = false; 465 fn(); 466 }, timeoutDuration); 467 } 468 }; 469} 470 471// It's common for MutationObserver polyfills to be seen in the wild, however 472// these rely on Mutation Events which only occur when an element is connected 473// to the DOM. The algorithm used in this module does not use a connected element, 474// and so we must ensure that a *native* MutationObserver is available. 475const supportsNativeMutationObserver = isNative(window.MutationObserver); 476 477/** 478* Create a debounced version of a method, that's asynchronously deferred 479* but called in the minimum time possible. 480* 481* @method 482* @memberof Popper.Utils 483* @argument {Function} fn 484* @returns {Function} 485*/ 486var debounce = supportsNativeMutationObserver ? microtaskDebounce : taskDebounce; 487 488/** 489 * Mimics the `find` method of Array 490 * @method 491 * @memberof Popper.Utils 492 * @argument {Array} arr 493 * @argument prop 494 * @argument value 495 * @returns index or -1 496 */ 497function findIndex$1(arr, check) { 498 // use native find if supported 499 if (Array.prototype.find) { 500 return arr.find(check); 501 } 502 503 // use `filter` to obtain the same behavior of `find` 504 return arr.filter(check)[0]; 505} 506 507/** 508 * Return the index of the matching object 509 * @method 510 * @memberof Popper.Utils 511 * @argument {Array} arr 512 * @argument prop 513 * @argument value 514 * @returns index or -1 515 */ 516function findIndex(arr, prop, value) { 517 // use native findIndex if supported 518 if (Array.prototype.findIndex) { 519 return arr.findIndex(cur => cur[prop] === value); 520 } 521 522 // use `find` + `indexOf` if `findIndex` isn't supported 523 const match = findIndex$1(arr, obj => obj[prop] === value); 524 return arr.indexOf(match); 525} 526 527var _extends = Object.assign || function (target) { 528 for (var i = 1; i < arguments.length; i++) { 529 var source = arguments[i]; 530 531 for (var key in source) { 532 if (Object.prototype.hasOwnProperty.call(source, key)) { 533 target[key] = source[key]; 534 } 535 } 536 } 537 538 return target; 539}; 540 541/** 542 * Given the popper offsets, generate an output similar to getBoundingClientRect 543 * @method 544 * @memberof Popper.Utils 545 * @argument {Object} popperOffsets 546 * @returns {Object} ClientRect like output 547 */ 548function getClientRect(popperOffsets) { 549 return _extends({}, popperOffsets, { 550 right: popperOffsets.left + popperOffsets.width, 551 bottom: popperOffsets.top + popperOffsets.height 552 }); 553} 554 555/** 556 * Get the outer sizes of the given element (offset size + margins) 557 * @method 558 * @memberof Popper.Utils 559 * @argument {Element} element 560 * @returns {Object}
560 object containing width and height properties 561 */ 562function getOuterSizes(element) { 563 const styles = window.getComputedStyle(element); 564 const x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom); 565 const y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight); 566 const result = { 567 width: element.offsetWidth + y, 568 height: element.offsetHeight + x 569 }; 570 return result; 571} 572 573/** 574 * Get the opposite placement of the given one/ 575 * @method 576 * @memberof Popper.Utils 577 * @argument {String} placement 578 * @returns {String} flipped placement 579 */ 580function getOppositePlacement(placement) { 581 const hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' }; 582 return placement.replace(/left|right|bottom|top/g, matched => hash[matched]); 583} 584 585/** 586 * Get offsets to the popper 587 * @method 588 * @memberof Popper.Utils 589 * @param {Element} popper - the popper element 590 * @param {Element} reference - the reference element (the popper will be relative to this) 591 * @returns {Object} An object containing the offsets which will be applied to the popper 592 */ 593function getPopperOffsets(state, popper, referenceOffsets, placement) { 594 placement = placement.split('-')[0]; 595 596 // Get popper node sizes 597 const popperRect = getOuterSizes(popper); 598 599 // Add position, width and height to our offsets object 600 const popperOffsets = { 601 position: state.position, 602 width: popperRect.width, 603 height: popperRect.height 604 }; 605 606 // depending by the popper placement we have to compute its offsets slightly differently 607 const isHoriz = ['right', 'left'].indexOf(placement) !== -1; 608 const mainSide = isHoriz ? 'top' : 'left'; 609 const secondarySide = isHoriz ? 'left' : 'top'; 610 const measurement = isHoriz ? 'height' : 'width'; 611 const secondaryMeasurement = !isHoriz ? 'height' : 'width'; 612 613 popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2; 614 if (placement === secondarySide) { 615 popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement]; 616 } else { 617 popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)]; 618 } 619 620 return popperOffsets; 621} 622 623/** 624 * Get offsets to the reference element 625 * @method 626 * @memberof Popper.Utils 627 * @param {Object} state 628 * @param {Element} popper - the popper element 629 * @param {Element} reference - the reference element (the popper will be relative to this) 630 * @returns {Object} An object containing the offsets which will be applied to the popper 631 */ 632function getReferenceOffsets(state, popper, reference) { 633 const isParentFixed = state.position === 'fixed'; 634 const isParentTransformed = state.isParentTransformed; 635 const offsetParent = getOffsetParent(isParentFixed && isParentTransformed ? reference : popper); 636 637 return getOffsetRectRelativeToCustomParent(reference, offsetParent, isParentFixed, isParentTransformed); 638} 639 640/** 641 * Get the prefixed supported property name 642 * @method 643 * @memberof Popper.Utils 644 * @argument {String} property (camelCase) 645 * @returns {String} prefixed property (camelCase) 646 */ 647function getSupportedPropertyName(property) { 648 const prefixes = [false, 'ms', 'webkit', 'moz', 'o']; 649 const upperProp = property.charAt(0).toUpperCase() + property.slice(1); 650 651 for (let i = 0; i < prefixes.length - 1; i++) { 652 const prefix = prefixes[i]; 653 const toCheck = prefix ? `${ prefix }${ upperProp }` : property; 654 if (typeof window.document.body.style[toCheck] !== 'undefined') { 655 return toCheck; 656 } 657 } 658 return null; 659} 660 661/** 662 * Check if the given variable is a function 663 * @method 664 * @memberof Popper.Utils 665 * @argument {Element} element - Element to check 666 * @returns {Boolean} answer to: is a function? 667 */ 668function isFunction(functionToCheck) { 669 const getType = {}; 670 return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; 671} 672 673/** 674 * Helper used to know if the given modifier is enabled. 675 * @method 676 * @memberof Popper.Utils 677 * @returns {Boolean} 678 */ 679function isModifierEnabled(modifiers, modifierName) { 680 return modifiers.some(({ name, enabled }) => enabled && name === modifierName); 681} 682 683/** 684 * Helper used to know if the given modifier depends from another one. 685 * It checks if the needed modifier is listed and enabled. 686 * @method 687 * @memberof Popper.Utils 688 * @returns {Boolean} 689 */ 690function isModifierRequired(modifiers, requestingName, requestedName) { 691 const requesting = findIndex$1(modifiers, ({ name }) => name === requestingName); 692 693 return !!requesting && modifiers.some(modifier => {
694 return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order; 695 }); 696} 697 698/** 699 * Tells if a given input is a number 700 * @method 701 * @memberof Popper.Utils 702 * @param {*} input to check 703 * @return {Boolean} 704 */ 705function isNumeric(n) { 706 return n !== '' && !isNaN(parseFloat(n)) && isFinite(n); 707} 708 709/** 710 * Check if the given element has transforms applied to itself or a parent 711 * @method 712 * @memberof Popper.Utils 713 * @param {Element} element 714 * @return {Boolean} answer to "isTransformed?" 715 */ 716function isTransformed(element) { 717 if (element.nodeName === 'BODY') { 718 return false; 719 } 720 if (getStyleComputedProperty(element, 'transform') !== 'none') { 721 return true; 722 } 723 return getParentNode(element) ? isTransformed(getParentNode(element)) : element; 724} 725 726/** 727 * Remove event listeners used to update the popper position 728 * @method 729 * @memberof Popper.Utils 730 * @private 731 */ 732function removeEventListeners(reference, state) { 733 // NOTE: 1 DOM access here 734 window.removeEventListener('resize', state.updateBound); 735 if (state.scrollElement) { 736 state.scrollElement.removeEventListener('scroll', state.updateBound); 737 } 738 state.updateBound = null; 739 state.scrollElement = null; 740 state.eventsEnabled = false; 741 return state; 742} 743 744/** 745 * Loop trough the list of modifiers and run them in order, each of them will then edit the data object 746 * @method 747 * @memberof Popper.Utils 748 * @param {Object} data 749 * @param {Array} modifiers 750 * @param {Function} ends 751 */ 752function runModifiers(modifiers, data, ends) { 753 const modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends)); 754 755 modifiersToRun.forEach(modifier => { 756 if (modifier.enabled && isFunction(modifier.function)) { 757 data = modifier.function(data, modifier); 758 } 759 }); 760 761 return data; 762} 763 764/** 765 * Set the attributes to the given popper 766 * @method 767 * @memberof Popper.Utils 768 * @argument {Element} element - Element to apply the attributes to 769 * @argument {Object} styles - Object with a list of properties and values which will be applied to the element 770 */ 771function setAttributes(element, attributes) { 772 Object.keys(attributes).forEach(function (prop) { 773 const value = attributes[prop]; 774 if (value !== false) { 775 element.setAttribute(prop, attributes[prop]); 776 } else { 777 element.removeAttribute(prop); 778 } 779 }); 780} 781 782/** 783 * Set the style to the given popper 784 * @method 785 * @memberof Popper.Utils 786 * @argument {Element} element - Element to apply the style to 787 * @argument {Object} styles - Object with a list of properties and values which will be applied to the element 788 */ 789function setStyles(element, styles) { 790 Object.keys(styles).forEach(prop => { 791 let unit = ''; 792 // add unit if the value is numeric and is one of the following 793 if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) { 794 unit = 'px'; 795 } 796 element.style[prop] = styles[prop] + unit; 797 }); 798} 799 800/** 801 * Setup needed event listeners used to update the popper position 802 * @method 803 * @memberof Popper.Utils 804 * @private 805 */ 806function setupEventListeners(reference, options, state, updateBound) { 807 // NOTE: 1 DOM access here 808 state.updateBound = updateBound; 809 window.addEventListener('resize', state.updateBound, { passive: true }); 810 let target = getScrollParent(reference); 811 if (target.nodeName === 'BODY') { 812 target = window; 813 } 814 target.addEventListener('scroll', state.updateBound, { passive: true }); 815 state.scrollElement = target; 816 state.eventsEnabled = true; 817 818 return state; 819} 820 821/** @namespace Popper.Utils */ 822var Utils = { 823 computeAutoPlacement, 824 debounce, 825 findIndex, 826 getBoundaries, 827 getBoundingClientRect, 828 getClientRect, 829 getOffsetParent, 830 getOffsetRect, 831 getOffsetRectRelativeToCustomParent, 832 getOuterSizes, 833 getParentNode, 834 getPopperOffsets, 835 getPosition, 836 getReferenceOffsets, 837 getScroll, 838 getScrollParent, 839 getStyleComputedProperty, 840 getSupportedPropertyName, 841 getTotalScroll, 842 getWindowSizes, 843 isFixed, 844 isFunction, 845 isModifierEnabled, 846 isModifierRequired, 847 isNative, 848 isNumeric, 849 isTransformed, 850 removeEventListeners, 851 runModifiers, 852 setAttributes, 853 setStyles, 854 setupEventListeners 855}; 856 857/** 858 * Apply the computed styles to the popper element 859 * @method 860 * @memberof Modifiers 861 * @argument {Object} data - The data object generated by `update` method 862 * @argument {Object} data.styles - List of style properties - values to apply to popper element 863 * @argument {Object} data.attributes - List of attribute properties - values to apply to popper element 864 * @argument {Object} options - Modifiers configuration and options 865 * @returns {Object} The same data object 866 */ 867function applyStyle(data, options) { 868 // apply the final offsets to the popper 869 // NOTE: 1 DOM access here 870 const styles = { 871 position: data.offsets.popper.position 872 }; 873 874 const attributes = { 875 'x-placement': data.placement 876 }; 877 878 // round top and left to avoid blurry text 879 const left = Math.round(data.offsets.popper.left); 880 const top = Math.round(data.offsets.popper.top); 881 882 // if gpuAcceleration is set to true and transform is supported, 883 // we use `translate3d` to apply the position to the popper we 884 // automatically use the supported prefixed version if needed
885 const prefixedProperty = getSupportedPropertyName('transform'); 886 if (options.gpuAcceleration && prefixedProperty) { 887 styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)'; 888 styles.top = 0; 889 styles.left = 0; 890 styles.willChange = 'transform'; 891 } 892 // othwerise, we use the standard `left` and `top` properties 893 else { 894 styles.left = left; 895 styles.top = top; 896 styles.willChange = 'top, left'; 897 } 898 899 // any property present in `data.styles` will be applied to the popper, 900 // in this way we can make the 3rd party modifiers add custom styles to it 901 // Be aware, modifiers could override the properties defined in the previous 902 // lines of this modifier! 903 setStyles(data.instance.popper, _extends({}, styles, data.styles)); 904 905 // any property present in `data.attributes` will be applied to the popper, 906 // they will be set as HTML attributes of the element 907 setAttributes(data.instance.popper, _extends({}, attributes, data.attributes)); 908 909 // if the arrow style has been computed, apply the arrow style 910 if (data.offsets.arrow) { 911 setStyles(data.arrowElement, data.offsets.arrow); 912 } 913 914 return data; 915} 916 917/** 918 * Set the x-placement attribute before everything else because it could be used to add margins to the popper 919 * margins needs to be calculated to get the correct popper offsets 920 * @method 921 * @memberof Popper.modifiers 922 * @param {HTMLElement} reference - The reference element used to position the popper 923 * @param {HTMLElement} popper - The HTML element used as popper. 924 * @param {Object} options - Popper.js options 925 */ 926function applyStyleOnLoad(reference, popper, options, modifierOptions, state) { 927 // compute reference element offsets 928 const referenceOffsets = getReferenceOffsets(state, popper, reference); 929 930 // compute auto placement, store placement inside the data object, 931 // modifiers will be able to edit `placement` if needed 932 // and refer to originalPlacement to know the original value 933 options.placement = computeAutoPlacement(options.placement, referenceOffsets, popper); 934 935 popper.setAttribute('x-placement', options.placement); 936 return options; 937} 938 939/** 940 * Modifier used to move the arrowElements on the edge of the popper to make sure them are always between the popper and the reference element 941 * It will use the CSS outer size of the arrowElement element to know how many pixels of conjuction are needed 942 * @method 943 * @memberof Modifiers 944 * @argument {Object} data - The data object generated by update method 945 * @argument {Object} options - Modifiers configuration and options 946 * @returns {Object} The data object, properly modified 947 */ 948function arrow(data, options) { 949 // arrow depends on keepTogether in order to work 950 if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) { 951 console.warn('WARNING: `keepTogether` modifier is required by arrow modifier in order to work, be sure to include it before `arrow`!'); 952 return data; 953 } 954 955 let arrowElement = options.element; 956 957 // if arrowElement is a string, suppose it's a CSS selector 958 if (typeof arrowElement === 'string') { 959 arrowElement = data.instance.popper.querySelector(arrowElement); 960 961 // if arrowElement is not found, don't run the modifier 962 if (!arrowElement) { 963 return data; 964 } 965 } else { 966 // if the arrowElement isn't a query selector we must check that the 967 // provided DOM node is child of its popper node 968 if (!data.instance.popper.contains(arrowElement)) { 969 console.warn('WARNING: `arrow.element` must be child of its popper element!'); 970 return data; 971 } 972 } 973 974 const placement = data.placement.split('-')[0]; 975 const popper = getClientRect(data.offsets.popper); 976 const reference = data.offsets.reference; 977 const isVertical = ['left', 'right'].indexOf(placement) !== -1; 978 979 const len = isVertical ? 'height' : 'width'; 980 const side = isVertical ? 'top' : 'left'; 981 const altSide = isVertical ? 'left' : 'top'; 982 const opSide = isVertical ? 'bottom' : 'right'; 983 const arrowElementSize = getOuterSizes(arrowElement)[len]; 984 985 // 986 // extends keepTogether behavior making sure the popper and its reference have enough pixels in conjuction 987 // 988 989 // top/left side
990 if (reference[opSide] - arrowElementSize < popper[side]) { 991 data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize); 992 } 993 // bottom/right side 994 if (reference[side] + arrowElementSize > popper[opSide]) { 995 data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide]; 996 } 997 998 // compute center of the popper 999 const center = reference[side] + reference[len] / 2 - arrowElementSize / 2; 1000 1001 // Compute the sideValue using the updated popper offsets 1002 let sideValue = center - getClientRect(data.offsets.popper)[side]; 1003 1004 // prevent arrowElement from being placed not contiguously to its popper 1005 sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0); 1006 1007 data.arrowElement = arrowElement; 1008 data.offsets.arrow = {}; 1009 data.offsets.arrow[side] = sideValue; 1010 data.offsets.arrow[altSide] = ''; // make sure to unset any eventual altSide value from the DOM node 1011 1012 return data; 1013} 1014 1015/** 1016 * Get the opposite placement variation of the given one/ 1017 * @method 1018 * @memberof Popper.Utils 1019 * @argument {String} placement variation 1020 * @returns {String} flipped placement variation 1021 */ 1022function getOppositeVariation(variation) { 1023 if (variation === 'end') { 1024 return 'start'; 1025 } else if (variation === 'start') { 1026 return 'end'; 1027 } 1028 return variation; 1029} 1030 1031/** 1032 * Modifier used to flip the placement of the popper when the latter is starting overlapping its reference element. 1033 * Requires the `preventOverflow` modifier before it in order to work. 1034 * **NOTE:** data.instance modifier will run all its previous modifiers everytime it tries to flip the popper! 1035 * @method 1036 * @memberof Modifiers 1037 * @argument {Object} data - The data object generated by update method 1038 * @argument {Object} options - Modifiers configuration and options 1039 * @returns {Object} The data object, properly modified 1040 */ 1041function flip(data, options) { 1042 // if `inner` modifier is enabled, we can't use the `flip` modifier 1043 if (isModifierEnabled(data.instance.modifiers, 'inner')) { 1044 return data; 1045 } 1046 1047 if (data.flipped && data.placement === data.originalPlacement) { 1048 // seems like flip is trying to loop, probably there's not enough space on any of the flippable sides 1049 return data; 1050 } 1051 1052 const boundaries = getBoundaries(data.instance.popper, options.padding, options.boundariesElement); 1053 1054 let placement = data.placement.split('-')[0]; 1055 let placementOpposite = getOppositePlacement(placement); 1056 let variation = data.placement.split('-')[1] || ''; 1057 1058 let flipOrder = []; 1059 1060 if (options.behavior === 'flip') { 1061 flipOrder = [placement, placementOpposite]; 1062 } else { 1063 flipOrder = options.behavior; 1064 } 1065 1066 flipOrder.forEach((step, index) => { 1067 if (placement !== step || flipOrder.length === index + 1) { 1068 return data; 1069 } 1070 1071 placement = data.placement.split('-')[0]; 1072 placementOpposite = getOppositePlacement(placement); 1073 1074 const popperOffsets = getClientRect(data.offsets.popper); 1075 const refOffsets = data.offsets.reference; 1076 1077 // using Math.floor because the reference offsets may contain decimals we are not going to consider here 1078 const overlapsRef = placement === 'left' && Math.floor(popperOffsets.right) > Math.floor(refOffsets.left) || placement === 'right' && Math.floor(popperOffsets.left) < Math.floor(refOffsets.right) || placement === 'top' && Math.floor(popperOffsets.bottom) > Math.floor(refOffsets.top) || placement === 'bottom' && Math.floor(popperOffsets.top) < Math.floor(refOffsets.bottom); 1079 1080 const overflowsBoundaries = placement === 'left' && Math.floor(popperOffsets.left) < Math.floor(boundaries.left) || placement === 'right' && Math.floor(popperOffsets.right) > Math.floor(boundaries.right) || placement === 'top' && Math.floor(popperOffsets.top) < Math.floor(boundaries.top) || placement === 'bottom' && Math.floor(popperOffsets.bottom) > Math.floor(boundaries.bottom); 1081 1082 // flip the variation if required 1083 const isVertical = ['top', 'bottom'].indexOf(placement) !== -1; 1084 const flippedVariation = !!options.flipVariations && (isVertical && variation === 'start' && Math.floor(popperOffsets.left) < Math.floor(boundaries.left) || isVertical && variation === 'end' && Math.floor(popperOffsets.right) > Math.floor(boundaries.right) || !isVertical && variation === 'start' && Math.floor(popperOffsets.top) < Math.floor(boundaries.top) || !isVertical && variation === 'end' && Math.floor(popperOffsets.bottom) > Math.floor(boundaries.bottom)); 1085 1086 if (overlapsRef || overflowsBoundaries || flippedVariation) { 1087 // this boolean to detect any flip loop 1088 data.flipped = true; 1089 1090 if (overlapsRef || overflowsBoundaries) { 1091 placement = flipOrder[index + 1]; 1092 } 1093 1094 if (flippedVariation) { 1095 variation = getOppositeVariation(variation); 1096 } 1097 1098 data.placement = placement + (variation ? '-' + variation : ''); 1099 data.offsets.popper = getPopperOffsets(data.instance.state, data.instance.popper, data.offsets.reference, data.placement); 1100 1101 data = runModifiers(data.instance.modifiers, data, 'flip'); 1102 } 1103 }); 1104 return data; 1105} 1106 1107/** 1108 * Modifier used to make sure the popper is always near its reference element 1109 * It cares only about the first axis, you can still have poppers with margin 1110 * between the popper and its reference element. 1111 * @method 1112 * @memberof Modifiers 1113 * @argument {Object} data - The data object generated by update method 1114 * @argument {Object} options - Modifiers configuration and options 1115 * @returns {Object} The data object, properly modified 1116 */ 1117function keepTogether(data) { 1118 const popper = getClientRect(data.offsets.popper); 1119 const reference = data.offsets.reference; 1120 const placement = data.placement.split('-')[0]; 1121 const floor = Math.floor; 1122 1123 if (['top', 'bottom'].indexOf(placement) !== -1) { 1124 if (popper.right < floor(reference.left)) { 1125 data.offsets.popper.left = floor(reference.left) - popper.width; 1126 } 1127 if (popper.left > floor(reference.right)) { 1128 data.offsets.popper.left = floor(reference.right); 1129 } 1130 } else { 1131 if (popper.bottom < floor(reference.top)) { 1132 data.offsets.popper.top = floor(reference.top) - popper.height; 1133 } 1134 if (popper.top > floor(reference.bottom)) { 1135 data.offsets.popper.top = floor(reference.bottom); 1136 } 1137 } 1138 1139 return data; 1140} 1141 1142/** 1143 * Modifier used to add an offset to the popper, useful if you more granularity positioning your popper. 1144 * The offsets will shift the popper on the side of its reference element. 1145 * @method 1146 * @memberof Modifiers 1147 * @argument {Object} data - The data object generated by update method 1148 * @argument {Object} options - Modifiers configuration and options 1149 * @argument {Number|String} options.offset=0 1150 * Basic usage allows a number used to nudge the popper by the given amount of pixels. 1151 * You can pass a percentage value as string (eg. `20%`) to nudge by the given percentage (relative to reference element size) 1152 * Other supported units are `vh` and `vw` (relative to viewport) 1153 * Additionally, you can pass a pair of values (eg. `10 20` or `2vh 20%`) to nudge the popper 1154 * on both axis. 1155 * A note about percentage values, if you want to refer a percentage to the popper size instead of the reference element size, 1156 * use `%p` instead of `%` (eg: `20%p`). To make it clearer, you can replace `%` with `%r` and use eg.`10%p 25%r`. 1157 * > **Heads up!** The order of the axis is relative to the popper placement: `bottom` or `top` are `X,Y`, the other are `Y,X` 1158 * @returns {Object} The data object, properly modified 1159 */ 1160function offset(data, options) { 1161 const placement = data.placement; 1162 const popper = data.offsets.popper; 1163 1164 let offsets; 1165 if (isNumeric(options.offset)) { 1166 offsets = [options.offset, 0]; 1167 } else { 1168 // split the offset in case we are providing a pair of offsets separated 1169 // by a blank space 1170 offsets = options.offset.split(' '); 1171 1172 // itherate through each offset to compute them in case they are percentages 1173 offsets = offsets.map((offset, index) => { 1174 // separate value from unit 1175 const split = offset.match(/(\d*\.?\d*)(.*)/); 1176 const value = +split[1]; 1177 const unit = split[2]; 1178 1179 // use height if placement is left or right and index is 0 otherwise use width 1180 // in this way the first offset will use an axis and the second one 1181 // will use the other one 1182 let useHeight = placement.indexOf('right') !== -1 || placement.indexOf('left') !== -1; 1183 1184 if (index === 1) {
1185 useHeight = !useHeight; 1186 } 1187 1188 const measurement = useHeight ? 'height' : 'width'; 1189 1190 // if is a percentage, we calculate the value of it using as base the 1191 // sizes of the reference element 1192 if (unit === '%' || unit === '%r') { 1193 const referenceRect = getClientRect(data.offsets.reference); 1194 let len = referenceRect[measurement]; 1195 return len / 100 * value; 1196 } 1197 // if is a percentage relative to the popper, we calculate the value of it using 1198 // as base the sizes of the popper 1199 else if (unit === '%p') { 1200 const popperRect = getClientRect(data.offsets.popper); 1201 let len = popperRect[measurement]; 1202 return len / 100 * value; 1203 } 1204 // if is a vh or vw, we calculate the size based on the viewport 1205 else if (unit === 'vh' || unit === 'vw') { 1206 let size; 1207 if (unit === 'vh') { 1208 size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); 1209 } else { 1210 size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); 1211 } 1212 return size / 100 * value; 1213 } 1214 // if is an explicit pixel unit, we get rid of the unit and keep the value 1215 else if (unit === 'px') { 1216 return +value; 1217 } 1218 // if is an implicit unit, it's px, and we return just the value 1219 else { 1220 return +offset; 1221 } 1222 }); 1223 } 1224 1225 if (data.placement.indexOf('left') !== -1) { 1226 popper.top += offsets[0]; 1227 popper.left -= offsets[1] || 0; 1228 } else if (data.placement.indexOf('right') !== -1) { 1229 popper.top += offsets[0]; 1230 popper.left += offsets[1] || 0; 1231 } else if (data.placement.indexOf('top') !== -1) { 1232 popper.left += offsets[0]; 1233 popper.top -= offsets[1] || 0; 1234 } else if (data.placement.indexOf('bottom') !== -1) { 1235 popper.left += offsets[0]; 1236 popper.top += offsets[1] || 0; 1237 } 1238 return data; 1239} 1240 1241/** 1242 * Modifier used to prevent the popper from being positioned outside the boundary. 1243 * 1244 * An scenario exists where the reference itself is not within the boundaries. We can 1245 * say it has "escaped the boundaries" — or just "escaped". In this case we need to 1246 * decide whether the popper should either: 1247 * 1248 * - detach from the reference and remain "trapped" in the boundaries, or 1249 * - if it should be ignore the boundary and "escape with the reference" 1250 * 1251 * When `escapeWithReference` is `true`, and reference is completely outside the 1252 * boundaries, the popper will overflow (or completely leave) the boundaries in order 1253 * to remain attached to the edge of the reference. 1254 * 1255 * @method 1256 * @memberof Modifiers 1257 * @argument {Object} data - The data object generated by `update` method 1258 * @argument {Object} options - Modifiers configuration and options 1259 * @returns {Object} The data object, properly modified 1260 */ 1261function preventOverflow(data, options) { 1262 const boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper); 1263 const boundaries = getBoundaries(data.instance.popper, options.padding, boundariesElement); 1264 options.boundaries = boundaries; 1265 1266 const order = options.priority; 1267 let popper = getClientRect(data.offsets.popper); 1268 1269 const check = { 1270 left() { 1271 let left = popper.left; 1272 if (popper.left < boundaries.left && !shouldOverflowBoundary(data, options, 'left')) { 1273 left = Math.max(popper.left, boundaries.left); 1274 } 1275 return { left }; 1276 }, 1277 right() { 1278 let left = popper.left; 1279 if (popper.right > boundaries.right && !shouldOverflowBoundary(data, options, 'right')) { 1280 left = Math.min(popper.left, boundaries.right - popper.width); 1281 } 1282 return { left }; 1283 }, 1284 top() { 1285 let top = popper.top; 1286 if (popper.top < boundaries.top && !shouldOverflowBoundary(data, options, 'top')) { 1287 top = Math.max(popper.top, boundaries.top); 1288 } 1289 return { top }; 1290 }, 1291 bottom() { 1292 let top = popper.top; 1293 if (popper.bottom >
1293 boundaries.bottom && !shouldOverflowBoundary(data, options, 'bottom')) { 1294 top = Math.min(popper.top, boundaries.bottom - popper.height); 1295 } 1296 return { top }; 1297 } 1298 }; 1299 1300 order.forEach(direction => { 1301 popper = _extends({}, popper, check[direction]()); 1302 }); 1303 1304 data.offsets.popper = popper; 1305 1306 return data; 1307} 1308 1309/** 1310 * Determine if the popper should overflow a boundary edge to stay together with the reference. 1311 */ 1312function shouldOverflowBoundary(data, options, overflowDirection) { 1313 if (!options.escapeWithReference) { 1314 return false; 1315 } 1316 1317 if (data.flipped && isSameAxis(data.originalPlacement, overflowDirection)) { 1318 return true; 1319 } 1320 1321 if (!isSameAxis(data.originalPlacement, overflowDirection)) { 1322 return true; 1323 } 1324 1325 return true; 1326} 1327 1328/** 1329 * Determine if two placement values are on the same axis. 1330 */ 1331function isSameAxis(a, b) { 1332 // placement syntax: 1333 // 1334 // ( "top" | "right" | "bottom" | "left" ) ( "-start" | "" | "-end" ) 1335 // |------------- Direction -------------| 1336 // 1337 const aDirection = a.split('-')[0]; 1338 const bDirection = b.split('-')[0]; 1339 1340 return aDirection === bDirection || aDirection === getOppositePlacement(b); 1341} 1342 1343/** 1344 * Modifier used to shift the popper on the start or end of its reference element side 1345 * @method 1346 * @memberof Modifiers 1347 * @argument {Object} data - The data object generated by `update` method 1348 * @argument {Object} options - Modifiers configuration and options 1349 * @returns {Object} The data object, properly modified 1350 */ 1351function shift(data) { 1352 const placement = data.placement; 1353 const basePlacement = placement.split('-')[0]; 1354 const shiftvariation = placement.split('-')[1]; 1355 1356 // if shift shiftvariation is specified, run the modifier 1357 if (shiftvariation) { 1358 const reference = data.offsets.reference; 1359 const popper = getClientRect(data.offsets.popper); 1360 1361 const shiftOffsets = { 1362 y: { 1363 start: { top: reference.top }, 1364 end: { top: reference.top + reference.height - popper.height } 1365 }, 1366 x: { 1367 start: { left: reference.left }, 1368 end: { left: reference.left + reference.width - popper.width } 1369 } 1370 }; 1371 1372 const axis = ['bottom', 'top'].indexOf(basePlacement) !== -1 ? 'x' : 'y'; 1373 1374 data.offsets.popper = _extends({}, popper, shiftOffsets[axis][shiftvariation]); 1375 } 1376 1377 return data; 1378} 1379 1380/** 1381 * Modifier used to hide the popper when its reference element is outside of the 1382 * popper boundaries. It will set an x-hidden attribute which can be used to hide 1383 * the popper when its reference is out of boundaries. 1384 * @method 1385 * @memberof Modifiers 1386 * @argument {Object} data - The data object generated by update method 1387 * @argument {Object} options - Modifiers configuration and options 1388 * @returns {Object} The data object, properly modified 1389 */ 1390function hide(data) { 1391 if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) { 1392 console.warn('WARNING: preventOverflow modifier is required by hide modifier in order to work, be sure to include it before hide!'); 1393 return data; 1394 } 1395 1396 const refRect = data.offsets.reference; 1397 const bound = findIndex$1(data.instance.modifiers, modifier => modifier.name === 'preventOverflow').boundaries; 1398 1399 if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) { 1400 // Avoid unnecessary DOM access if visibility hasn't changed 1401 if (data.hide === true) { 1402 return data; 1403 } 1404 1405 data.hide = true; 1406 data.attributes['x-out-of-boundaries'] = ''; 1407 } else { 1408 // Avoid unnecessary DOM access if visibility hasn't changed 1409 if (data.hide === false) { 1410 return data; 1411 } 1412 1413 data.hide = false;
1414 data.attributes['x-out-of-boundaries'] = false; 1415 } 1416 1417 return data; 1418} 1419 1420/** 1421 * Modifier used to make the popper flow toward the inner of the reference element. 1422 * By default, when this modifier is disabled, the popper will be placed outside 1423 * the reference element. 1424 * @method 1425 * @memberof Modifiers 1426 * @argument {Object} data - The data object generated by `update` method 1427 * @argument {Object} options - Modifiers configuration and options 1428 * @returns {Object} The data object, properly modified 1429 */ 1430function inner(data) { 1431 const placement = data.placement; 1432 const basePlacement = placement.split('-')[0]; 1433 const popper = getClientRect(data.offsets.popper); 1434 const reference = getClientRect(data.offsets.reference); 1435 const isHoriz = ['left', 'right'].indexOf(basePlacement) !== -1; 1436 1437 const subtractLength = ['top', 'left'].indexOf(basePlacement) === -1; 1438 1439 popper[isHoriz ? 'left' : 'top'] = reference[placement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0); 1440 1441 data.placement = getOppositePlacement(placement); 1442 data.offsets.popper = getClientRect(popper); 1443 1444 return data; 1445} 1446 1447/** 1448 * Modifiers are plugins used to alter the behavior of your poppers. 1449 * Popper.js uses a set of 7 modifiers to provide all the basic functionalities 1450 * needed by the library. 1451 *
1452 * Each modifier is an object containing several properties listed below. 1453 * @namespace Modifiers 1454 * @param {Object} modifier - Modifier descriptor 1455 * @param {Integer} modifier.order 1456 * The `order` property defines the execution order of the modifiers. 1457 * The built-in modifiers have orders with a gap of 100 units in between, 1458 * this allows you to inject additional modifiers between the existing ones 1459 * without having to redefine the order of all of them. 1460 * The modifiers are executed starting from the one with the lowest order. 1461 * @param {Boolean} modifier.enabled - When `true`, the modifier will be used. 1462 * @param {Modifiers~modifier} modifier.function - Modifier function. 1463 * @param {Modifiers~onLoad} modifier.onLoad - Function executed on popper initalization 1464 * @return {Object} data - Each modifier must return the modified `data` object. 1465 */ 1466var modifiers = { 1467 shift: { 1468 order: 100, 1469 enabled: true, 1470 function: shift 1471 }, 1472 offset: { 1473 order: 200, 1474 enabled: true, 1475 function: offset, 1476 // nudges popper from its origin by the given amount of pixels (can be negative) 1477 offset: 0 1478 }, 1479 preventOverflow: { 1480 order: 300, 1481 enabled: true, 1482 function: preventOverflow, 1483 // popper will try to prevent overflow following these priorities 1484 // by default, then, it could overflow on the left and on top of the boundariesElement 1485 priority: ['left', 'right', 'top', 'bottom'], 1486 // amount of pixel used to define a minimum distance between the boundaries and the popper 1487 // this makes sure the popper has always a little padding between the edges of its container 1488 padding: 5, 1489 boundariesElement: 'scrollParent' 1490 }, 1491 keepTogether: { 1492 order: 400, 1493 enabled: true, 1494 function: keepTogether 1495 }, 1496 arrow: { 1497 order: 500, 1498 enabled: true, 1499 function: arrow, 1500 // selector or node used as arrow 1501 element: '[x-arrow]' 1502 }, 1503 flip: { 1504 order: 600, 1505 enabled: true, 1506 function: flip, 1507 // the behavior used to change the popper's placement 1508 behavior: 'flip', 1509 // the popper will flip if it hits the edges of the boundariesElement - padding 1510 padding: 5, 1511 boundariesElement: 'viewport' 1512 }, 1513 inner: { 1514 order: 700, 1515 enabled: false, 1516 function: inner 1517 }, 1518 hide: { 1519 order: 800, 1520 enabled: true, 1521 function: hide 1522 }, 1523 applyStyle: { 1524 order: 900, 1525 enabled: true, 1526 // if true, it uses the CSS 3d transformation to position the popper 1527 gpuAcceleration: true, 1528 function: applyStyle, 1529 onLoad: applyStyleOnLoad 1530 } 1531}; 1532 1533/** 1534 * Modifiers can edit the `data` object to change the beheavior of the popper.
1535 * This object contains all the informations used by Popper.js to compute the 1536 * popper position. 1537 * The modifier can edit the data as needed, and then `return` it as result. 1538 * 1539 * @callback Modifiers~modifier 1540 * @param {dataObject} data 1541 * @return {dataObject} modified data 1542 */ 1543 1544/** 1545 * The `dataObject` is an object containing all the informations used by Popper.js 1546 * this object get passed to modifiers and to the `onCreate` and `onUpdate` callbacks. 1547 * @name dataObject 1548 * @property {Object} data.instance The Popper.js instance 1549 * @property {String} data.placement Placement applied to popper 1550 * @property {String} data.originalPlacement Placement originally defined on init 1551 * @property {Boolean} data.flipped True if popper has been flipped by flip modifier 1552 * @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper. 1553 * @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier 1554 * @property {Object} data.styles Any CSS property defined here will be applied to the popper, it expects the JavaScript nomenclature (eg. `marginBottom`) 1555 * @property {Object} data.boundaries Offsets of the popper boundaries 1556 * @property {Object} data.offsets The measurements of popper, reference and arrow elements. 1557 * @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values 1558 * @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values 1559 * @property {Object} data.offsets.arro] `top` and `left` offsets, only one of them will be different from 0 1560 */ 1561 1562// Utils 1563// Modifiers 1564// default options 1565const DEFAULTS = { 1566 // placement of the popper 1567 placement: 'bottom', 1568 1569 // whether events (resize, scroll) are initially enabled 1570 eventsEnabled: true, 1571 1572 /** 1573 * Callback called when the popper is created. 1574 * By default, is set to no-op. 1575 * Access Popper.js instance with `data.instance`. 1576 * @callback createCallback 1577 * @static 1578 * @param {dataObject} data 1579 */ 1580 onCreate: () => {}, 1581 1582 /** 1583 * Callback called when the popper is updated, this callback is not called 1584 * on the initialization/creation of the popper, but only on subsequent 1585 * updates. 1586 * By default, is set to no-op. 1587 * Access Popper.js instance with `data.instance`. 1588 * @callback updateCallback 1589 * @static 1590 * @param {dataObject} data 1591 */ 1592 onUpdate: () => {}, 1593 1594 // list of functions used to modify the offsets before they are applied to the popper 1595 modifiers 1596}; 1597 1598/** 1599 * Create a new Popper.js instance 1600 * @class Popper 1601 * @param {HTMLElement} reference - The reference element used to position the popper 1602 * @param {HTMLElement} popper - The HTML element used as popper. 1603 * @param {Object} options 1604 * @param {String} options.placement=bottom 1605 * Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -end), 1606 * left(-start, -end)` 1607 * 1608 * @param {Boolean} options.eventsEnabled=true 1609 * Whether events (resize, scroll) are initially enabled 1610 * @param {Boolean} options.gpuAcceleration=true 1611 * When this property is set to true, the popper position will be applied using CSS3 translate3d, allowing the 1612 * browser to use the GPU to accelerate the rendering. 1613 * If set to false, the popper will be placed using `top` and `left` properties, not using the GPU. 1614 * 1615 * @param {Boolean} options.removeOnDestroy=false 1616 * Set to true if you want to automatically remove the popper when you call the `destroy` method. 1617 * 1618 * @param {Object} options.modifiers 1619 * List of functions used to modify the data before they are applied to the popper (see source code for default values) 1620 * 1621 * @param {Object} options.modifiers.arrow - Arrow modifier configuration 1622 * @param {String|HTMLElement} options.modifiers.arrow.element='[x-arrow]' 1623 * The DOM Node used as arrow for the popper, or a CSS selector used to get the DOM node. It must be child of 1624 * its parent Popper. Popper.js will apply to the given element the style required to align the arrow with its 1625 * reference element. 1626 * By default, it will look for a child node of the popper with the `x-arrow` attribute. 1627 * 1628 * @param {Object} options.modifiers.offset - Offset modifier configuration 1629 * @param {Number} options.modifiers.offset.offset=0
1630 * Amount of pixels the popper will be shifted (can be negative). 1631 * 1632 * @param {Object} options.modifiers.preventOverflow - PreventOverflow modifier configuration 1633 * @param {Array} [options.modifiers.preventOverflow.priority=['left', 'right', 'top', 'bottom']] 1634 * Priority used when Popper.js tries to avoid overflows from the boundaries, they will be checked in order, 1635 * this means that the last one will never overflow 1636 * @param {String|HTMLElement} options.modifiers.preventOverflow.boundariesElement='scrollParent' 1637 * Boundaries used by the modifier, can be `scrollParent`, `window`, `viewport` or any DOM element. 1638 * @param {Number} options.modifiers.preventOverflow.padding=5 1639 * Amount of pixel used to define a minimum distance between the boundaries and the popper 1640 * this makes sure the popper has always a little padding between the edges of its container. 1641 * 1642 * @param {Object} options.modifiers.flip - Flip modifier configuration 1643 * @param {String|Array} options.modifiers.flip.behavior='flip' 1644 * The behavior used by the `flip` modifier to change the placement of the popper when the latter is trying to 1645 * overlap its reference element. Defining `flip` as value, the placement will be flipped on 1646 * its axis (`right - left`, `top - bottom`). 1647 * You can even pass an array of placements (eg: `['right', 'left', 'top']` ) to manually specify 1648 * how alter the placement when a flip is needed. (eg. in the above example, it would first flip from right to left, 1649 * then, if even in its new placement, the popper is overlapping its reference element, it will be moved to top) 1650 * @param {String|HTMLElement} options.modifiers.flip.boundariesElement='viewport' 1651 * The element which will define the boundaries of the popper position, the popper will never be placed outside 1652 * of the defined boundaries (except if `keepTogether` is enabled) 1653 * 1654 * @param {Object} options.modifiers.inner - Inner modifier configuration 1655 * @param {Number} options.modifiers.innner.enabled=false 1656 * Set to `true` to make the popper flow toward the inner of the reference element. 1657 * 1658 * @param {Number} options.modifiers.flip.padding=5 1659 * Amount of pixel used to define a minimum distance between the boundaries and the popper 1660 * this makes sure the popper has always a little padding between the edges of its container. 1661 * 1662 * @param {createCallback} options.onCreate - onCreate callback 1663 * Function called after the Popper has been instantiated. 1664 * 1665 * @param {updateCallback} options.onUpdate - onUpdate callback 1666 * Function called on subsequent updates of Popper. 1667 * 1668 * @return {Object} instance - The generated Popper.js instance 1669 */ 1670class Popper { 1671 constructor(reference, popper, options = {}) { 1672 this.scheduleUpdate = () => requestAnimationFrame(this.update); 1673 1674 // make update() debounced, so that it only runs at most once-per-tick 1675 this.update = debounce(this.update.bind(this)); 1676 1677 // with {} we create a new object with the options inside it 1678 this.options = _extends({}, Popper.Defaults, options); 1679 1680 // init state 1681 this.state = { 1682 isDestroyed: false, 1683 isCreated: false 1684 }; 1685 1686 // get reference and popper elements (allow jQuery wrappers) 1687 this.reference = reference.jquery ? reference[0] : reference; 1688 this.popper = popper.jquery ? popper[0] : popper; 1689 1690 // refactoring modifiers' list (Object => Array) 1691 this.modifiers = Object.keys(Popper.Defaults.modifiers).map(name => _extends({ name }, Popper.Defaults.modifiers[name])); 1692 1693 // assign default values to modifiers, making sure to override them with 1694 // the ones defined by user 1695 this.modifiers = this.modifiers.map(defaultConfig => { 1696 const userConfig = options.modifiers && options.modifiers[defaultConfig.name] || {}; 1697 return _extends({}, defaultConfig, userConfig); 1698 }); 1699 1700 // add custom modifiers to the modifiers list 1701 if (options.modifiers) { 1702 this.options.modifiers = _extends({}, Popper.Defaults.modifiers, options.modifiers);
1703 Object.keys(options.modifiers).forEach(name => { 1704 // take in account only custom modifiers 1705 if (Popper.Defaults.modifiers[name] === undefined) { 1706 const modifier = options.modifiers[name]; 1707 modifier.name = name; 1708 this.modifiers.push(modifier); 1709 } 1710 }); 1711 } 1712 1713 // get the popper position type 1714 this.state.position = getPosition(this.reference); 1715 1716 // sort the modifiers by order 1717 this.modifiers = this.modifiers.sort((a, b) => a.order - b.order); 1718 1719 // modifiers have the ability to execute arbitrary code when Popper.js get inited 1720 // such code is executed in the same order of its modifier 1721 // they could add new properties to their options configuration 1722 // BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`! 1723 this.modifiers.forEach(modifierOptions => { 1724 if (modifierOptions.enabled && isFunction(modifierOptions.onLoad)) { 1725 modifierOptions.onLoad(this.reference, this.popper, this.options, modifierOptions, this.state); 1726 } 1727 }); 1728 1729 // determine how we should set the origin of offsets 1730 this.state.isParentTransformed = isTransformed(this.popper.parentNode); 1731 1732 // fire the first update to position the popper in the right place 1733 this.update(); 1734 1735 const eventsEnabled = this.options.eventsEnabled; 1736 if (eventsEnabled) { 1737 // setup event listeners, they will take care of update the position in specific situations 1738 this.enableEventListeners(); 1739 } 1740 1741 this.state.eventsEnabled = eventsEnabled; 1742 } 1743 1744 // 1745 // Methods 1746 // 1747 1748 /** 1749 * Updates the position of the popper, computing the new offsets and applying the new style 1750 * Prefer `scheduleUpdate` over `update` because of performance reasons 1751 * @method 1752 * @memberof Popper 1753 */ 1754 update() { 1755 // if popper is destroyed, don't perform any further update 1756 if (this.state.isDestroyed) { 1757 return; 1758 } 1759 1760 let data = { 1761 instance: this, 1762 styles: {}, 1763 attributes: {}, 1764 flipped: false, 1765 offsets: {} 1766 }; 1767 1768 // make sure to apply the popper position before any computation 1769 this.state.position = getPosition(this.reference); 1770 setStyles(this.popper, { position: this.state.position }); 1771 1772 // compute reference element offsets 1773 data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference); 1774 1775 // compute auto placement, store placement inside the data object, 1776 // modifiers will be able to edit `placement` if needed 1777 // and refer to originalPlacement to know the original value 1778 data.placement = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper); 1779 1780 // store the computed placement inside `originalPlacement` 1781 data.originalPlacement = this.options.placement; 1782 1783 // compute the popper offsets 1784 data.offsets.popper = getPopperOffsets(this.state, this.popper, data.offsets.reference, data.placement); 1785 1786 // run the modifiers 1787 data = runModifiers(this.modifiers, data); 1788 1789 // the first `update` will call `onCreate` callback 1790 // the other ones will call `onUpdate` callback 1791 if (!this.state.isCreated) { 1792 this.state.isCreated = true; 1793 this.options.onCreate(data); 1794 } else { 1795 this.options.onUpdate(data); 1796 } 1797 } 1798 1799 /** 1800 * Schedule an update, it will run on the next UI update available 1801 * @method 1802 * @memberof Popper 1803 */ 1804 1805 1806 /** 1807 * Destroy the popper 1808 * @method 1809 * @memberof Popper 1810 */ 1811 destroy() { 1812 this.state.isDestroyed = true; 1813 1814 // touch DOM only if `applyStyle` modifier is enabled 1815 if (isModifierEnabled(this.modifiers, 'applyStyle')) { 1816 this.popper.removeAttribute('x-placement'); 1817 this.popper.style.left = ''; 1818 this.popper.style.position = ''; 1819 this.popper.style.top = '';
1820 this.popper.style[getSupportedPropertyName('transform')] = ''; 1821 } 1822 1823 this.disableEventListeners(); 1824 1825 // remove the popper if user explicity asked for the deletion on destroy 1826 // do not use `remove` because IE11 doesn't support it 1827 if (this.options.removeOnDestroy) { 1828 this.popper.parentNode.removeChild(this.popper); 1829 } 1830 return this; 1831 } 1832 1833 /** 1834 * it will add resize/scroll events and start recalculating 1835 * position of the popper element when they are triggered 1836 * @method 1837 * @memberof Popper 1838 */ 1839 enableEventListeners() { 1840 if (!this.state.eventsEnabled) { 1841 this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate); 1842 } 1843 } 1844 1845 /** 1846 * it will remove resize/scroll events and won't recalculate 1847 * popper position when they are triggered. It also won't trigger onUpdate callback anymore, 1848 * unless you call 'update' method manually. 1849 * @method 1850 * @memberof Popper 1851 */ 1852 disableEventListeners() { 1853 if (this.state.eventsEnabled) { 1854 window.cancelAnimationFrame(this.scheduleUpdate); 1855 this.state = removeEventListeners(this.reference, this.state); 1856 } 1857 } 1858 1859 /** 1860 * Collection of utilities useful when writing custom modifiers 1861 * @memberof Popper 1862 */ 1863 1864 1865 /** 1866 * List of accepted placements to use as values of the `placement` option 1867 * @memberof Popper 1868 */ 1869 1870 1871 /** 1872 * Default Popper.js options 1873 * @memberof Popper 1874 */ 1875} 1876Popper.Utils = Utils; 1877Popper.placements = ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'right', 'right-start', 'right-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end']; 1878Popper.Defaults = DEFAULTS; 1879 1880return Popper; 1881 1882}))); 1883//# sourceMappingURL=popper.js.map
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.