diff --git a/BTCPayServer/Views/Wallets/WalletSendVault.cshtml b/BTCPayServer/Views/Wallets/WalletSendVault.cshtml index 21b94c46d..27536df33 100644 --- a/BTCPayServer/Views/Wallets/WalletSendVault.cshtml +++ b/BTCPayServer/Views/Wallets/WalletSendVault.cshtml @@ -18,7 +18,7 @@
- + diff --git a/BTCPayServer/wwwroot/vendor/popper/popper.js b/BTCPayServer/wwwroot/vendor/popper/popper.js index c46aa9f74..939091fce 100644 --- a/BTCPayServer/wwwroot/vendor/popper/popper.js +++ b/BTCPayServer/wwwroot/vendor/popper/popper.js @@ -1,6 +1,6 @@ /**! * @fileOverview Kickass library to create and place poppers near their reference elements. - * @version 1.11.1 + * @version 1.16.0 * @license * Copyright (c) 2016 Federico Zivolo and contributors * @@ -29,52 +29,29 @@ }(this, (function () { 'use strict'; - var nativeHints = ['native code', '[object MutationObserverConstructor]']; + var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && typeof navigator !== 'undefined'; - /** - * Determine if a function is implemented natively (as opposed to a polyfill). - * @method - * @memberof Popper.Utils - * @argument {Function | undefined} fn the function to check - * @returns {Boolean} - */ - var isNative = (function (fn) { - return nativeHints.some(function (hint) { - return (fn || '').toString().indexOf(hint) > -1; - }); - }); - - var isBrowser = typeof window !== 'undefined'; - var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox']; - var timeoutDuration = 0; - for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) { - if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) { - timeoutDuration = 1; - break; + var timeoutDuration = function () { + var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox']; + for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) { + if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) { + return 1; + } } - } + return 0; + }(); function microtaskDebounce(fn) { - var scheduled = false; - var i = 0; - var elem = document.createElement('span'); - - // MutationObserver provides a mechanism for scheduling microtasks, which - // are scheduled *before* the next task. This gives us a way to debounce - // a function but ensure it's called *before* the next paint. - var observer = new MutationObserver(function () { - fn(); - scheduled = false; - }); - - observer.observe(elem, { attributes: true }); - + var called = false; return function () { - if (!scheduled) { - scheduled = true; - elem.setAttribute('x-index', i); - i = i + 1; // don't use compund (+=) because it doesn't get optimized in V8 + if (called) { + return; } + called = true; + window.Promise.resolve().then(function () { + called = false; + fn(); + }); }; } @@ -91,11 +68,7 @@ }; } - // It's common for MutationObserver polyfills to be seen in the wild, however - // these rely on Mutation Events which only occur when an element is connected - // to the DOM. The algorithm used in this module does not use a connected element, - // and so we must ensure that a *native* MutationObserver is available. - var supportsNativeMutationObserver = isBrowser && isNative(window.MutationObserver); + var supportsMicroTasks = isBrowser && window.Promise; /** * Create a debounced version of a method, that's asynchronously deferred @@ -106,7 +79,7 @@ * @argument {Function} fn * @returns {Function} */ - var debounce = supportsNativeMutationObserver ? microtaskDebounce : taskDebounce; + var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce; /** * Check if the given variable is a function @@ -132,6 +105,7 @@ return []; } // NOTE: 1 DOM access here + var window = element.ownerDocument.defaultView; var css = window.getComputedStyle(element, null); return property ? css[property] : css; } @@ -159,8 +133,16 @@ */ function getScrollParent(element) { // Return body, `getScroll` will take care to get the correct `scrollTop` from it - if (!element || ['HTML', 'BODY', '#document'].indexOf(element.nodeName) !== -1) { - return window.document.body; + if (!element) { + return document.body; + } + + switch (element.nodeName) { + case 'HTML': + case 'BODY': + return element.ownerDocument.body; + case '#document': + return element.body; } // Firefox want us to check `-x` and `-y` variations as well @@ -170,13 +152,44 @@ overflowX = _getStyleComputedProp.overflowX, overflowY = _getStyleComputedProp.overflowY; - if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) { + if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) { return element; } return getScrollParent(getParentNode(element)); } + /** + * Returns the reference node of the reference object, or the reference object itself. + * @method + * @memberof Popper.Utils + * @param {Element|Object} reference - the reference element (the popper will be relative to this) + * @returns {Element} parent + */ + function getReferenceNode(reference) { + return reference && reference.referenceNode ? reference.referenceNode : reference; + } + + var isIE11 = isBrowser && !!(window.MSInputMethodContext && document.documentMode); + var isIE10 = isBrowser && /MSIE 10/.test(navigator.userAgent); + + /** + * Determines if the browser is Internet Explorer + * @method + * @memberof Popper.Utils + * @param {Number} version to check + * @returns {Boolean} isIE + */ + function isIE(version) { + if (version === 11) { + return isIE11; + } + if (version === 10) { + return isIE10; + } + return isIE11 || isIE10; + } + /** * Returns the offset parent of the given element * @method @@ -185,17 +198,28 @@ * @returns {Element} offset parent */ function getOffsetParent(element) { + if (!element) { + return document.documentElement; + } + + var noOffsetParent = isIE(10) ? document.body : null; + // NOTE: 1 DOM access here - var offsetParent = element && element.offsetParent; + var offsetParent = element.offsetParent || null; + // Skip hidden elements which don't have an offsetParent + while (offsetParent === noOffsetParent && element.nextElementSibling) { + offsetParent = (element = element.nextElementSibling).offsetParent; + } + var nodeName = offsetParent && offsetParent.nodeName; if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') { - return window.document.documentElement; + return element ? element.ownerDocument.documentElement : document.documentElement; } - // .offsetParent will return the closest TD or TABLE in case + // .offsetParent will return the closest TH, TD or TABLE in case // no offsetParent is present, I hate this job... - if (['TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') { + if (['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') { return getOffsetParent(offsetParent); } @@ -237,7 +261,7 @@ function findCommonOffsetParent(element1, element2) { // This check is needed to avoid errors in case one of the elements isn't defined for any reason if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) { - return window.document.documentElement; + return document.documentElement; } // Here we make sure to give as "start" the element that comes first in the DOM @@ -285,8 +309,8 @@ var nodeName = element.nodeName; if (nodeName === 'BODY' || nodeName === 'HTML') { - var html = window.document.documentElement; - var scrollingElement = window.document.scrollingElement || html; + var html = element.ownerDocument.documentElement; + var scrollingElement = element.ownerDocument.scrollingElement || html; return scrollingElement[upperSide]; } @@ -329,32 +353,17 @@ var sideA = axis === 'x' ? 'Left' : 'Top'; var sideB = sideA === 'Left' ? 'Right' : 'Bottom'; - return +styles['border' + sideA + 'Width'].split('px')[0] + +styles['border' + sideB + 'Width'].split('px')[0]; + return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10); } - /** - * Tells if you are running Internet Explorer 10 - * @method - * @memberof Popper.Utils - * @returns {Boolean} isIE10 - */ - var isIE10 = undefined; - - var isIE10$1 = function () { - if (isIE10 === undefined) { - isIE10 = navigator.appVersion.indexOf('MSIE 10') !== -1; - } - return isIE10; - }; - function getSize(axis, body, html, computedStyle) { - return Math.max(body['offset' + axis], html['client' + axis], html['offset' + axis], isIE10$1() ? html['offset' + axis] + computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')] + computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')] : 0); + 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); } - function getWindowSizes() { - var body = window.document.body; - var html = window.document.documentElement; - var computedStyle = isIE10$1() && window.getComputedStyle(html); + function getWindowSizes(document) { + var body = document.body; + var html = document.documentElement; + var computedStyle = isIE(10) && getComputedStyle(html); return { height: getSize('Height', body, html, computedStyle), @@ -446,8 +455,8 @@ // IE10 10 FIX: Please, don't ask, the element isn't // considered in DOM in some circumstances... // This isn't reproducible in IE10 compatibility mode of IE11 - if (isIE10$1()) { - try { + try { + if (isIE(10)) { rect = element.getBoundingClientRect(); var scrollTop = getScroll(element, 'top'); var scrollLeft = getScroll(element, 'left'); @@ -455,10 +464,10 @@ rect.left += scrollLeft; rect.bottom += scrollTop; rect.right += scrollLeft; - } catch (err) { } - } else { - rect = element.getBoundingClientRect(); - } + } else { + rect = element.getBoundingClientRect(); + } + } catch (e) { } var result = { left: rect.left, @@ -468,9 +477,9 @@ }; // subtract scrollbar size from sizes - var sizes = element.nodeName === 'HTML' ? getWindowSizes() : {}; - var width = sizes.width || element.clientWidth || result.right - result.left; - var height = sizes.height || element.clientHeight || result.bottom - result.top; + var sizes = element.nodeName === 'HTML' ? getWindowSizes(element.ownerDocument) : {}; + var width = sizes.width || element.clientWidth || result.width; + var height = sizes.height || element.clientHeight || result.height; var horizScrollbar = element.offsetWidth - width; var vertScrollbar = element.offsetHeight - height; @@ -490,16 +499,23 @@ } function getOffsetRectRelativeToArbitraryNode(children, parent) { - var isIE10 = isIE10$1(); + var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + + var isIE10 = isIE(10); var isHTML = parent.nodeName === 'HTML'; var childrenRect = getBoundingClientRect(children); var parentRect = getBoundingClientRect(parent); var scrollParent = getScrollParent(children); var styles = getStyleComputedProperty(parent); - var borderTopWidth = +styles.borderTopWidth.split('px')[0]; - var borderLeftWidth = +styles.borderLeftWidth.split('px')[0]; + var borderTopWidth = parseFloat(styles.borderTopWidth, 10); + var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10); + // In cases where the parent is fixed, we must ignore negative scroll in offset calc + if (fixedPosition && isHTML) { + parentRect.top = Math.max(parentRect.top, 0); + parentRect.left = Math.max(parentRect.left, 0); + } var offsets = getClientRect({ top: childrenRect.top - parentRect.top - borderTopWidth, left: childrenRect.left - parentRect.left - borderLeftWidth, @@ -514,8 +530,8 @@ // differently when margins are applied to it. The margins are included in // the box of the documentElement, in the other cases not. if (!isIE10 && isHTML) { - var marginTop = +styles.marginTop.split('px')[0]; - var marginLeft = +styles.marginLeft.split('px')[0]; + var marginTop = parseFloat(styles.marginTop, 10); + var marginLeft = parseFloat(styles.marginLeft, 10); offsets.top -= borderTopWidth - marginTop; offsets.bottom -= borderTopWidth - marginTop; @@ -527,7 +543,7 @@ offsets.marginLeft = marginLeft; } - if (isIE10 ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') { + if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') { offsets = includeScroll(offsets, parent); } @@ -535,13 +551,15 @@ } function getViewportOffsetRectRelativeToArtbitraryNode(element) { - var html = window.document.documentElement; + var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var html = element.ownerDocument.documentElement; var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html); var width = Math.max(html.clientWidth, window.innerWidth || 0); var height = Math.max(html.clientHeight, window.innerHeight || 0); - var scrollTop = getScroll(html); - var scrollLeft = getScroll(html, 'left'); + var scrollTop = !excludeScroll ? getScroll(html) : 0; + var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0; var offset = { top: scrollTop - relativeOffset.top + relativeOffset.marginTop, @@ -569,7 +587,31 @@ if (getStyleComputedProperty(element, 'position') === 'fixed') { return true; } - return isFixed(getParentNode(element)); + var parentNode = getParentNode(element); + if (!parentNode) { + return false; + } + return isFixed(parentNode); + } + + /** + * Finds the first parent of an element that has a transformed property defined + * @method + * @memberof Popper.Utils + * @argument {Element} element + * @returns {Element} first transformed parent or documentElement + */ + + function getFixedPositionOffsetParent(element) { + // This check is needed to avoid errors in case one of the elements isn't defined for any reason + if (!element || !element.parentElement || isIE()) { + return document.documentElement; + } + var el = element.parentElement; + while (el && getStyleComputedProperty(el, 'transform') === 'none') { + el = el.parentElement; + } + return el || document.documentElement; } /** @@ -580,35 +622,39 @@ * @param {HTMLElement} reference * @param {number} padding * @param {HTMLElement} boundariesElement - Element used to define the boundaries + * @param {Boolean} fixedPosition - Is in fixed position mode * @returns {Object} Coordinates of the boundaries */ function getBoundaries(popper, reference, padding, boundariesElement) { + var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + // NOTE: 1 DOM access here + var boundaries = { top: 0, left: 0 }; - var offsetParent = findCommonOffsetParent(popper, reference); + var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, getReferenceNode(reference)); // Handle viewport case if (boundariesElement === 'viewport') { - boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent); + boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition); } else { // Handle other cases based on DOM element used as boundaries var boundariesNode = void 0; if (boundariesElement === 'scrollParent') { - boundariesNode = getScrollParent(getParentNode(popper)); + boundariesNode = getScrollParent(getParentNode(reference)); if (boundariesNode.nodeName === 'BODY') { - boundariesNode = window.document.documentElement; + boundariesNode = popper.ownerDocument.documentElement; } } else if (boundariesElement === 'window') { - boundariesNode = window.document.documentElement; + boundariesNode = popper.ownerDocument.documentElement; } else { boundariesNode = boundariesElement; } - var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent); + var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition); // In case of HTML, we need a different computation if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) { - var _getWindowSizes = getWindowSizes(), + var _getWindowSizes = getWindowSizes(popper.ownerDocument), height = _getWindowSizes.height, width = _getWindowSizes.width; @@ -623,10 +669,12 @@ } // Add paddings - boundaries.left += padding; - boundaries.top += padding; - boundaries.right -= padding; - boundaries.bottom -= padding; + padding = padding || 0; + var isPaddingNumber = typeof padding === 'number'; + boundaries.left += isPaddingNumber ? padding : padding.left || 0; + boundaries.top += isPaddingNumber ? padding : padding.top || 0; + boundaries.right -= isPaddingNumber ? padding : padding.right || 0; + boundaries.bottom -= isPaddingNumber ? padding : padding.bottom || 0; return boundaries; } @@ -679,8 +727,8 @@ return _extends({ key: key }, rects[key], { - area: getArea(rects[key]) - }); + area: getArea(rects[key]) + }); }).sort(function (a, b) { return b.area - a.area; }); @@ -705,11 +753,14 @@ * @param {Object} state * @param {Element} popper - the popper element * @param {Element} reference - the reference element (the popper will be relative to this) + * @param {Element} fixedPosition - is in fixed position mode * @returns {Object} An object containing the offsets which will be applied to the popper */ function getReferenceOffsets(state, popper, reference) { - var commonOffsetParent = findCommonOffsetParent(popper, reference); - return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent); + var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + + var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, getReferenceNode(reference)); + return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition); } /** @@ -720,9 +771,10 @@ * @returns {Object} object containing width and height properties */ function getOuterSizes(element) { + var window = element.ownerDocument.defaultView; var styles = window.getComputedStyle(element); - var x = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom); - var y = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight); + var x = parseFloat(styles.marginTop || 0) + parseFloat(styles.marginBottom || 0); + var y = parseFloat(styles.marginLeft || 0) + parseFloat(styles.marginRight || 0); var result = { width: element.offsetWidth + y, height: element.offsetHeight + x @@ -840,10 +892,11 @@ var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends)); modifiersToRun.forEach(function (modifier) { - if (modifier.function) { + if (modifier['function']) { + // eslint-disable-line dot-notation console.warn('`modifier.function` is deprecated, use `modifier.fn`!'); } - var fn = modifier.function || modifier.fn; + var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation if (modifier.enabled && isFunction(fn)) { // Add properties to offsets to make them a complete clientRect object // we do this before each modifier to make sure the previous one doesn't @@ -874,13 +927,14 @@ var data = { instance: this, styles: {}, + arrowStyles: {}, attributes: {}, flipped: false, offsets: {} }; // compute reference element offsets - data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference); + data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference, this.options.positionFixed); // compute auto placement, store placement inside the data object, // modifiers will be able to edit `placement` if needed @@ -890,9 +944,12 @@ // store the computed placement inside `originalPlacement` data.originalPlacement = data.placement; + data.positionFixed = this.options.positionFixed; + // compute the popper offsets data.offsets.popper = getPopperOffsets(this.popper, data.offsets.reference, data.placement); - data.offsets.popper.position = 'absolute'; + + data.offsets.popper.position = this.options.positionFixed ? 'fixed' : 'absolute'; // run the modifiers data = runModifiers(this.modifiers, data); @@ -932,10 +989,10 @@ var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O']; var upperProp = property.charAt(0).toUpperCase() + property.slice(1); - for (var i = 0; i < prefixes.length - 1; i++) { + for (var i = 0; i < prefixes.length; i++) { var prefix = prefixes[i]; var toCheck = prefix ? '' + prefix + upperProp : property; - if (typeof window.document.body.style[toCheck] !== 'undefined') { + if (typeof document.body.style[toCheck] !== 'undefined') { return toCheck; } } @@ -943,7 +1000,7 @@ } /** - * Destroy the popper + * Destroys the popper. * @method * @memberof Popper */ @@ -953,15 +1010,18 @@ // touch DOM only if `applyStyle` modifier is enabled if (isModifierEnabled(this.modifiers, 'applyStyle')) { this.popper.removeAttribute('x-placement'); - this.popper.style.left = ''; this.popper.style.position = ''; this.popper.style.top = ''; + this.popper.style.left = ''; + this.popper.style.right = ''; + this.popper.style.bottom = ''; + this.popper.style.willChange = ''; this.popper.style[getSupportedPropertyName('transform')] = ''; } this.disableEventListeners(); - // remove the popper if user explicity asked for the deletion on destroy + // remove the popper if user explicitly asked for the deletion on destroy // do not use `remove` because IE11 doesn't support it if (this.options.removeOnDestroy) { this.popper.parentNode.removeChild(this.popper); @@ -969,9 +1029,19 @@ return this; } + /** + * Get the window associated with the element + * @argument {Element} element + * @returns {Window} + */ + function getWindow(element) { + var ownerDocument = element.ownerDocument; + return ownerDocument ? ownerDocument.defaultView : window; + } + function attachToScrollParents(scrollParent, event, callback, scrollParents) { var isBody = scrollParent.nodeName === 'BODY'; - var target = isBody ? window : scrollParent; + var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent; target.addEventListener(event, callback, { passive: true }); if (!isBody) { @@ -989,7 +1059,7 @@ function setupEventListeners(reference, options, state, updateBound) { // Resize event listener on window state.updateBound = updateBound; - window.addEventListener('resize', state.updateBound, { passive: true }); + getWindow(reference).addEventListener('resize', state.updateBound, { passive: true }); // Scroll event listener on scroll parents var scrollElement = getScrollParent(reference); @@ -1020,7 +1090,7 @@ */ function removeEventListeners(reference, state) { // Remove resize event listener on window - window.removeEventListener('resize', state.updateBound); + getWindow(reference).removeEventListener('resize', state.updateBound); // Remove scroll event listener on scroll parents state.scrollParents.forEach(function (target) { @@ -1037,14 +1107,14 @@ /** * It will remove resize/scroll events and won't recalculate popper position - * when they are triggered. It also won't trigger onUpdate callback anymore, + * when they are triggered. It also won't trigger `onUpdate` callback anymore, * unless you call `update` method manually. * @method * @memberof Popper */ function disableEventListeners() { if (this.state.eventsEnabled) { - window.cancelAnimationFrame(this.scheduleUpdate); + cancelAnimationFrame(this.scheduleUpdate); this.state = removeEventListeners(this.reference, this.state); } } @@ -1118,9 +1188,9 @@ // they will be set as HTML attributes of the element setAttributes(data.instance.popper, data.attributes); - // if the arrow style has been computed, apply the arrow style - if (data.offsets.arrow) { - setStyles(data.arrowElement, data.offsets.arrow); + // if arrowElement is defined and arrowStyles has some properties + if (data.arrowElement && Object.keys(data.arrowStyles).length) { + setStyles(data.arrowElement, data.arrowStyles); } return data; @@ -1133,12 +1203,12 @@ * @method * @memberof Popper.modifiers * @param {HTMLElement} reference - The reference element used to position the popper - * @param {HTMLElement} popper - The HTML element used as popper. + * @param {HTMLElement} popper - The HTML element used as popper * @param {Object} options - Popper.js options */ function applyStyleOnLoad(reference, popper, options, modifierOptions, state) { // compute reference element offsets - var referenceOffsets = getReferenceOffsets(state, popper, reference); + var referenceOffsets = getReferenceOffsets(state, popper, reference, options.positionFixed); // compute auto placement, store placement inside the data object, // modifiers will be able to edit `placement` if needed @@ -1149,11 +1219,62 @@ // Apply `position` to popper before anything else because // without the position applied we can't guarantee correct computations - setStyles(popper, { position: 'absolute' }); + setStyles(popper, { position: options.positionFixed ? 'fixed' : 'absolute' }); return options; } + /** + * @function + * @memberof Popper.Utils + * @argument {Object} data - The data object generated by `update` method + * @argument {Boolean} shouldRound - If the offsets should be rounded at all + * @returns {Object} The popper's position offsets rounded + * + * The tale of pixel-perfect positioning. It's still not 100% perfect, but as + * good as it can be within reason. + * Discussion here: https://github.com/FezVrasta/popper.js/pull/715 + * + * Low DPI screens cause a popper to be blurry if not using full pixels (Safari + * as well on High DPI screens). + * + * Firefox prefers no rounding for positioning and does not have blurriness on + * high DPI screens. + * + * Only horizontal placement and left/right values need to be considered. + */ + function getRoundedOffsets(data, shouldRound) { + var _data$offsets = data.offsets, + popper = _data$offsets.popper, + reference = _data$offsets.reference; + var round = Math.round, + floor = Math.floor; + + var noRound = function noRound(v) { + return v; + }; + + var referenceWidth = round(reference.width); + var popperWidth = round(popper.width); + + var isVertical = ['left', 'right'].indexOf(data.placement) !== -1; + var isVariation = data.placement.indexOf('-') !== -1; + var sameWidthParity = referenceWidth % 2 === popperWidth % 2; + var bothOddWidth = referenceWidth % 2 === 1 && popperWidth % 2 === 1; + + var horizontalToInteger = !shouldRound ? noRound : isVertical || isVariation || sameWidthParity ? round : floor; + var verticalToInteger = !shouldRound ? noRound : round; + + return { + left: horizontalToInteger(bothOddWidth && !isVariation && shouldRound ? popper.left - 1 : popper.left), + top: verticalToInteger(popper.top), + bottom: verticalToInteger(popper.bottom), + right: horizontalToInteger(popper.right) + }; + } + + var isFirefox = isBrowser && /Firefox/i.test(navigator.userAgent); + /** * @function * @memberof Modifiers @@ -1184,13 +1305,7 @@ position: popper.position }; - // floor sides to avoid blurry text - var offsets = { - left: Math.floor(popper.left), - top: Math.floor(popper.top), - bottom: Math.floor(popper.bottom), - right: Math.floor(popper.right) - }; + var offsets = getRoundedOffsets(data, window.devicePixelRatio < 2 || !isFirefox); var sideA = x === 'bottom' ? 'top' : 'bottom'; var sideB = y === 'right' ? 'left' : 'right'; @@ -1212,12 +1327,22 @@ var left = void 0, top = void 0; if (sideA === 'bottom') { - top = -offsetParentRect.height + offsets.bottom; + // when offsetParent is the positioning is relative to the bottom of the screen (excluding the scrollbar) + // and not the bottom of the html element + if (offsetParent.nodeName === 'HTML') { + top = -offsetParent.clientHeight + offsets.bottom; + } else { + top = -offsetParentRect.height + offsets.bottom; + } } else { top = offsets.top; } if (sideB === 'right') { - left = -offsetParentRect.width + offsets.right; + if (offsetParent.nodeName === 'HTML') { + left = -offsetParent.clientWidth + offsets.right; + } else { + left = -offsetParentRect.width + offsets.right; + } } else { left = offsets.left; } @@ -1240,9 +1365,10 @@ 'x-placement': data.placement }; - // Update attributes and styles of `data` + // Update `data` attributes, styles and arrowStyles data.attributes = _extends({}, attributes, data.attributes); data.styles = _extends({}, styles, data.styles); + data.arrowStyles = _extends({}, data.offsets.arrow, data.arrowStyles); return data; } @@ -1283,6 +1409,8 @@ * @returns {Object} The data object, properly modified */ function arrow(data, options) { + var _data$offsets$arrow; + // arrow depends on keepTogether in order to work if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) { return data; @@ -1315,13 +1443,15 @@ var isVertical = ['left', 'right'].indexOf(placement) !== -1; var len = isVertical ? 'height' : 'width'; - var side = isVertical ? 'top' : 'left'; + var sideCapitalized = isVertical ? 'Top' : 'Left'; + var side = sideCapitalized.toLowerCase(); var altSide = isVertical ? 'left' : 'top'; var opSide = isVertical ? 'bottom' : 'right'; var arrowElementSize = getOuterSizes(arrowElement)[len]; // - // extends keepTogether behavior making sure the popper and its reference have enough pixels in conjuction + // extends keepTogether behavior making sure the popper and its + // reference have enough pixels in conjunction // // top/left side @@ -1332,20 +1462,23 @@ if (reference[side] + arrowElementSize > popper[opSide]) { data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide]; } + data.offsets.popper = getClientRect(data.offsets.popper); // compute center of the popper var center = reference[side] + reference[len] / 2 - arrowElementSize / 2; // Compute the sideValue using the updated popper offsets - var sideValue = center - getClientRect(data.offsets.popper)[side]; + // take popper margin in account because we don't have this info available + var css = getStyleComputedProperty(data.instance.popper); + var popperMarginSide = parseFloat(css['margin' + sideCapitalized], 10); + var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width'], 10); + var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide; // prevent arrowElement from being placed not contiguously to its popper sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0); data.arrowElement = arrowElement; - data.offsets.arrow = {}; - data.offsets.arrow[side] = Math.round(sideValue); - data.offsets.arrow[altSide] = ''; // make sure to unset any eventual altSide value from the DOM node + data.offsets.arrow = (_data$offsets$arrow = {}, defineProperty(_data$offsets$arrow, side, Math.round(sideValue)), defineProperty(_data$offsets$arrow, altSide, ''), _data$offsets$arrow); return data; } @@ -1388,7 +1521,7 @@ * - `top-end` (on top of reference, right aligned) * - `right-start` (on right of reference, top aligned) * - `bottom` (on bottom, centered) - * - `auto-right` (on the side with more space available, alignment depends by placement) + * - `auto-end` (on the side with more space available, alignment depends by placement) * * @static * @type {Array} @@ -1444,7 +1577,7 @@ return data; } - var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement); + var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement, data.positionFixed); var placement = data.placement.split('-')[0]; var placementOpposite = getOppositePlacement(placement); @@ -1490,7 +1623,14 @@ // flip the variation if required var isVertical = ['top', 'bottom'].indexOf(placement) !== -1; - var flippedVariation = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom); + + // flips variation if reference element overflows boundaries + var flippedVariationByRef = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom); + + // flips variation if popper content overflows boundaries + var flippedVariationByContent = !!options.flipVariationsByContent && (isVertical && variation === 'start' && overflowsRight || isVertical && variation === 'end' && overflowsLeft || !isVertical && variation === 'start' && overflowsBottom || !isVertical && variation === 'end' && overflowsTop); + + var flippedVariation = flippedVariationByRef || flippedVariationByContent; if (overlapsRef || overflowsBoundaries || flippedVariation) { // this boolean to detect any flip loop @@ -1736,7 +1876,27 @@ boundariesElement = getOffsetParent(boundariesElement); } - var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement); + // NOTE: DOM access here + // resets the popper's position so that the document size can be calculated excluding + // the size of the popper element itself + var transformProp = getSupportedPropertyName('transform'); + var popperStyles = data.instance.popper.style; // assignment to help minification + var top = popperStyles.top, + left = popperStyles.left, + transform = popperStyles[transformProp]; + + popperStyles.top = ''; + popperStyles.left = ''; + popperStyles[transformProp] = ''; + + var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement, data.positionFixed); + + // NOTE: DOM access here + // restores the original style properties after the offsets have been computed + popperStyles.top = top; + popperStyles.left = left; + popperStyles[transformProp] = transform; + options.boundaries = boundaries; var order = options.priority; @@ -1859,7 +2019,7 @@ var subtractLength = ['top', 'left'].indexOf(basePlacement) === -1; - popper[isHoriz ? 'left' : 'top'] = reference[placement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0); + popper[isHoriz ? 'left' : 'top'] = reference[basePlacement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0); data.placement = getOppositePlacement(placement); data.offsets.popper = getClientRect(popper); @@ -1910,7 +2070,7 @@ * The `offset` modifier can shift your popper on both its axis. * * It accepts the following units: - * - `px` or unitless, interpreted as pixels + * - `px` or unit-less, interpreted as pixels * - `%` or `%r`, percentage relative to the length of the reference element * - `%p`, percentage relative to the length of the popper element * - `vw`, CSS viewport width unit @@ -1918,7 +2078,7 @@ * * For length is intended the main axis relative to the placement of the popper.