An unfinished system to manage all your paper documentation in an easy way.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

popper-utils.js 33KB

pirms 5 gadiem
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  1. /**!
  2. * @fileOverview Kickass library to create and place poppers near their reference elements.
  3. * @version 1.15.0
  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. /**
  26. * Get CSS computed property of the given element
  27. * @method
  28. * @memberof Popper.Utils
  29. * @argument {Eement} element
  30. * @argument {String} property
  31. */
  32. function getStyleComputedProperty(element, property) {
  33. if (element.nodeType !== 1) {
  34. return [];
  35. }
  36. // NOTE: 1 DOM access here
  37. const window = element.ownerDocument.defaultView;
  38. const css = window.getComputedStyle(element, null);
  39. return property ? css[property] : css;
  40. }
  41. /**
  42. * Returns the parentNode or the host of the element
  43. * @method
  44. * @memberof Popper.Utils
  45. * @argument {Element} element
  46. * @returns {Element} parent
  47. */
  48. function getParentNode(element) {
  49. if (element.nodeName === 'HTML') {
  50. return element;
  51. }
  52. return element.parentNode || element.host;
  53. }
  54. /**
  55. * Returns the scrolling parent of the given element
  56. * @method
  57. * @memberof Popper.Utils
  58. * @argument {Element} element
  59. * @returns {Element} scroll parent
  60. */
  61. function getScrollParent(element) {
  62. // Return body, `getScroll` will take care to get the correct `scrollTop` from it
  63. if (!element) {
  64. return document.body;
  65. }
  66. switch (element.nodeName) {
  67. case 'HTML':
  68. case 'BODY':
  69. return element.ownerDocument.body;
  70. case '#document':
  71. return element.body;
  72. }
  73. // Firefox want us to check `-x` and `-y` variations as well
  74. const { overflow, overflowX, overflowY } = getStyleComputedProperty(element);
  75. if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
  76. return element;
  77. }
  78. return getScrollParent(getParentNode(element));
  79. }
  80. var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
  81. const isIE11 = isBrowser && !!(window.MSInputMethodContext && document.documentMode);
  82. const isIE10 = isBrowser && /MSIE 10/.test(navigator.userAgent);
  83. /**
  84. * Determines if the browser is Internet Explorer
  85. * @method
  86. * @memberof Popper.Utils
  87. * @param {Number} version to check
  88. * @returns {Boolean} isIE
  89. */
  90. function isIE(version) {
  91. if (version === 11) {
  92. return isIE11;
  93. }
  94. if (version === 10) {
  95. return isIE10;
  96. }
  97. return isIE11 || isIE10;
  98. }
  99. /**
  100. * Returns the offset parent of the given element
  101. * @method
  102. * @memberof Popper.Utils
  103. * @argument {Element} element
  104. * @returns {Element} offset parent
  105. */
  106. function getOffsetParent(element) {
  107. if (!element) {
  108. return document.documentElement;
  109. }
  110. const noOffsetParent = isIE(10) ? document.body : null;
  111. // NOTE: 1 DOM access here
  112. let offsetParent = element.offsetParent || null;
  113. // Skip hidden elements which don't have an offsetParent
  114. while (offsetParent === noOffsetParent && element.nextElementSibling) {
  115. offsetParent = (element = element.nextElementSibling).offsetParent;
  116. }
  117. const nodeName = offsetParent && offsetParent.nodeName;
  118. if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') {
  119. return element ? element.ownerDocument.documentElement : document.documentElement;
  120. }
  121. // .offsetParent will return the closest TH, TD or TABLE in case
  122. // no offsetParent is present, I hate this job...
  123. if (['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') {
  124. return getOffsetParent(offsetParent);
  125. }
  126. return offsetParent;
  127. }
  128. function isOffsetContainer(element) {
  129. const { nodeName } = element;
  130. if (nodeName === 'BODY') {
  131. return false;
  132. }
  133. return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element;
  134. }
  135. /**
  136. * Finds the root node (document, shadowDOM root) of the given element
  137. * @method
  138. * @memberof Popper.Utils
  139. * @argument {Element} node
  140. * @returns {Element} root node
  141. */
  142. function getRoot(node) {
  143. if (node.parentNode !== null) {
  144. return getRoot(node.parentNode);
  145. }
  146. return node;
  147. }
  148. /**
  149. * Finds the offset parent common to the two provided nodes
  150. * @method
  151. * @memberof Popper.Utils
  152. * @argument {Element} element1
  153. * @argument {Element} element2
  154. * @returns {Element} common offset parent
  155. */
  156. function findCommonOffsetParent(element1, element2) {
  157. // This check is needed to avoid errors in case one of the elements isn't defined for any reason
  158. if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) {
  159. return document.documentElement;
  160. }
  161. // Here we make sure to give as "start" the element that comes first in the DOM
  162. const order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING;
  163. const start = order ? element1 : element2;
  164. const end = order ? element2 : element1;
  165. // Get common ancestor container
  166. const range = document.createRange();
  167. range.setStart(start, 0);
  168. range.setEnd(end, 0);
  169. const { commonAncestorContainer } = range;
  170. // Both nodes are inside #document
  171. if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) {
  172. if (isOffsetContainer(commonAncestorContainer)) {
  173. return commonAncestorContainer;
  174. }
  175. return getOffsetParent(commonAncestorContainer);
  176. }
  177. // one of the nodes is inside shadowDOM, find which one
  178. const element1root = getRoot(element1);
  179. if (element1root.host) {
  180. return findCommonOffsetParent(element1root.host, element2);
  181. } else {
  182. return findCommonOffsetParent(element1, getRoot(element2).host);
  183. }
  184. }
  185. /**
  186. * Gets the scroll value of the given element in the given side (top and left)
  187. * @method
  188. * @memberof Popper.Utils
  189. * @argument {Element} element
  190. * @argument {String} side `top` or `left`
  191. * @returns {number} amount of scrolled pixels
  192. */
  193. function getScroll(element, side = 'top') {
  194. const upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft';
  195. const nodeName = element.nodeName;
  196. if (nodeName === 'BODY' || nodeName === 'HTML') {
  197. const html = element.ownerDocument.documentElement;
  198. const scrollingElement = element.ownerDocument.scrollingElement || html;
  199. return scrollingElement[upperSide];
  200. }
  201. return element[upperSide];
  202. }
  203. /*
  204. * Sum or subtract the element scroll values (left and top) from a given rect object
  205. * @method
  206. * @memberof Popper.Utils
  207. * @param {Object} rect - Rect object you want to change
  208. * @param {HTMLElement} element - The element from the function reads the scroll values
  209. * @param {Boolean} subtract - set to true if you want to subtract the scroll values
  210. * @return {Object} rect - The modifier rect object
  211. */
  212. function includeScroll(rect, element, subtract = false) {
  213. const scrollTop = getScroll(element, 'top');
  214. const scrollLeft = getScroll(element, 'left');
  215. const modifier = subtract ? -1 : 1;
  216. rect.top += scrollTop * modifier;
  217. rect.bottom += scrollTop * modifier;
  218. rect.left += scrollLeft * modifier;
  219. rect.right += scrollLeft * modifier;
  220. return rect;
  221. }
  222. /*
  223. * Helper to detect borders of a given element
  224. * @method
  225. * @memberof Popper.Utils
  226. * @param {CSSStyleDeclaration} styles
  227. * Result of `getStyleComputedProperty` on the given element
  228. * @param {String} axis - `x` or `y`
  229. * @return {number} borders - The borders size of the given axis
  230. */
  231. function getBordersSize(styles, axis) {
  232. const sideA = axis === 'x' ? 'Left' : 'Top';
  233. const sideB = sideA === 'Left' ? 'Right' : 'Bottom';
  234. return parseFloat(styles[`border${sideA}Width`], 10) + parseFloat(styles[`border${sideB}Width`], 10);
  235. }
  236. function getSize(axis, body, html, computedStyle) {
  237. return Math.max(body[`offset${axis}`], body[`scroll${axis}`], html[`client${axis}`], html[`offset${axis}`], html[`scroll${axis}`], isIE(10) ? parseInt(html[`offset${axis}`]) + parseInt(computedStyle[`margin${axis === 'Height' ? 'Top' : 'Left'}`]) + parseInt(computedStyle[`margin${axis === 'Height' ? 'Bottom' : 'Right'}`]) : 0);
  238. }
  239. function getWindowSizes(document) {
  240. const body = document.body;
  241. const html = document.documentElement;
  242. const computedStyle = isIE(10) && getComputedStyle(html);
  243. return {
  244. height: getSize('Height', body, html, computedStyle),
  245. width: getSize('Width', body, html, computedStyle)
  246. };
  247. }
  248. var _extends = Object.assign || function (target) {
  249. for (var i = 1; i < arguments.length; i++) {
  250. var source = arguments[i];
  251. for (var key in source) {
  252. if (Object.prototype.hasOwnProperty.call(source, key)) {
  253. target[key] = source[key];
  254. }
  255. }
  256. }
  257. return target;
  258. };
  259. /**
  260. * Given element offsets, generate an output similar to getBoundingClientRect
  261. * @method
  262. * @memberof Popper.Utils
  263. * @argument {Object} offsets
  264. * @returns {Object} ClientRect like output
  265. */
  266. function getClientRect(offsets) {
  267. return _extends({}, offsets, {
  268. right: offsets.left + offsets.width,
  269. bottom: offsets.top + offsets.height
  270. });
  271. }
  272. /**
  273. * Get bounding client rect of given element
  274. * @method
  275. * @memberof Popper.Utils
  276. * @param {HTMLElement} element
  277. * @return {Object} client rect
  278. */
  279. function getBoundingClientRect(element) {
  280. let rect = {};
  281. // IE10 10 FIX: Please, don't ask, the element isn't
  282. // considered in DOM in some circumstances...
  283. // This isn't reproducible in IE10 compatibility mode of IE11
  284. try {
  285. if (isIE(10)) {
  286. rect = element.getBoundingClientRect();
  287. const scrollTop = getScroll(element, 'top');
  288. const scrollLeft = getScroll(element, 'left');
  289. rect.top += scrollTop;
  290. rect.left += scrollLeft;
  291. rect.bottom += scrollTop;
  292. rect.right += scrollLeft;
  293. } else {
  294. rect = element.getBoundingClientRect();
  295. }
  296. } catch (e) {}
  297. const result = {
  298. left: rect.left,
  299. top: rect.top,
  300. width: rect.right - rect.left,
  301. height: rect.bottom - rect.top
  302. };
  303. // subtract scrollbar size from sizes
  304. const sizes = element.nodeName === 'HTML' ? getWindowSizes(element.ownerDocument) : {};
  305. const width = sizes.width || element.clientWidth || result.right - result.left;
  306. const height = sizes.height || element.clientHeight || result.bottom - result.top;
  307. let horizScrollbar = element.offsetWidth - width;
  308. let vertScrollbar = element.offsetHeight - height;
  309. // if an hypothetical scrollbar is detected, we must be sure it's not a `border`
  310. // we make this check conditional for performance reasons
  311. if (horizScrollbar || vertScrollbar) {
  312. const styles = getStyleComputedProperty(element);
  313. horizScrollbar -= getBordersSize(styles, 'x');
  314. vertScrollbar -= getBordersSize(styles, 'y');
  315. result.width -= horizScrollbar;
  316. result.height -= vertScrollbar;
  317. }
  318. return getClientRect(result);
  319. }
  320. function getOffsetRectRelativeToArbitraryNode(children, parent, fixedPosition = false) {
  321. const isIE10 = isIE(10);
  322. const isHTML = parent.nodeName === 'HTML';
  323. const childrenRect = getBoundingClientRect(children);
  324. const parentRect = getBoundingClientRect(parent);
  325. const scrollParent = getScrollParent(children);
  326. const styles = getStyleComputedProperty(parent);
  327. const borderTopWidth = parseFloat(styles.borderTopWidth, 10);
  328. const borderLeftWidth = parseFloat(styles.borderLeftWidth, 10);
  329. // In cases where the parent is fixed, we must ignore negative scroll in offset calc
  330. if (fixedPosition && isHTML) {
  331. parentRect.top = Math.max(parentRect.top, 0);
  332. parentRect.left = Math.max(parentRect.left, 0);
  333. }
  334. let offsets = getClientRect({
  335. top: childrenRect.top - parentRect.top - borderTopWidth,
  336. left: childrenRect.left - parentRect.left - borderLeftWidth,
  337. width: childrenRect.width,
  338. height: childrenRect.height
  339. });
  340. offsets.marginTop = 0;
  341. offsets.marginLeft = 0;
  342. // Subtract margins of documentElement in case it's being used as parent
  343. // we do this only on HTML because it's the only element that behaves
  344. // differently when margins are applied to it. The margins are included in
  345. // the box of the documentElement, in the other cases not.
  346. if (!isIE10 && isHTML) {
  347. const marginTop = parseFloat(styles.marginTop, 10);
  348. const marginLeft = parseFloat(styles.marginLeft, 10);
  349. offsets.top -= borderTopWidth - marginTop;
  350. offsets.bottom -= borderTopWidth - marginTop;
  351. offsets.left -= borderLeftWidth - marginLeft;
  352. offsets.right -= borderLeftWidth - marginLeft;
  353. // Attach marginTop and marginLeft because in some circumstances we may need them
  354. offsets.marginTop = marginTop;
  355. offsets.marginLeft = marginLeft;
  356. }
  357. if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') {
  358. offsets = includeScroll(offsets, parent);
  359. }
  360. return offsets;
  361. }
  362. function getViewportOffsetRectRelativeToArtbitraryNode(element, excludeScroll = false) {
  363. const html = element.ownerDocument.documentElement;
  364. const relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html);
  365. const width = Math.max(html.clientWidth, window.innerWidth || 0);
  366. const height = Math.max(html.clientHeight, window.innerHeight || 0);
  367. const scrollTop = !excludeScroll ? getScroll(html) : 0;
  368. const scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0;
  369. const offset = {
  370. top: scrollTop - relativeOffset.top + relativeOffset.marginTop,
  371. left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft,
  372. width,
  373. height
  374. };
  375. return getClientRect(offset);
  376. }
  377. /**
  378. * Check if the given element is fixed or is inside a fixed parent
  379. * @method
  380. * @memberof Popper.Utils
  381. * @argument {Element} element
  382. * @argument {Element} customContainer
  383. * @returns {Boolean} answer to "isFixed?"
  384. */
  385. function isFixed(element) {
  386. const nodeName = element.nodeName;
  387. if (nodeName === 'BODY' || nodeName === 'HTML') {
  388. return false;
  389. }
  390. if (getStyleComputedProperty(element, 'position') === 'fixed') {
  391. return true;
  392. }
  393. const parentNode = getParentNode(element);
  394. if (!parentNode) {
  395. return false;
  396. }
  397. return isFixed(parentNode);
  398. }
  399. /**
  400. * Finds the first parent of an element that has a transformed property defined
  401. * @method
  402. * @memberof Popper.Utils
  403. * @argument {Element} element
  404. * @returns {Element} first transformed parent or documentElement
  405. */
  406. function getFixedPositionOffsetParent(element) {
  407. // This check is needed to avoid errors in case one of the elements isn't defined for any reason
  408. if (!element || !element.parentElement || isIE()) {
  409. return document.documentElement;
  410. }
  411. let el = element.parentElement;
  412. while (el && getStyleComputedProperty(el, 'transform') === 'none') {
  413. el = el.parentElement;
  414. }
  415. return el || document.documentElement;
  416. }
  417. /**
  418. * Computed the boundaries limits and return them
  419. * @method
  420. * @memberof Popper.Utils
  421. * @param {HTMLElement} popper
  422. * @param {HTMLElement} reference
  423. * @param {number} padding
  424. * @param {HTMLElement} boundariesElement - Element used to define the boundaries
  425. * @param {Boolean} fixedPosition - Is in fixed position mode
  426. * @returns {Object} Coordinates of the boundaries
  427. */
  428. function getBoundaries(popper, reference, padding, boundariesElement, fixedPosition = false) {
  429. // NOTE: 1 DOM access here
  430. let boundaries = { top: 0, left: 0 };
  431. const offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
  432. // Handle viewport case
  433. if (boundariesElement === 'viewport') {
  434. boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition);
  435. } else {
  436. // Handle other cases based on DOM element used as boundaries
  437. let boundariesNode;
  438. if (boundariesElement === 'scrollParent') {
  439. boundariesNode = getScrollParent(getParentNode(reference));
  440. if (boundariesNode.nodeName === 'BODY') {
  441. boundariesNode = popper.ownerDocument.documentElement;
  442. }
  443. } else if (boundariesElement === 'window') {
  444. boundariesNode = popper.ownerDocument.documentElement;
  445. } else {
  446. boundariesNode = boundariesElement;
  447. }
  448. const offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition);
  449. // In case of HTML, we need a different computation
  450. if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) {
  451. const { height, width } = getWindowSizes(popper.ownerDocument);
  452. boundaries.top += offsets.top - offsets.marginTop;
  453. boundaries.bottom = height + offsets.top;
  454. boundaries.left += offsets.left - offsets.marginLeft;
  455. boundaries.right = width + offsets.left;
  456. } else {
  457. // for all the other DOM elements, this one is good
  458. boundaries = offsets;
  459. }
  460. }
  461. // Add paddings
  462. padding = padding || 0;
  463. const isPaddingNumber = typeof padding === 'number';
  464. boundaries.left += isPaddingNumber ? padding : padding.left || 0;
  465. boundaries.top += isPaddingNumber ? padding : padding.top || 0;
  466. boundaries.right -= isPaddingNumber ? padding : padding.right || 0;
  467. boundaries.bottom -= isPaddingNumber ? padding : padding.bottom || 0;
  468. return boundaries;
  469. }
  470. function getArea({ width, height }) {
  471. return width * height;
  472. }
  473. /**
  474. * Utility used to transform the `auto` placement to the placement with more
  475. * available space.
  476. * @method
  477. * @memberof Popper.Utils
  478. * @argument {Object} data - The data object generated by update method
  479. * @argument {Object} options - Modifiers configuration and options
  480. * @returns {Object} The data object, properly modified
  481. */
  482. function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement, padding = 0) {
  483. if (placement.indexOf('auto') === -1) {
  484. return placement;
  485. }
  486. const boundaries = getBoundaries(popper, reference, padding, boundariesElement);
  487. const rects = {
  488. top: {
  489. width: boundaries.width,
  490. height: refRect.top - boundaries.top
  491. },
  492. right: {
  493. width: boundaries.right - refRect.right,
  494. height: boundaries.height
  495. },
  496. bottom: {
  497. width: boundaries.width,
  498. height: boundaries.bottom - refRect.bottom
  499. },
  500. left: {
  501. width: refRect.left - boundaries.left,
  502. height: boundaries.height
  503. }
  504. };
  505. const sortedAreas = Object.keys(rects).map(key => _extends({
  506. key
  507. }, rects[key], {
  508. area: getArea(rects[key])
  509. })).sort((a, b) => b.area - a.area);
  510. const filteredAreas = sortedAreas.filter(({ width, height }) => width >= popper.clientWidth && height >= popper.clientHeight);
  511. const computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key;
  512. const variation = placement.split('-')[1];
  513. return computedPlacement + (variation ? `-${variation}` : '');
  514. }
  515. const longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox'];
  516. let timeoutDuration = 0;
  517. for (let i = 0; i < longerTimeoutBrowsers.length; i += 1) {
  518. if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) {
  519. timeoutDuration = 1;
  520. break;
  521. }
  522. }
  523. function microtaskDebounce(fn) {
  524. let called = false;
  525. return () => {
  526. if (called) {
  527. return;
  528. }
  529. called = true;
  530. window.Promise.resolve().then(() => {
  531. called = false;
  532. fn();
  533. });
  534. };
  535. }
  536. function taskDebounce(fn) {
  537. let scheduled = false;
  538. return () => {
  539. if (!scheduled) {
  540. scheduled = true;
  541. setTimeout(() => {
  542. scheduled = false;
  543. fn();
  544. }, timeoutDuration);
  545. }
  546. };
  547. }
  548. const supportsMicroTasks = isBrowser && window.Promise;
  549. /**
  550. * Create a debounced version of a method, that's asynchronously deferred
  551. * but called in the minimum time possible.
  552. *
  553. * @method
  554. * @memberof Popper.Utils
  555. * @argument {Function} fn
  556. * @returns {Function}
  557. */
  558. var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce;
  559. /**
  560. * Mimics the `find` method of Array
  561. * @method
  562. * @memberof Popper.Utils
  563. * @argument {Array} arr
  564. * @argument prop
  565. * @argument value
  566. * @returns index or -1
  567. */
  568. function find(arr, check) {
  569. // use native find if supported
  570. if (Array.prototype.find) {
  571. return arr.find(check);
  572. }
  573. // use `filter` to obtain the same behavior of `find`
  574. return arr.filter(check)[0];
  575. }
  576. /**
  577. * Return the index of the matching object
  578. * @method
  579. * @memberof Popper.Utils
  580. * @argument {Array} arr
  581. * @argument prop
  582. * @argument value
  583. * @returns index or -1
  584. */
  585. function findIndex(arr, prop, value) {
  586. // use native findIndex if supported
  587. if (Array.prototype.findIndex) {
  588. return arr.findIndex(cur => cur[prop] === value);
  589. }
  590. // use `find` + `indexOf` if `findIndex` isn't supported
  591. const match = find(arr, obj => obj[prop] === value);
  592. return arr.indexOf(match);
  593. }
  594. /**
  595. * Get the position of the given element, relative to its offset parent
  596. * @method
  597. * @memberof Popper.Utils
  598. * @param {Element} element
  599. * @return {Object} position - Coordinates of the element and its `scrollTop`
  600. */
  601. function getOffsetRect(element) {
  602. let elementRect;
  603. if (element.nodeName === 'HTML') {
  604. const { width, height } = getWindowSizes(element.ownerDocument);
  605. elementRect = {
  606. width,
  607. height,
  608. left: 0,
  609. top: 0
  610. };
  611. } else {
  612. elementRect = {
  613. width: element.offsetWidth,
  614. height: element.offsetHeight,
  615. left: element.offsetLeft,
  616. top: element.offsetTop
  617. };
  618. }
  619. // position
  620. return getClientRect(elementRect);
  621. }
  622. /**
  623. * Get the outer sizes of the given element (offset size + margins)
  624. * @method
  625. * @memberof Popper.Utils
  626. * @argument {Element} element
  627. * @returns {Object} object containing width and height properties
  628. */
  629. function getOuterSizes(element) {
  630. const window = element.ownerDocument.defaultView;
  631. const styles = window.getComputedStyle(element);
  632. const x = parseFloat(styles.marginTop || 0) + parseFloat(styles.marginBottom || 0);
  633. const y = parseFloat(styles.marginLeft || 0) + parseFloat(styles.marginRight || 0);
  634. const result = {
  635. width: element.offsetWidth + y,
  636. height: element.offsetHeight + x
  637. };
  638. return result;
  639. }
  640. /**
  641. * Get the opposite placement of the given one
  642. * @method
  643. * @memberof Popper.Utils
  644. * @argument {String} placement
  645. * @returns {String} flipped placement
  646. */
  647. function getOppositePlacement(placement) {
  648. const hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
  649. return placement.replace(/left|right|bottom|top/g, matched => hash[matched]);
  650. }
  651. /**
  652. * Get offsets to the popper
  653. * @method
  654. * @memberof Popper.Utils
  655. * @param {Object} position - CSS position the Popper will get applied
  656. * @param {HTMLElement} popper - the popper element
  657. * @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this)
  658. * @param {String} placement - one of the valid placement options
  659. * @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper
  660. */
  661. function getPopperOffsets(popper, referenceOffsets, placement) {
  662. placement = placement.split('-')[0];
  663. // Get popper node sizes
  664. const popperRect = getOuterSizes(popper);
  665. // Add position, width and height to our offsets object
  666. const popperOffsets = {
  667. width: popperRect.width,
  668. height: popperRect.height
  669. };
  670. // depending by the popper placement we have to compute its offsets slightly differently
  671. const isHoriz = ['right', 'left'].indexOf(placement) !== -1;
  672. const mainSide = isHoriz ? 'top' : 'left';
  673. const secondarySide = isHoriz ? 'left' : 'top';
  674. const measurement = isHoriz ? 'height' : 'width';
  675. const secondaryMeasurement = !isHoriz ? 'height' : 'width';
  676. popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2;
  677. if (placement === secondarySide) {
  678. popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement];
  679. } else {
  680. popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)];
  681. }
  682. return popperOffsets;
  683. }
  684. /**
  685. * Get offsets to the reference element
  686. * @method
  687. * @memberof Popper.Utils
  688. * @param {Object} state
  689. * @param {Element} popper - the popper element
  690. * @param {Element} reference - the reference element (the popper will be relative to this)
  691. * @param {Element} fixedPosition - is in fixed position mode
  692. * @returns {Object} An object containing the offsets which will be applied to the popper
  693. */
  694. function getReferenceOffsets(state, popper, reference, fixedPosition = null) {
  695. const commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference);
  696. return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition);
  697. }
  698. /**
  699. * Get the prefixed supported property name
  700. * @method
  701. * @memberof Popper.Utils
  702. * @argument {String} property (camelCase)
  703. * @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix)
  704. */
  705. function getSupportedPropertyName(property) {
  706. const prefixes = [false, 'ms', 'Webkit', 'Moz', 'O'];
  707. const upperProp = property.charAt(0).toUpperCase() + property.slice(1);
  708. for (let i = 0; i < prefixes.length; i++) {
  709. const prefix = prefixes[i];
  710. const toCheck = prefix ? `${prefix}${upperProp}` : property;
  711. if (typeof document.body.style[toCheck] !== 'undefined') {
  712. return toCheck;
  713. }
  714. }
  715. return null;
  716. }
  717. /**
  718. * Check if the given variable is a function
  719. * @method
  720. * @memberof Popper.Utils
  721. * @argument {Any} functionToCheck - variable to check
  722. * @returns {Boolean} answer to: is a function?
  723. */
  724. function isFunction(functionToCheck) {
  725. const getType = {};
  726. return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
  727. }
  728. /**
  729. * Helper used to know if the given modifier is enabled.
  730. * @method
  731. * @memberof Popper.Utils
  732. * @returns {Boolean}
  733. */
  734. function isModifierEnabled(modifiers, modifierName) {
  735. return modifiers.some(({ name, enabled }) => enabled && name === modifierName);
  736. }
  737. /**
  738. * Helper used to know if the given modifier depends from another one.<br />
  739. * It checks if the needed modifier is listed and enabled.
  740. * @method
  741. * @memberof Popper.Utils
  742. * @param {Array} modifiers - list of modifiers
  743. * @param {String} requestingName - name of requesting modifier
  744. * @param {String} requestedName - name of requested modifier
  745. * @returns {Boolean}
  746. */
  747. function isModifierRequired(modifiers, requestingName, requestedName) {
  748. const requesting = find(modifiers, ({ name }) => name === requestingName);
  749. const isRequired = !!requesting && modifiers.some(modifier => {
  750. return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order;
  751. });
  752. if (!isRequired) {
  753. const requesting = `\`${requestingName}\``;
  754. const requested = `\`${requestedName}\``;
  755. console.warn(`${requested} modifier is required by ${requesting} modifier in order to work, be sure to include it before ${requesting}!`);
  756. }
  757. return isRequired;
  758. }
  759. /**
  760. * Tells if a given input is a number
  761. * @method
  762. * @memberof Popper.Utils
  763. * @param {*} input to check
  764. * @return {Boolean}
  765. */
  766. function isNumeric(n) {
  767. return n !== '' && !isNaN(parseFloat(n)) && isFinite(n);
  768. }
  769. /**
  770. * Get the window associated with the element
  771. * @argument {Element} element
  772. * @returns {Window}
  773. */
  774. function getWindow(element) {
  775. const ownerDocument = element.ownerDocument;
  776. return ownerDocument ? ownerDocument.defaultView : window;
  777. }
  778. /**
  779. * Remove event listeners used to update the popper position
  780. * @method
  781. * @memberof Popper.Utils
  782. * @private
  783. */
  784. function removeEventListeners(reference, state) {
  785. // Remove resize event listener on window
  786. getWindow(reference).removeEventListener('resize', state.updateBound);
  787. // Remove scroll event listener on scroll parents
  788. state.scrollParents.forEach(target => {
  789. target.removeEventListener('scroll', state.updateBound);
  790. });
  791. // Reset state
  792. state.updateBound = null;
  793. state.scrollParents = [];
  794. state.scrollElement = null;
  795. state.eventsEnabled = false;
  796. return state;
  797. }
  798. /**
  799. * Loop trough the list of modifiers and run them in order,
  800. * each of them will then edit the data object.
  801. * @method
  802. * @memberof Popper.Utils
  803. * @param {dataObject} data
  804. * @param {Array} modifiers
  805. * @param {String} ends - Optional modifier name used as stopper
  806. * @returns {dataObject}
  807. */
  808. function runModifiers(modifiers, data, ends) {
  809. const modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends));
  810. modifiersToRun.forEach(modifier => {
  811. if (modifier['function']) {
  812. // eslint-disable-line dot-notation
  813. console.warn('`modifier.function` is deprecated, use `modifier.fn`!');
  814. }
  815. const fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation
  816. if (modifier.enabled && isFunction(fn)) {
  817. // Add properties to offsets to make them a complete clientRect object
  818. // we do this before each modifier to make sure the previous one doesn't
  819. // mess with these values
  820. data.offsets.popper = getClientRect(data.offsets.popper);
  821. data.offsets.reference = getClientRect(data.offsets.reference);
  822. data = fn(data, modifier);
  823. }
  824. });
  825. return data;
  826. }
  827. /**
  828. * Set the attributes to the given popper
  829. * @method
  830. * @memberof Popper.Utils
  831. * @argument {Element} element - Element to apply the attributes to
  832. * @argument {Object} styles
  833. * Object with a list of properties and values which will be applied to the element
  834. */
  835. function setAttributes(element, attributes) {
  836. Object.keys(attributes).forEach(function (prop) {
  837. const value = attributes[prop];
  838. if (value !== false) {
  839. element.setAttribute(prop, attributes[prop]);
  840. } else {
  841. element.removeAttribute(prop);
  842. }
  843. });
  844. }
  845. /**
  846. * Set the style to the given popper
  847. * @method
  848. * @memberof Popper.Utils
  849. * @argument {Element} element - Element to apply the style to
  850. * @argument {Object} styles
  851. * Object with a list of properties and values which will be applied to the element
  852. */
  853. function setStyles(element, styles) {
  854. Object.keys(styles).forEach(prop => {
  855. let unit = '';
  856. // add unit if the value is numeric and is one of the following
  857. if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) {
  858. unit = 'px';
  859. }
  860. element.style[prop] = styles[prop] + unit;
  861. });
  862. }
  863. function attachToScrollParents(scrollParent, event, callback, scrollParents) {
  864. const isBody = scrollParent.nodeName === 'BODY';
  865. const target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent;
  866. target.addEventListener(event, callback, { passive: true });
  867. if (!isBody) {
  868. attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents);
  869. }
  870. scrollParents.push(target);
  871. }
  872. /**
  873. * Setup needed event listeners used to update the popper position
  874. * @method
  875. * @memberof Popper.Utils
  876. * @private
  877. */
  878. function setupEventListeners(reference, options, state, updateBound) {
  879. // Resize event listener on window
  880. state.updateBound = updateBound;
  881. getWindow(reference).addEventListener('resize', state.updateBound, { passive: true });
  882. // Scroll event listener on scroll parents
  883. const scrollElement = getScrollParent(reference);
  884. attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents);
  885. state.scrollElement = scrollElement;
  886. state.eventsEnabled = true;
  887. return state;
  888. }
  889. // This is here just for backward compatibility with versions lower than v1.10.3
  890. // you should import the utilities using named exports, if you want them all use:
  891. // ```
  892. // import * as PopperUtils from 'popper-utils';
  893. // ```
  894. // The default export will be removed in the next major version.
  895. var index = {
  896. computeAutoPlacement,
  897. debounce,
  898. findIndex,
  899. getBordersSize,
  900. getBoundaries,
  901. getBoundingClientRect,
  902. getClientRect,
  903. getOffsetParent,
  904. getOffsetRect,
  905. getOffsetRectRelativeToArbitraryNode,
  906. getOuterSizes,
  907. getParentNode,
  908. getPopperOffsets,
  909. getReferenceOffsets,
  910. getScroll,
  911. getScrollParent,
  912. getStyleComputedProperty,
  913. getSupportedPropertyName,
  914. getWindowSizes,
  915. isFixed,
  916. isFunction,
  917. isModifierEnabled,
  918. isModifierRequired,
  919. isNumeric,
  920. removeEventListeners,
  921. runModifiers,
  922. setAttributes,
  923. setStyles,
  924. setupEventListeners
  925. };
  926. export { computeAutoPlacement, debounce, findIndex, getBordersSize, getBoundaries, getBoundingClientRect, getClientRect, getOffsetParent, getOffsetRect, getOffsetRectRelativeToArbitraryNode, getOuterSizes, getParentNode, getPopperOffsets, getReferenceOffsets, getScroll, getScrollParent, getStyleComputedProperty, getSupportedPropertyName, getWindowSizes, isFixed, isFunction, isModifierEnabled, isModifierRequired, isNumeric, removeEventListeners, runModifiers, setAttributes, setStyles, setupEventListeners };
  927. export default index;
  928. //# sourceMappingURL=popper-utils.js.map