1 | /**
|
2 | * @fileOverview Kickass library to create and place poppers near their reference elements.
|
3 | * @version {{version}}
|
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 | //
|
27 | // Cross module loader
|
28 | // Supported: Node, AMD, Browser globals
|
29 | //
|
30 | ;(function (root, factory) {
|
31 | if (typeof define === 'function' && define.amd) {
|
32 | // AMD. Register as an anonymous module.
|
33 | define(factory);
|
34 | } else if (typeof module === 'object' && module.exports) {
|
35 | // Node. Does not work with strict CommonJS, but
|
36 | // only CommonJS-like environments that support module.exports,
|
37 | // like Node.
|
38 | module.exports = factory();
|
39 | } else {
|
40 | // Browser globals (root is window)
|
41 | root.Popper = factory();
|
42 | }
|
43 | }(this, function () {
|
44 |
|
45 | ;
|
46 |
|
47 | var root = window;
|
48 |
|
49 | // default options
|
50 | var DEFAULTS = {
|
51 | // placement of the popper
|
52 | placement: 'bottom',
|
53 |
|
54 | gpuAcceleration: true,
|
55 |
|
56 | // shift popper from its origin by the given amount of pixels (can be negative)
|
57 | offset: 0,
|
58 |
|
59 | // the element which will act as boundary of the popper
|
60 | boundariesElement: 'viewport',
|
61 |
|
62 | // amount of pixel used to define a minimum distance between the boundaries and the popper
|
63 | boundariesPadding: 5,
|
64 |
|
65 | // popper will try to prevent overflow following this order,
|
66 | // by default, then, it could overflow on the left and on top of the boundariesElement
|
67 | preventOverflowOrder: ['left', 'right', 'top', 'bottom'],
|
68 |
|
69 | // the behavior used by flip to change the placement of the popper
|
70 | flipBehavior: 'flip',
|
71 |
|
72 | arrowElement: '[x-arrow]',
|
73 |
|
74 | arrowOffset: 0,
|
75 |
|
76 | // list of functions used to modify the offsets before they are applied to the popper
|
77 | modifiers: [ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle'],
|
78 |
|
79 | modifiersIgnored: [],
|
80 |
|
81 | forceAbsolute: false
|
82 | };
|
83 |
|
84 | /**
|
85 | * Create a new Popper.js instance
|
86 | * @constructor Popper
|
87 | * @param {HTMLElement} reference - The reference element used to position the popper
|
88 | * @param {HTMLElement|Object} popper
|
89 | * The HTML element used as popper, or a configuration used to generate the popper.
|
90 | * @param {String} [popper.tagName='div'] The tag name of the generated popper.
|
91 | * @param {Array} [popper.classNames=['popper']] Array of classes to apply to the generated popper.
|
92 | * @param {Array} [popper.attributes] Array of attributes to apply, specify `attr:value` to assign a value to it.
|
93 | * @param {HTMLElement|String} [popper.parent=window.document.body] The parent element, given as HTMLElement or as query string.
|
94 | * @param {String} [popper.content=''] The content of the popper, it can be text, html, or node; if it is not text, set `contentType` to `html` or `node`.
|
95 | * @param {String} [popper.contentType='text'] If `html`, the `content` will be parsed as HTML. If `node`, it will be appended as-is.
|
96 | * @param {String} [popper.arrowTagName='div'] Same as `popper.tagName` but for the arrow element.
|
97 | * @param {Array} [popper.arrowClassNames='popper__arrow'] Same as `popper.classNames` but for the arrow element.
|
98 | * @param {String} [popper.arrowAttributes=['x-arrow']] Same as `popper.attributes` but for the arrow element.
|
99 | * @param {Object} options
|
100 | * @param {String} [options.placement=bottom]
|
101 | * Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -right),
|
102 | * left(-start, -end)`
|
103 | *
|
104 | * @param {HTMLElement|String} [options.arrowElement='[x-arrow]']
|
105 | * The DOM Node used as arrow for the popper, or a CSS selector used to get the DOM node. It must be child of
|
106 | * its parent Popper. Popper.js will apply to the given element the style required to align the arrow with its
|
107 | * reference element.
|
108 | * By default, it will look for a child node of the popper with the `x-arrow` attribute.
|
109 | *
|
110 | * @param {Boolean} [options.gpuAcceleration=true]
|
111 | * When this property is set to true, the popper position will be applied using CSS3 translate3d, allowing the
|
112 | * browser to use the GPU to accelerate the rendering.
|
113 | * If set to false, the popper will be placed using `top` and `left` properties, not using the GPU.
|
114 | *
|
115 | * @param {Number} [options.offset=0]
|
116 | * Amount of pixels the popper will be shifted (can be negative).
|
117 | *
|
118 | * @param {String|Element} [options.boundariesElement='viewport']
|
119 | * The element which will define the boundaries of the popper position, the popper will never be placed outside
|
120 | * of the defined boundaries (except if `keepTogether` is enabled)
|
121 | *
|
122 | * @param {Number} [options.boundariesPadding=5]
|
123 | * Additional padding for the boundaries
|
124 | *
|
125 | * @param {Array} [options.preventOverflowOrder=['left', 'right', 'top', 'bottom']]
|
126 | * Order used when Popper.js tries to avoid overflows from the boundaries, they will be checked in order,
|
127 | * this means that the last ones will never overflow
|
128 | *
|
129 | * @param {String|Array} [options.flipBehavior='flip']
|
130 | * The behavior used by the `flip` modifier to change the placement of the popper when the latter is trying to
|
131 | * overlap its reference element. Defining `flip` as value, the placement will be flipped on
|
132 | * its axis (`right - left`, `top - bottom`).
|
133 | * You can even pass an array of placements (eg: `['right', 'left', 'top']` ) to manually specify
|
134 | * how alter the placement when a flip is needed. (eg. in the above example, it would first flip from right to left,
|
135 | * then, if even in its new placement, the popper is overlapping its reference element, it will be moved to top)
|
136 | *
|
137 | * @param {Array} [options.modifiers=[ 'shift', 'offset', 'preventOverflow', 'keepTogether', 'arrow', 'flip', 'applyStyle']]
|
138 | * List of functions used to modify the data before they are applied to the popper, add your custom functions
|
139 | * to this array to edit the offsets and placement.
|
140 | * The function should reflect the @params and @returns of preventOverflow
|
141 | *
|
142 | * @param {Array} [options.modifiersIgnored=[]]
|
143 | * Put here any built-in modifier name you want to exclude from the modifiers list
|
144 | * The function should reflect the @params and @returns of preventOverflow
|
145 | *
|
146 | * @param {Boolean} [options.removeOnDestroy=false]
|
147 | * Set to true if you want to automatically remove the popper when you call the `destroy` method.
|
148 | */
|
149 | function Popper(reference, popper, options) {
|
150 | this._reference = reference.jquery ? reference[0] : reference;
|
151 | this.state = {};
|
152 |
|
153 | // if the popper variable is a configuration object, parse it to generate an HTMLElement
|
154 | // generate a default popper if is not defined
|
155 | var isNotDefined = typeof popper === 'undefined' || popper === null;
|
156 | var isConfig = popper && Object.prototype.toString.call(popper) === '[object Object]';
|
157 | if (isNotDefined || isConfig) {
|
158 | this._popper = this.parse(isConfig ? popper : {});
|
159 | }
|
160 | // otherwise, use the given HTMLElement as popper
|
161 | else {
|
162 | this._popper = popper.jquery ? popper[0] : popper;
|
163 | }
|
164 |
|
165 | // with {} we create a new object with the options inside it
|
166 | this._options = Object.assign({}, DEFAULTS, options);
|
167 |
|
168 | // refactoring modifiers' list
|
169 | this._options.modifiers = this._options.modifiers.map(function(modifier){
|
170 | // remove ignored modifiers
|
171 | if (this._options.modifiersIgnored.indexOf(modifier) !== -1) return;
|
172 |
|
173 | // set the x-placement attribute before everything else because it could be used to add margins to the popper
|
174 | // margins needs to be calculated to get the correct popper offsets
|
175 | if (modifier === 'applyStyle') {
|
176 | this._popper.setAttribute('x-placement', this._options.placement);
|
177 | }
|
178 |
|
179 | // return predefined modifier identified by string or keep the custom one
|
180 | return this.modifiers[modifier] || modifier;
|
181 | }.bind(this));
|
182 |
|
183 | // make sure to apply the popper position before any computation
|
184 | this.state.position = this._getPosition(this._popper, this._reference);
|
185 | setStyle(this._popper, { position: this.state.position, top: 0 });
|
186 |
|
187 | // fire the first update to position the popper in the right place
|
188 | this.update();
|
189 |
|
190 | // setup event listeners, they will take care of update the position in specific situations
|
191 | this._setupEventListeners();
|
192 | return this;
|
193 | }
|
194 |
|
195 |
|
196 | //
|
197 | // Methods
|
198 | //
|
199 | /**
|
200 | * Destroy the popper
|
201 | * @method
|
202 | * @memberof Popper
|
203 | */
|
204 | Popper.prototype.destroy = function() {
|
205 | this._popper.removeAttribute('x-placement');
|
206 | this._popper.style.left = '';
|
207 | this._popper.style.position = '';
|
208 | this._popper.style.top = '';
|
209 | this._popper.style[getSupportedPropertyName('transform')] = '';
|
210 | this._removeEventListeners();
|
211 |
|
212 | // remove the popper if user explicity asked for the deletion on destroy
|
213 | if (this._options.removeOnDestroy) {
|
214 | this._popper.remove();
|
215 | }
|
216 | return this;
|
217 | };
|
218 |
|
219 | /**
|
220 | * Updates the position of the popper, computing the new offsets and applying the new style
|
221 | * @method
|
222 | * @memberof Popper
|
223 | */
|
224 | Popper.prototype.update = function() {
|
225 | var data = { instance: this, styles: {} };
|
226 |
|
227 | // store placement inside the data object, modifiers will be able to edit `placement` if needed
|
228 | // and refer to _originalPlacement to know the original value
|
229 | data.placement = this._options.placement;
|
230 | data._originalPlacement = this._options.placement;
|
231 |
|
232 | // compute the popper and reference offsets and put them inside data.offsets
|
233 | data.offsets = this._getOffsets(this._popper, this._reference, data.placement);
|
234 |
|
235 | // get boundaries
|
236 | data.boundaries = this._getBoundaries(data, this._options.boundariesPadding, this._options.boundariesElement);
|
237 |
|
238 | data = this.runModifiers(data, this._options.modifiers);
|
239 |
|
240 | if (typeof this.state.updateCallback === 'function') {
|
241 | this.state.updateCallback(data);
|
242 | }
|
243 | };
|
244 |
|
245 | /**
|
246 | * If a function is passed, it will be executed after the initialization of popper with as first argument the Popper instance.
|
247 | * @method
|
248 | * @memberof Popper
|
249 | * @param {Function} callback
|
250 | */
|
251 | Popper.prototype.onCreate = function(callback) {
|
252 | // the createCallbacks return as first argument the popper instance
|
253 | callback(this);
|
254 | return this;
|
255 | };
|
256 |
|
257 | /**
|
258 | * If a function is passed, it will be executed after each update of popper with as first argument the set of coordinates and informations
|
259 | * used to style popper and its arrow.
|
260 | * NOTE: it doesn't get fired on the first call of the `Popper.update()` method inside the `Popper` constructor!
|
261 | * @method
|
262 | * @memberof Popper
|
263 | * @param {Function} callback
|
264 | */
|
265 | Popper.prototype.onUpdate = function(callback) {
|
266 | this.state.updateCallback = callback;
|
267 | return this;
|
268 | };
|
269 |
|
270 | /**
|
271 | * Helper used to generate poppers from a configuration file
|
272 | * @method
|
273 | * @memberof Popper
|
274 | * @param config {Object} configuration
|
275 | * @returns {HTMLElement} popper
|
276 | */
|
277 | Popper.prototype.parse = function(config) {
|
278 | var defaultConfig = {
|
279 | tagName: 'div',
|
280 | classNames: [ 'popper' ],
|
281 | attributes: [],
|
282 | parent: root.document.body,
|
283 | content: '',
|
284 | contentType: 'text',
|
285 | arrowTagName: 'div',
|
286 | arrowClassNames: [ 'popper__arrow' ],
|
287 | arrowAttributes: [ 'x-arrow']
|
288 | };
|
289 | config = Object.assign({}, defaultConfig, config);
|
290 |
|
291 | var d = root.document;
|
292 |
|
293 | var popper = d.createElement(config.tagName);
|
294 | addClassNames(popper, config.classNames);
|
295 | addAttributes(popper, config.attributes);
|
296 | if (config.contentType === 'node') {
|
297 | popper.appendChild(config.content.jquery ? config.content[0] : config.content);
|
298 | }else if (config.contentType === 'html') {
|
299 | popper.innerHTML = config.content;
|
300 | } else {
|
301 | popper.textContent = config.content;
|
302 | }
|
303 |
|
304 | if (config.arrowTagName) {
|
305 | var arrow = d.createElement(config.arrowTagName);
|
306 | addClassNames(arrow, config.arrowClassNames);
|
307 | addAttributes(arrow, config.arrowAttributes);
|
308 | popper.appendChild(arrow);
|
309 | }
|
310 |
|
311 | var parent = config.parent.jquery ? config.parent[0] : config.parent;
|
312 |
|
313 | // if the given parent is a string, use it to match an element
|
314 | // if more than one element is matched, the first one will be used as parent
|
315 | // if no elements are matched, the script will throw an error
|
316 | if (typeof parent === 'string') {
|
317 | parent = d.querySelectorAll(config.parent);
|
318 | if (parent.length > 1) {
|
319 | console.warn('WARNING: the given `parent` query(' + config.parent + ') matched more than one element, the first one will be used');
|
320 | }
|
321 | if (parent.length === 0) {
|
322 | throw 'ERROR: the given `parent` doesn\'t exists!';
|
323 | }
|
324 | parent = parent[0];
|
325 | }
|
326 | // if the given parent is a DOM nodes list or an array of nodes with more than one element,
|
327 | // the first one will be used as parent
|
328 | if (parent.length > 1 && parent instanceof Element === false) {
|
329 | console.warn('WARNING: you have passed as parent a list of elements, the first one will be used');
|
330 | parent = parent[0];
|
331 | }
|
332 |
|
333 | // append the generated popper to its parent
|
334 | parent.appendChild(popper);
|
335 |
|
336 | return popper;
|
337 |
|
338 | /**
|
339 | * Adds class names to the given element
|
340 | * @function
|
341 | * @ignore
|
342 | * @param {HTMLElement} target
|
343 | * @param {Array} classes
|
344 | */
|
345 | function addClassNames(element, classNames) {
|
346 | classNames.forEach(function(className) {
|
347 | element.classList.add(className);
|
348 | });
|
349 | }
|
350 |
|
351 | /**
|
352 | * Adds attributes to the given element
|
353 | * @function
|
354 | * @ignore
|
355 | * @param {HTMLElement} target
|
356 | * @param {Array} attributes
|
357 | * @example
|
358 | * addAttributes(element, [ 'data-info:foobar' ]);
|
359 | */
|
360 | function addAttributes(element, attributes) {
|
361 | attributes.forEach(function(attribute) {
|
362 | element.setAttribute(attribute.split(':')[0], attribute.split(':')[1] || '');
|
363 | });
|
364 | }
|
365 |
|
366 | };
|
367 |
|
368 | /**
|
369 | * Helper used to get the position which will be applied to the popper
|
370 | * @method
|
371 | * @memberof Popper
|
372 | * @param config {HTMLElement} popper element
|
373 | * @param reference {HTMLElement} reference element
|
374 | * @returns {String} position
|
375 | */
|
376 | Popper.prototype._getPosition = function(popper, reference) {
|
377 | var container = getOffsetParent(reference);
|
378 |
|
379 | if (this._options.forceAbsolute) {
|
380 | return 'absolute';
|
381 | }
|
382 |
|
383 | // Decide if the popper will be fixed
|
384 | // If the reference element is inside a fixed context, the popper will be fixed as well to allow them to scroll together
|
385 | var isParentFixed = isFixed(reference, container);
|
386 | return isParentFixed ? 'fixed' : 'absolute';
|
387 | };
|
388 |
|
389 | /**
|
390 | * Get offsets to the popper
|
391 | * @method
|
392 | * @memberof Popper
|
393 | * @access private
|
394 | * @param {Element} popper - the popper element
|
395 | * @param {Element} reference - the reference element (the popper will be relative to this)
|
396 | * @returns {Object} An object containing the offsets which will be applied to the popper
|
397 | */
|
398 | Popper.prototype._getOffsets = function(popper, reference, placement) {
|
399 | placement = placement.split('-')[0];
|
400 | var popperOffsets = {};
|
401 |
|
402 | popperOffsets.position = this.state.position;
|
403 | var isParentFixed = popperOffsets.position === 'fixed';
|
404 |
|
405 | //
|
406 | // Get reference element position
|
407 | //
|
408 | var referenceOffsets = getOffsetRectRelativeToCustomParent(reference, getOffsetParent(popper), isParentFixed);
|
409 |
|
410 | //
|
411 | // Get popper sizes
|
412 | //
|
413 | var popperRect = getOuterSizes(popper);
|
414 |
|
415 | //
|
416 | // Compute offsets of popper
|
417 | //
|
418 |
|
419 | // depending by the popper placement we have to compute its offsets slightly differently
|
420 | if (['right', 'left'].indexOf(placement) !== -1) {
|
421 | popperOffsets.top = referenceOffsets.top + referenceOffsets.height / 2 - popperRect.height / 2;
|
422 | if (placement === 'left') {
|
423 | popperOffsets.left = referenceOffsets.left - popperRect.width;
|
424 | } else {
|
425 | popperOffsets.left = referenceOffsets.right;
|
426 | }
|
427 | } else {
|
428 | popperOffsets.left = referenceOffsets.left + referenceOffsets.width / 2 - popperRect.width / 2;
|
429 | if (placement === 'top') {
|
430 | popperOffsets.top = referenceOffsets.top - popperRect.height;
|
431 | } else {
|
432 | popperOffsets.top = referenceOffsets.bottom;
|
433 | }
|
434 | }
|
435 |
|
436 | // Add width and height to our offsets object
|
437 | popperOffsets.width = popperRect.width;
|
438 | popperOffsets.height = popperRect.height;
|
439 |
|
440 | return {
|
441 | popper: popperOffsets,
|
442 | reference: referenceOffsets
|
443 | };
|
444 | };
|
445 |
|
446 |
|
447 | /**
|
448 | * Setup needed event listeners used to update the popper position
|
449 | * @method
|
450 | * @memberof Popper
|
451 | * @access private
|
452 | */
|
453 | Popper.prototype._setupEventListeners = function() {
|
454 | // NOTE: 1 DOM access here
|
455 | this.state.updateBound = this.update.bind(this);
|
456 | root.addEventListener('resize', this.state.updateBound);
|
457 | // if the boundariesElement is window we don't need to listen for the scroll event
|
458 | if (this._options.boundariesElement !== 'window') {
|
459 | var target = getScrollParent(this._reference);
|
460 | // here it could be both `body` or `documentElement` thanks to Firefox, we then check both
|
461 | if (target === root.document.body || target === root.document.documentElement) {
|
462 | target = root;
|
463 | }
|
464 | target.addEventListener('scroll', this.state.updateBound);
|
465 | this.state.scrollTarget = target;
|
466 | }
|
467 | };
|
468 |
|
469 | /**
|
470 | * Remove event listeners used to update the popper position
|
471 | * @method
|
472 | * @memberof Popper
|
473 | * @access private
|
474 | */
|
475 | Popper.prototype._removeEventListeners = function() {
|
476 | // NOTE: 1 DOM access here
|
477 | root.removeEventListener('resize', this.state.updateBound);
|
478 | if (this._options.boundariesElement !== 'window' && this.state.scrollTarget) {
|
479 | this.state.scrollTarget.removeEventListener('scroll', this.state.updateBound);
|
480 | this.state.scrollTarget = null;
|
481 | }
|
482 | this.state.updateBound = null;
|
483 | };
|
484 |
|
485 | /**
|
486 | * Computed the boundaries limits and return them
|
487 | * @method
|
488 | * @memberof Popper
|
489 | * @access private
|
490 | * @param {Object} data - Object containing the property "offsets" generated by `_getOffsets`
|
491 | * @param {Number} padding - Boundaries padding
|
492 | * @param {Element} boundariesElement - Element used to define the boundaries
|
493 | * @returns {Object} Coordinates of the boundaries
|
494 | */
|
495 | Popper.prototype._getBoundaries = function(data, padding, boundariesElement) {
|
496 | // NOTE: 1 DOM access here
|
497 | var boundaries = {};
|
498 | var width, height;
|
499 | if (boundariesElement === 'window') {
|
500 | var body = root.document.body,
|
501 | html = root.document.documentElement;
|
502 |
|
503 | height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
|
504 | width = Math.max( body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth );
|
505 |
|
506 | boundaries = {
|
507 | top: 0,
|
508 | right: width,
|
509 | bottom: height,
|
510 | left: 0
|
511 | };
|
512 | } else if (boundariesElement === 'viewport') {
|
513 | var offsetParent = getOffsetParent(this._popper);
|
514 | var scrollParent = getScrollParent(this._popper);
|
515 | var offsetParentRect = getOffsetRect(offsetParent);
|
516 |
|
517 | // Thanks the fucking native API, `document.body.scrollTop` & `document.documentElement.scrollTop`
|
518 | var getScrollTopValue = function (element) {
|
519 | return element == document.body ? Math.max(document.documentElement.scrollTop, document.body.scrollTop) : element.scrollTop;
|
520 | }
|
521 | var getScrollLeftValue = function (element) {
|
522 | return element == document.body ? Math.max(document.documentElement.scrollLeft, document.body.scrollLeft) : element.scrollLeft;
|
523 | }
|
524 |
|
525 | // if the popper is fixed we don't have to substract scrolling from the boundaries
|
526 | var scrollTop = data.offsets.popper.position === 'fixed' ? 0 : getScrollTopValue(scrollParent);
|
527 | var scrollLeft = data.offsets.popper.position === 'fixed' ? 0 : getScrollLeftValue(scrollParent);
|
528 |
|
529 | boundaries = {
|
530 | top: 0 - (offsetParentRect.top - scrollTop),
|
531 | right: root.document.documentElement.clientWidth - (offsetParentRect.left - scrollLeft),
|
532 | bottom: root.document.documentElement.clientHeight - (offsetParentRect.top - scrollTop),
|
533 | left: 0 - (offsetParentRect.left - scrollLeft)
|
534 | };
|
535 | } else {
|
536 | if (getOffsetParent(this._popper) === boundariesElement) {
|
537 | boundaries = {
|
538 | top: 0,
|
539 | left: 0,
|
540 | right: boundariesElement.clientWidth,
|
541 | bottom: boundariesElement.clientHeight
|
542 | };
|
543 | } else {
|
544 | boundaries = getOffsetRect(boundariesElement);
|
545 | }
|
546 | }
|
547 | boundaries.left += padding;
|
548 | boundaries.right -= padding;
|
549 | boundaries.top = boundaries.top + padding;
|
550 | boundaries.bottom = boundaries.bottom - padding;
|
551 | return boundaries;
|
552 | };
|
553 |
|
554 |
|
555 | /**
|
556 | * Loop trough the list of modifiers and run them in order, each of them will then edit the data object
|
557 | * @method
|
558 | * @memberof Popper
|
559 | * @access public
|
560 | * @param {Object} data
|
561 | * @param {Array} modifiers
|
562 | * @param {Function} ends
|
563 | */
|
564 | Popper.prototype.runModifiers = function(data, modifiers, ends) {
|
565 | var modifiersToRun = modifiers.slice();
|
566 | if (ends !== undefined) {
|
567 | modifiersToRun = this._options.modifiers.slice(0, getArrayKeyIndex(this._options.modifiers, ends));
|
568 | }
|
569 |
|
570 | modifiersToRun.forEach(function(modifier) {
|
571 | if (isFunction(modifier)) {
|
572 | data = modifier.call(this, data);
|
573 | }
|
574 | }.bind(this));
|
575 |
|
576 | return data;
|
577 | };
|
578 |
|
579 | /**
|
580 | * Helper used to know if the given modifier depends from another one.
|
581 | * @method
|
582 | * @memberof Popper
|
583 | * @param {String} requesting - name of requesting modifier
|
584 | * @param {String} requested - name of requested modifier
|
585 | * @returns {Boolean}
|
586 | */
|
587 | Popper.prototype.isModifierRequired = function(requesting, requested) {
|
588 | var index = getArrayKeyIndex(this._options.modifiers, requesting);
|
589 | return !!this._options.modifiers.slice(0, index).filter(function(modifier) {
|
590 | return modifier === requested;
|
591 | }).length;
|
592 | };
|
593 |
|
594 | //
|
595 | // Modifiers
|
596 | //
|
597 |
|
598 | /**
|
599 | * Modifiers list
|
600 | * @namespace Popper.modifiers
|
601 | * @memberof Popper
|
602 | * @type {Object}
|
603 | */
|
604 | Popper.prototype.modifiers = {};
|
605 |
|
606 | /**
|
607 | * Apply the computed styles to the popper element
|
608 | * @method
|
609 | * @memberof Popper.modifiers
|
610 | * @argument {Object} data - The data object generated by `update` method
|
611 | * @returns {Object} The same data object
|
612 | */
|
613 | Popper.prototype.modifiers.applyStyle = function(data) {
|
614 | // apply the final offsets to the popper
|
615 | // NOTE: 1 DOM access here
|
616 | var styles = {
|
617 | position: data.offsets.popper.position
|
618 | };
|
619 |
|
620 | // round top and left to avoid blurry text
|
621 | var left = Math.round(data.offsets.popper.left);
|
622 | var top = Math.round(data.offsets.popper.top);
|
623 |
|
624 | // if gpuAcceleration is set to true and transform is supported, we use `translate3d` to apply the position to the popper
|
625 | // we automatically use the supported prefixed version if needed
|
626 | var prefixedProperty;
|
627 | if (this._options.gpuAcceleration && (prefixedProperty = getSupportedPropertyName('transform'))) {
|
628 | styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
|
629 | styles.top = 0;
|
630 | styles.left = 0;
|
631 | }
|
632 | // othwerise, we use the standard `left` and `top` properties
|
633 | else {
|
634 | styles.left =left;
|
635 | styles.top = top;
|
636 | }
|
637 |
|
638 | // any property present in `data.styles` will be applied to the popper,
|
639 | // in this way we can make the 3rd party modifiers add custom styles to it
|
640 | // Be aware, modifiers could override the properties defined in the previous
|
641 | // lines of this modifier!
|
642 | Object.assign(styles, data.styles);
|
643 |
|
644 | setStyle(this._popper, styles);
|
645 |
|
646 | // set an attribute which will be useful to style the tooltip (use it to properly position its arrow)
|
647 | // NOTE: 1 DOM access here
|
648 | this._popper.setAttribute('x-placement', data.placement);
|
649 |
|
650 | // if the arrow modifier is required and the arrow style has been computed, apply the arrow style
|
651 | if (this.isModifierRequired(this.modifiers.applyStyle, this.modifiers.arrow) && data.offsets.arrow) {
|
652 | setStyle(data.arrowElement, data.offsets.arrow);
|
653 | }
|
654 |
|
655 | return data;
|
656 | };
|
657 |
|
658 | /**
|
659 | * Modifier used to shift the popper on the start or end of its reference element side
|
660 | * @method
|
661 | * @memberof Popper.modifiers
|
662 | * @argument {Object} data - The data object generated by `update` method
|
663 | * @returns {Object} The data object, properly modified
|
664 | */
|
665 | Popper.prototype.modifiers.shift = function(data) {
|
666 | var placement = data.placement;
|
667 | var basePlacement = placement.split('-')[0];
|
668 | var shiftVariation = placement.split('-')[1];
|
669 |
|
670 | // if shift shiftVariation is specified, run the modifier
|
671 | if (shiftVariation) {
|
672 | var reference = data.offsets.reference;
|
673 | var popper = getPopperClientRect(data.offsets.popper);
|
674 |
|
675 | var shiftOffsets = {
|
676 | y: {
|
677 | start: { top: reference.top },
|
678 | end: { top: reference.top + reference.height - popper.height }
|
679 | },
|
680 | x: {
|
681 | start: { left: reference.left },
|
682 | end: { left: reference.left + reference.width - popper.width }
|
683 | }
|
684 | };
|
685 |
|
686 | var axis = ['bottom', 'top'].indexOf(basePlacement) !== -1 ? 'x' : 'y';
|
687 |
|
688 | data.offsets.popper = Object.assign(popper, shiftOffsets[axis][shiftVariation]);
|
689 | }
|
690 |
|
691 | return data;
|
692 | };
|
693 |
|
694 |
|
695 | /**
|
696 | * Modifier used to make sure the popper does not overflows from it's boundaries
|
697 | * @method
|
698 | * @memberof Popper.modifiers
|
699 | * @argument {Object} data - The data object generated by `update` method
|
700 | * @returns {Object} The data object, properly modified
|
701 | */
|
702 | Popper.prototype.modifiers.preventOverflow = function(data) {
|
703 | var order = this._options.preventOverflowOrder;
|
704 | var popper = getPopperClientRect(data.offsets.popper);
|
705 |
|
706 | var check = {
|
707 | left: function() {
|
708 | var left = popper.left;
|
709 | if (popper.left < data.boundaries.left) {
|
710 | left = Math.max(popper.left, data.boundaries.left);
|
711 | }
|
712 | return { left: left };
|
713 | },
|
714 | right: function() {
|
715 | var left = popper.left;
|
716 | if (popper.right > data.boundaries.right) {
|
717 | left = Math.min(popper.left, data.boundaries.right - popper.width);
|
718 | }
|
719 | return { left: left };
|
720 | },
|
721 | top: function() {
|
722 | var top = popper.top;
|
723 | if (popper.top < data.boundaries.top) {
|
724 | top = Math.max(popper.top, data.boundaries.top);
|
725 | }
|
726 | return { top: top };
|
727 | },
|
728 | bottom: function() {
|
729 | var top = popper.top;
|
730 | if (popper.bottom > data.boundaries.bottom) {
|
731 | top = Math.min(popper.top, data.boundaries.bottom - popper.height);
|
732 | }
|
733 | return { top: top };
|
734 | }
|
735 | };
|
736 |
|
737 | order.forEach(function(direction) {
|
738 | data.offsets.popper = Object.assign(popper, check[direction]());
|
739 | });
|
740 |
|
741 | return data;
|
742 | };
|
743 |
|
744 | /**
|
745 | * Modifier used to make sure the popper is always near its reference
|
746 | * @method
|
747 | * @memberof Popper.modifiers
|
748 | * @argument {Object} data - The data object generated by _update method
|
749 | * @returns {Object} The data object, properly modified
|
750 | */
|
751 | Popper.prototype.modifiers.keepTogether = function(data) {
|
752 | var popper = getPopperClientRect(data.offsets.popper);
|
753 | var reference = data.offsets.reference;
|
754 | var f = Math.floor;
|
755 |
|
756 | if (popper.right < f(reference.left)) {
|
757 | data.offsets.popper.left = f(reference.left) - popper.width;
|
758 | }
|
759 | if (popper.left > f(reference.right)) {
|
760 | data.offsets.popper.left = f(reference.right);
|
761 | }
|
762 | if (popper.bottom < f(reference.top)) {
|
763 | data.offsets.popper.top = f(reference.top) - popper.height;
|
764 | }
|
765 | if (popper.top > f(reference.bottom)) {
|
766 | data.offsets.popper.top = f(reference.bottom);
|
767 | }
|
768 |
|
769 | return data;
|
770 | };
|
771 |
|
772 | /**
|
773 | * Modifier used to flip the placement of the popper when the latter is starting overlapping its reference element.
|
774 | * Requires the `preventOverflow` modifier before it in order to work.
|
775 | * **NOTE:** This modifier will run all its previous modifiers everytime it tries to flip the popper!
|
776 | * @method
|
777 | * @memberof Popper.modifiers
|
778 | * @argument {Object} data - The data object generated by _update method
|
779 | * @returns {Object} The data object, properly modified
|
780 | */
|
781 | Popper.prototype.modifiers.flip = function(data) {
|
782 | // check if preventOverflow is in the list of modifiers before the flip modifier.
|
783 | // otherwise flip would not work as expected.
|
784 | if (!this.isModifierRequired(this.modifiers.flip, this.modifiers.preventOverflow)) {
|
785 | console.warn('WARNING: preventOverflow modifier is required by flip modifier in order to work, be sure to include it before flip!');
|
786 | return data;
|
787 | }
|
788 |
|
789 | if (data.flipped && data.placement === data._originalPlacement) {
|
790 | // seems like flip is trying to loop, probably there's not enough space on any of the flippable sides
|
791 | return data;
|
792 | }
|
793 |
|
794 | var placement = data.placement.split('-')[0];
|
795 | var placementOpposite = getOppositePlacement(placement);
|
796 | var variation = data.placement.split('-')[1] || '';
|
797 |
|
798 | var flipOrder = [];
|
799 | if(this._options.flipBehavior === 'flip') {
|
800 | flipOrder = [
|
801 | placement,
|
802 | placementOpposite
|
803 | ];
|
804 | } else {
|
805 | flipOrder = this._options.flipBehavior;
|
806 | }
|
807 |
|
808 | flipOrder.forEach(function(step, index) {
|
809 | if (placement !== step || flipOrder.length === index + 1) {
|
810 | return;
|
811 | }
|
812 |
|
813 | placement = data.placement.split('-')[0];
|
814 | placementOpposite = getOppositePlacement(placement);
|
815 |
|
816 | var popperOffsets = getPopperClientRect(data.offsets.popper);
|
817 |
|
818 | // this boolean is used to distinguish right and bottom from top and left
|
819 | // they need different computations to get flipped
|
820 | var a = ['right', 'bottom'].indexOf(placement) !== -1;
|
821 |
|
822 | // using Math.floor because the reference offsets may contain decimals we are not going to consider here
|
823 | if (
|
824 | a && Math.floor(data.offsets.reference[placement]) > Math.floor(popperOffsets[placementOpposite]) ||
|
825 | !a && Math.floor(data.offsets.reference[placement]) < Math.floor(popperOffsets[placementOpposite])
|
826 | ) {
|
827 | // we'll use this boolean to detect any flip loop
|
828 | data.flipped = true;
|
829 | data.placement = flipOrder[index + 1];
|
830 | if (variation) {
|
831 | data.placement += '-' + variation;
|
832 | }
|
833 | data.offsets.popper = this._getOffsets(this._popper, this._reference, data.placement).popper;
|
834 |
|
835 | data = this.runModifiers(data, this._options.modifiers, this._flip);
|
836 | }
|
837 | }.bind(this));
|
838 | return data;
|
839 | };
|
840 |
|
841 | /**
|
842 | * Modifier used to add an offset to the popper, useful if you more granularity positioning your popper.
|
843 | * The offsets will shift the popper on the side of its reference element.
|
844 | * @method
|
845 | * @memberof Popper.modifiers
|
846 | * @argument {Object} data - The data object generated by _update method
|
847 | * @returns {Object} The data object, properly modified
|
848 | */
|
849 | Popper.prototype.modifiers.offset = function(data) {
|
850 | var offset = this._options.offset;
|
851 | var popper = data.offsets.popper;
|
852 |
|
853 | if (data.placement.indexOf('left') !== -1) {
|
854 | popper.top -= offset;
|
855 | }
|
856 | else if (data.placement.indexOf('right') !== -1) {
|
857 | popper.top += offset;
|
858 | }
|
859 | else if (data.placement.indexOf('top') !== -1) {
|
860 | popper.left -= offset;
|
861 | }
|
862 | else if (data.placement.indexOf('bottom') !== -1) {
|
863 | popper.left += offset;
|
864 | }
|
865 | return data;
|
866 | };
|
867 |
|
868 | /**
|
869 | * Modifier used to move the arrows on the edge of the popper to make sure them are always between the popper and the reference element
|
870 | * It will use the CSS outer size of the arrow element to know how many pixels of conjuction are needed
|
871 | * @method
|
872 | * @memberof Popper.modifiers
|
873 | * @argument {Object} data - The data object generated by _update method
|
874 | * @returns {Object} The data object, properly modified
|
875 | */
|
876 | Popper.prototype.modifiers.arrow = function(data) {
|
877 | var arrow = this._options.arrowElement;
|
878 | var arrowOffset = this._options.arrowOffset;
|
879 |
|
880 | // if the arrowElement is a string, suppose it's a CSS selector
|
881 | if (typeof arrow === 'string') {
|
882 | arrow = this._popper.querySelector(arrow);
|
883 | }
|
884 |
|
885 | // if arrow element is not found, don't run the modifier
|
886 | if (!arrow) {
|
887 | return data;
|
888 | }
|
889 |
|
890 | // the arrow element must be child of its popper
|
891 | if (!this._popper.contains(arrow)) {
|
892 | console.warn('WARNING: `arrowElement` must be child of its popper element!');
|
893 | return data;
|
894 | }
|
895 |
|
896 | // arrow depends on keepTogether in order to work
|
897 | if (!this.isModifierRequired(this.modifiers.arrow, this.modifiers.keepTogether)) {
|
898 | console.warn('WARNING: keepTogether modifier is required by arrow modifier in order to work, be sure to include it before arrow!');
|
899 | return data;
|
900 | }
|
901 |
|
902 | var arrowStyle = {};
|
903 | var placement = data.placement.split('-')[0];
|
904 | var popper = getPopperClientRect(data.offsets.popper);
|
905 | var reference = data.offsets.reference;
|
906 | var isVertical = ['left', 'right'].indexOf(placement) !== -1;
|
907 |
|
908 | var len = isVertical ? 'height' : 'width';
|
909 | var side = isVertical ? 'top' : 'left';
|
910 | var translate = isVertical ? 'translateY' : 'translateX';
|
911 | var altSide = isVertical ? 'left' : 'top';
|
912 | var opSide = isVertical ? 'bottom' : 'right';
|
913 | var arrowSize = getOuterSizes(arrow)[len];
|
914 |
|
915 | //
|
916 | // extends keepTogether behavior making sure the popper and its reference have enough pixels in conjuction
|
917 | //
|
918 |
|
919 | // top/left side
|
920 | if (reference[opSide] - arrowSize < popper[side]) {
|
921 | data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowSize);
|
922 | }
|
923 | // bottom/right side
|
924 | if (reference[side] + arrowSize > popper[opSide]) {
|
925 | data.offsets.popper[side] += (reference[side] + arrowSize) - popper[opSide];
|
926 | }
|
927 |
|
928 | // compute center of the popper
|
929 | var center = reference[side] + (arrowOffset || (reference[len] / 2) - (arrowSize / 2));
|
930 |
|
931 | var sideValue = center - popper[side];
|
932 |
|
933 | // prevent arrow from being placed not contiguously to its popper
|
934 | sideValue = Math.max(Math.min(popper[len] - arrowSize - 8, sideValue), 8);
|
935 | arrowStyle[side] = sideValue;
|
936 | arrowStyle[altSide] = ''; // make sure to remove any old style from the arrow
|
937 |
|
938 | data.offsets.arrow = arrowStyle;
|
939 | data.arrowElement = arrow;
|
940 |
|
941 | return data;
|
942 | };
|
943 |
|
944 |
|
945 | //
|
946 | // Helpers
|
947 | //
|
948 |
|
949 | /**
|
950 | * Get the outer sizes of the given element (offset size + margins)
|
951 | * @function
|
952 | * @ignore
|
953 | * @argument {Element} element
|
954 | * @returns {Object} object containing width and height properties
|
955 | */
|
956 | function getOuterSizes(element) {
|
957 | // NOTE: 1 DOM access here
|
958 | var _display = element.style.display, _visibility = element.style.visibility;
|
959 | element.style.display = 'block'; element.style.visibility = 'hidden';
|
960 | var calcWidthToForceRepaint = element.offsetWidth;
|
961 |
|
962 | // original method
|
963 | var styles = root.getComputedStyle(element);
|
964 | var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
|
965 | var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
|
966 | var result = { width: element.offsetWidth + y, height: element.offsetHeight + x };
|
967 |
|
968 | // reset element styles
|
969 | element.style.display = _display; element.style.visibility = _visibility;
|
970 | return result;
|
971 | }
|
972 |
|
973 | /**
|
974 | * Get the opposite placement of the given one/
|
975 | * @function
|
976 | * @ignore
|
977 | * @argument {String} placement
|
978 | * @returns {String} flipped placement
|
979 | */
|
980 | function getOppositePlacement(placement) {
|
981 | var hash = {left: 'right', right: 'left', bottom: 'top', top: 'bottom' };
|
982 | return placement.replace(/left|right|bottom|top/g, function(matched){
|
983 | return hash[matched];
|
984 | });
|
985 | }
|
986 |
|
987 | /**
|
988 | * Given the popper offsets, generate an output similar to getBoundingClientRect
|
989 | * @function
|
990 | * @ignore
|
991 | * @argument {Object} popperOffsets
|
992 | * @returns {Object} ClientRect like output
|
993 | */
|
994 | function getPopperClientRect(popperOffsets) {
|
995 | var offsets = Object.assign({}, popperOffsets);
|
996 | offsets.right = offsets.left + offsets.width;
|
997 | offsets.bottom = offsets.top + offsets.height;
|
998 | return offsets;
|
999 | }
|
1000 |
|
1001 | /**
|
1002 | * Given an array and the key to find, returns its index
|
1003 | * @function
|
1004 | * @ignore
|
1005 | * @argument {Array} arr
|
1006 | * @argument keyToFind
|
1007 | * @returns index or null
|
1008 | */
|
1009 | function getArrayKeyIndex(arr, keyToFind) {
|
1010 | var i = 0, key;
|
1011 | for (key in arr) {
|
1012 | if (arr[key] === keyToFind) {
|
1013 | return i;
|
1014 | }
|
1015 | i++;
|
1016 | }
|
1017 | return null;
|
1018 | }
|
1019 |
|
1020 | /**
|
1021 | * Get CSS computed property of the given element
|
1022 | * @function
|
1023 | * @ignore
|
1024 | * @argument {Eement} element
|
1025 | * @argument {String} property
|
1026 | */
|
1027 | function getStyleComputedProperty(element, property) {
|
1028 | // NOTE: 1 DOM access here
|
1029 | var css = root.getComputedStyle(element, null);
|
1030 | return css[property];
|
1031 | }
|
1032 |
|
1033 | /**
|
1034 | * Returns the offset parent of the given element
|
1035 | * @function
|
1036 | * @ignore
|
1037 | * @argument {Element} element
|
1038 | * @returns {Element} offset parent
|
1039 | */
|
1040 | function getOffsetParent(element) {
|
1041 | // NOTE: 1 DOM access here
|
1042 | var offsetParent = element.offsetParent;
|
1043 | return offsetParent === root.document.body || !offsetParent ? root.document.documentElement : offsetParent;
|
1044 | }
|
1045 |
|
1046 | /**
|
1047 | * Returns the scrolling parent of the given element
|
1048 | * @function
|
1049 | * @ignore
|
1050 | * @argument {Element} element
|
1051 | * @returns {Element} offset parent
|
1052 | */
|
1053 | function getScrollParent(element) {
|
1054 | var parent = element.parentNode;
|
1055 |
|
1056 | if (!parent) {
|
1057 | return element;
|
1058 | }
|
1059 |
|
1060 | if (parent === root.document) {
|
1061 | // Firefox puts the scrollTOp value on `documentElement` instead of `body`, we then check which of them is
|
1062 | // greater than 0 and return the proper element
|
1063 | if (root.document.body.scrollTop || root.document.body.scrollLeft) {
|
1064 | return root.document.body;
|
1065 | } else {
|
1066 | return root.document.documentElement;
|
1067 | }
|
1068 | }
|
1069 |
|
1070 | // Firefox want us to check `-x` and `-y` variations as well
|
1071 | if (
|
1072 | ['scroll', 'auto'].indexOf(getStyleComputedProperty(parent, 'overflow')) !== -1 ||
|
1073 | ['scroll', 'auto'].indexOf(getStyleComputedProperty(parent, 'overflow-x')) !== -1 ||
|
1074 | ['scroll', 'auto'].indexOf(getStyleComputedProperty(parent, 'overflow-y')) !== -1
|
1075 | ) {
|
1076 | // If the detected scrollParent is body, we perform an additional check on its parentNode
|
1077 | // in this way we'll get body if the browser is Chrome-ish, or documentElement otherwise
|
1078 | // fixes issue #65
|
1079 | return parent;
|
1080 | }
|
1081 | return getScrollParent(element.parentNode);
|
1082 | }
|
1083 |
|
1084 | /**
|
1085 | * Check if the given element is fixed or is inside a fixed parent
|
1086 | * @function
|
1087 | * @ignore
|
1088 | * @argument {Element} element
|
1089 | * @argument {Element} customContainer
|
1090 | * @returns {Boolean} answer to "isFixed?"
|
1091 | */
|
1092 | function isFixed(element) {
|
1093 | if (element === root.document.body) {
|
1094 | return false;
|
1095 | }
|
1096 | if (getStyleComputedProperty(element, 'position') === 'fixed') {
|
1097 | return true;
|
1098 | }
|
1099 | return element.parentNode ? isFixed(element.parentNode) : element;
|
1100 | }
|
1101 |
|
1102 | /**
|
1103 | * Set the style to the given popper
|
1104 | * @function
|
1105 | * @ignore
|
1106 | * @argument {Element} element - Element to apply the style to
|
1107 | * @argument {Object} styles - Object with a list of properties and values which will be applied to the element
|
1108 | */
|
1109 | function setStyle(element, styles) {
|
1110 | function is_numeric(n) {
|
1111 | return (n !== '' && !isNaN(parseFloat(n)) && isFinite(n));
|
1112 | }
|
1113 | Object.keys(styles).forEach(function(prop) {
|
1114 | var unit = '';
|
1115 | // add unit if the value is numeric and is one of the following
|
1116 | if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && is_numeric(styles[prop])) {
|
1117 | unit = 'px';
|
1118 | }
|
1119 | element.style[prop] = styles[prop] + unit;
|
1120 | });
|
1121 | }
|
1122 |
|
1123 | /**
|
1124 | * Check if the given variable is a function
|
1125 | * @function
|
1126 | * @ignore
|
1127 | * @argument {*} functionToCheck - variable to check
|
1128 | * @returns {Boolean} answer to: is a function?
|
1129 | */
|
1130 | function isFunction(functionToCheck) {
|
1131 | var getType = {};
|
1132 | return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
1133 | }
|
1134 |
|
1135 | /**
|
1136 | * Get the position of the given element, relative to its offset parent
|
1137 | * @function
|
1138 | * @ignore
|
1139 | * @param {Element} element
|
1140 | * @return {Object} position - Coordinates of the element and its `scrollTop`
|
1141 | */
|
1142 | function getOffsetRect(element) {
|
1143 | var elementRect = {
|
1144 | width: element.offsetWidth,
|
1145 | height: element.offsetHeight,
|
1146 | left: element.offsetLeft,
|
1147 | top: element.offsetTop
|
1148 | };
|
1149 |
|
1150 | elementRect.right = elementRect.left + elementRect.width;
|
1151 | elementRect.bottom = elementRect.top + elementRect.height;
|
1152 |
|
1153 | // position
|
1154 | return elementRect;
|
1155 | }
|
1156 |
|
1157 | /**
|
1158 | * Get bounding client rect of given element
|
1159 | * @function
|
1160 | * @ignore
|
1161 | * @param {HTMLElement} element
|
1162 | * @return {Object} client rect
|
1163 | */
|
1164 | function getBoundingClientRect(element) {
|
1165 | var rect = element.getBoundingClientRect();
|
1166 |
|
1167 | // whether the IE version is lower than 11
|
1168 | var isIE = navigator.userAgent.indexOf("MSIE") != -1;
|
1169 |
|
1170 | // fix ie document bounding top always 0 bug
|
1171 | var rectTop = isIE && element.tagName === 'HTML'
|
1172 | ? -element.scrollTop
|
1173 | : rect.top;
|
1174 |
|
1175 | return {
|
1176 | left: rect.left,
|
1177 | top: rectTop,
|
1178 | right: rect.right,
|
1179 | bottom: rect.bottom,
|
1180 | width: rect.right - rect.left,
|
1181 | height: rect.bottom - rectTop
|
1182 | };
|
1183 | }
|
1184 |
|
1185 | /**
|
1186 | * Given an element and one of its parents, return the offset
|
1187 | * @function
|
1188 | * @ignore
|
1189 | * @param {HTMLElement} element
|
1190 | * @param {HTMLElement} parent
|
1191 | * @return {Object} rect
|
1192 | */
|
1193 | function getOffsetRectRelativeToCustomParent(element, parent, fixed) {
|
1194 | var elementRect = getBoundingClientRect(element);
|
1195 | var parentRect = getBoundingClientRect(parent);
|
1196 |
|
1197 | if (fixed) {
|
1198 | var scrollParent = getScrollParent(parent);
|
1199 | parentRect.top += scrollParent.scrollTop;
|
1200 | parentRect.bottom += scrollParent.scrollTop;
|
1201 | parentRect.left += scrollParent.scrollLeft;
|
1202 | parentRect.right += scrollParent.scrollLeft;
|
1203 | }
|
1204 |
|
1205 | var rect = {
|
1206 | top: elementRect.top - parentRect.top ,
|
1207 | left: elementRect.left - parentRect.left ,
|
1208 | bottom: (elementRect.top - parentRect.top) + elementRect.height,
|
1209 | right: (elementRect.left - parentRect.left) + elementRect.width,
|
1210 | width: elementRect.width,
|
1211 | height: elementRect.height
|
1212 | };
|
1213 | return rect;
|
1214 | }
|
1215 |
|
1216 | /**
|
1217 | * Get the prefixed supported property name
|
1218 | * @function
|
1219 | * @ignore
|
1220 | * @argument {String} property (camelCase)
|
1221 | * @returns {String} prefixed property (camelCase)
|
1222 | */
|
1223 | function getSupportedPropertyName(property) {
|
1224 | var prefixes = ['', 'ms', 'webkit', 'moz', 'o'];
|
1225 |
|
1226 | for (var i = 0; i < prefixes.length; i++) {
|
1227 | var toCheck = prefixes[i] ? prefixes[i] + property.charAt(0).toUpperCase() + property.slice(1) : property;
|
1228 | if (typeof root.document.body.style[toCheck] !== 'undefined') {
|
1229 | return toCheck;
|
1230 | }
|
1231 | }
|
1232 | return null;
|
1233 | }
|
1234 |
|
1235 | /**
|
1236 | * The Object.assign() method is used to copy the values of all enumerable own properties from one or more source
|
1237 | * objects to a target object. It will return the target object.
|
1238 | * This polyfill doesn't support symbol properties, since ES5 doesn't have symbols anyway
|
1239 | * Source: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
|
1240 | * @function
|
1241 | * @ignore
|
1242 | */
|
1243 | if (!Object.assign) {
|
1244 | Object.defineProperty(Object, 'assign', {
|
1245 | enumerable: false,
|
1246 | configurable: true,
|
1247 | writable: true,
|
1248 | value: function(target) {
|
1249 | if (target === undefined || target === null) {
|
1250 | throw new TypeError('Cannot convert first argument to object');
|
1251 | }
|
1252 |
|
1253 | var to = Object(target);
|
1254 | for (var i = 1; i < arguments.length; i++) {
|
1255 | var nextSource = arguments[i];
|
1256 | if (nextSource === undefined || nextSource === null) {
|
1257 | continue;
|
1258 | }
|
1259 | nextSource = Object(nextSource);
|
1260 |
|
1261 | var keysArray = Object.keys(nextSource);
|
1262 | for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
|
1263 | var nextKey = keysArray[nextIndex];
|
1264 | var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
|
1265 | if (desc !== undefined && desc.enumerable) {
|
1266 | to[nextKey] = nextSource[nextKey];
|
1267 | }
|
1268 | }
|
1269 | }
|
1270 | return to;
|
1271 | }
|
1272 | });
|
1273 | }
|
1274 |
|
1275 | return Popper;
|
1276 | }));
|