Unverified Commit 9289782c authored by lauriii's avatar lauriii
Browse files

Issue #3201170 by bnjmnm, nod_, lauriii, zrpnr: Address jQuery UI position dependency

parent 1af77eaf
......@@ -115,7 +115,6 @@ drupal.autocomplete:
assets/vendor/jquery.ui/ui/jquery-1-7-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/keycode-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/plugin-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/position-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/safe-active-element-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/safe-blur-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/scroll-parent-min.js: { weight: -11.8, minified: true }
......@@ -137,6 +136,8 @@ drupal.autocomplete:
- core/drupalSettings
- core/drupal.ajax
- core/tabbable.jquery.shim
- core/drupal.jquery.position
drupal.array.find:
version: VERSION
......@@ -222,7 +223,6 @@ drupal.dialog:
assets/vendor/jquery.ui/ui/ie-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/keycode-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/plugin-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/position-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/safe-active-element-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/safe-blur-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/widget-min.js: { weight: -11.8, minified: true }
......@@ -246,6 +246,7 @@ drupal.dialog:
- core/drupal.debounce
- core/drupal.displace
- core/tabbable.jquery.shim
- core/drupal.jquery.position
drupal.dialog.ajax:
version: VERSION
......@@ -604,7 +605,6 @@ jquery.ui.dialog:
assets/vendor/jquery.ui/ui/jquery-1-7-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/keycode-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/plugin-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/position-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/safe-active-element-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/safe-blur-min.js: { weight: -11.8, minified: true }
assets/vendor/jquery.ui/ui/scroll-parent-min.js: { weight: -11.8, minified: true }
......@@ -624,6 +624,7 @@ jquery.ui.dialog:
dependencies:
- core/jquery
- core/tabbable.jquery.shim
- core/drupal.jquery.position
deprecated: *jquery_ui_unused_deprecated
jquery.ui.draggable:
......@@ -661,13 +662,23 @@ jquery.ui.mouse:
- core/jquery.ui.widget
deprecated: *jquery_ui_unused_deprecated
drupal.jquery.position:
# For most positioning needs, the core/popperjs library should be used. This
# is a modified version of jQuery UI position for that does not require any
# jQuery UI assets, only jQuery. It is provided by core for use with
# pre-existing libraries that expect the jQuery UI position API. Popperjs is
# recommended for any new uses.
version: VERSION
js:
misc/position.js: {}
dependencies:
- core/jquery
jquery.ui.position:
version: *jquery_ui_version
license: *jquery_ui_license
js:
assets/vendor/jquery.ui/ui/position-min.js: { weight: -11.8, minified: true }
dependencies:
- core/jquery.ui
- core/drupal.jquery.position
deprecated: *jquery_ui_unused_deprecated
jquery.ui.resizable:
......
......@@ -595,6 +595,7 @@ fixnull
flexbox
flexslider
flickr
flipfit
floatingspace
foaf
foat
......
/**
* @file
* A modified version of jQuery UI position.
*
* Per jQuery UI's public domain license, it is permissible to run modified
* versions of their code. This file offers the same functionality as what is
* provided by jQuery UI position, but refactored to meet Drupal coding
* standards, and restructured so it extends jQuery core instead of jQuery UI.
*
* For most positioning needs, the core/popperjs library should be used instead
* of the functionality provided here. This is provided to support pre-existing
* code that expects the jQuery position API.
*
* @see https://github.com/jquery/jquery-ui/blob/1.12.1/LICENSE.txt
* @see https://raw.githubusercontent.com/jquery/jquery-ui/1.12.1/ui/position.js
*/
/**
* This provides ported version of jQuery UI position, refactored to not depend
* on jQuery UI and to meet Drupal JavaScript coding standards. Functionality
* and usage is identical. It positions an element relative to another. The
* `position()` function can be called by any jQuery object. Additional details
* on using `position()` are provided in this file in the docblock for
* $.fn.position.
*/
(($) => {
let cachedScrollbarWidth = null;
const { max, abs } = Math;
const regexHorizontal = /left|center|right/;
const regexVertical = /top|center|bottom/;
const regexOffset = /[+-]\d+(\.[\d]+)?%?/;
const regexPosition = /^\w+/;
const regexPercent = /%$/;
const _position = $.fn.position;
function getOffsets(offsets, width, height) {
return [
parseFloat(offsets[0]) *
(regexPercent.test(offsets[0]) ? width / 100 : 1),
parseFloat(offsets[1]) *
(regexPercent.test(offsets[1]) ? height / 100 : 1),
];
}
function parseCss(element, property) {
return parseInt($.css(element, property), 10) || 0;
}
function getDimensions(elem) {
const raw = elem[0];
if (raw.nodeType === 9) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: 0, left: 0 },
};
}
if ($.isWindow(raw)) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: elem.scrollTop(), left: elem.scrollLeft() },
};
}
if (raw.preventDefault) {
return {
width: 0,
height: 0,
offset: { top: raw.pageY, left: raw.pageX },
};
}
return {
width: elem.outerWidth(),
height: elem.outerHeight(),
offset: elem.offset(),
};
}
const collisions = {
fit: {
left(position, data) {
const { within } = data;
const withinOffset = within.isWindow
? within.scrollLeft
: within.offset.left;
const outerWidth = within.width;
const collisionPosLeft =
position.left - data.collisionPosition.marginLeft;
const overLeft = withinOffset - collisionPosLeft;
const overRight =
collisionPosLeft + data.collisionWidth - outerWidth - withinOffset;
let newOverRight;
// Element is wider than within
if (data.collisionWidth > outerWidth) {
// Element is initially over the left side of within
if (overLeft > 0 && overRight <= 0) {
newOverRight =
position.left +
overLeft +
data.collisionWidth -
outerWidth -
withinOffset;
position.left += overLeft - newOverRight;
// Element is initially over right side of within
} else if (overRight > 0 && overLeft <= 0) {
position.left = withinOffset;
// Element is initially over both left and right sides of within
} else if (overLeft > overRight) {
position.left = withinOffset + outerWidth - data.collisionWidth;
} else {
position.left = withinOffset;
}
// Too far left -> align with left edge
} else if (overLeft > 0) {
position.left += overLeft;
// Too far right -> align with right edge
} else if (overRight > 0) {
position.left -= overRight;
// Adjust based on position and margin
} else {
position.left = max(position.left - collisionPosLeft, position.left);
}
},
top(position, data) {
const { within } = data;
const withinOffset = within.isWindow
? within.scrollTop
: within.offset.top;
const outerHeight = data.within.height;
const collisionPosTop = position.top - data.collisionPosition.marginTop;
const overTop = withinOffset - collisionPosTop;
const overBottom =
collisionPosTop + data.collisionHeight - outerHeight - withinOffset;
let newOverBottom;
// Element is taller than within
if (data.collisionHeight > outerHeight) {
// Element is initially over the top of within
if (overTop > 0 && overBottom <= 0) {
newOverBottom =
position.top +
overTop +
data.collisionHeight -
outerHeight -
withinOffset;
position.top += overTop - newOverBottom;
// Element is initially over bottom of within
} else if (overBottom > 0 && overTop <= 0) {
position.top = withinOffset;
// Element is initially over both top and bottom of within
} else if (overTop > overBottom) {
position.top = withinOffset + outerHeight - data.collisionHeight;
} else {
position.top = withinOffset;
}
// Too far up -> align with top
} else if (overTop > 0) {
position.top += overTop;
// Too far down -> align with bottom edge
} else if (overBottom > 0) {
position.top -= overBottom;
// Adjust based on position and margin
} else {
position.top = max(position.top - collisionPosTop, position.top);
}
},
},
flip: {
left(position, data) {
const { within } = data;
const withinOffset = within.offset.left + within.scrollLeft;
const outerWidth = within.width;
const offsetLeft = within.isWindow
? within.scrollLeft
: within.offset.left;
const collisionPosLeft =
position.left - data.collisionPosition.marginLeft;
const overLeft = collisionPosLeft - offsetLeft;
const overRight =
collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft;
const myOffset =
// eslint-disable-next-line no-nested-ternary
data.my[0] === 'left'
? -data.elemWidth
: data.my[0] === 'right'
? data.elemWidth
: 0;
const atOffset =
// eslint-disable-next-line no-nested-ternary
data.at[0] === 'left'
? data.targetWidth
: data.at[0] === 'right'
? -data.targetWidth
: 0;
const offset = -2 * data.offset[0];
let newOverRight;
let newOverLeft;
if (overLeft < 0) {
newOverRight =
position.left +
myOffset +
atOffset +
offset +
data.collisionWidth -
outerWidth -
withinOffset;
if (newOverRight < 0 || newOverRight < abs(overLeft)) {
position.left += myOffset + atOffset + offset;
}
} else if (overRight > 0) {
newOverLeft =
position.left -
data.collisionPosition.marginLeft +
myOffset +
atOffset +
offset -
offsetLeft;
if (newOverLeft > 0 || abs(newOverLeft) < overRight) {
position.left += myOffset + atOffset + offset;
}
}
},
top(position, data) {
const { within } = data;
const withinOffset = within.offset.top + within.scrollTop;
const outerHeight = within.height;
const offsetTop = within.isWindow
? within.scrollTop
: within.offset.top;
const collisionPosTop = position.top - data.collisionPosition.marginTop;
const overTop = collisionPosTop - offsetTop;
const overBottom =
collisionPosTop + data.collisionHeight - outerHeight - offsetTop;
const top = data.my[1] === 'top';
// eslint-disable-next-line no-nested-ternary
const myOffset = top
? -data.elemHeight
: data.my[1] === 'bottom'
? data.elemHeight
: 0;
const atOffset =
// eslint-disable-next-line no-nested-ternary
data.at[1] === 'top'
? data.targetHeight
: data.at[1] === 'bottom'
? -data.targetHeight
: 0;
const offset = -2 * data.offset[1];
let newOverTop;
let newOverBottom;
if (overTop < 0) {
newOverBottom =
position.top +
myOffset +
atOffset +
offset +
data.collisionHeight -
outerHeight -
withinOffset;
if (newOverBottom < 0 || newOverBottom < abs(overTop)) {
position.top += myOffset + atOffset + offset;
}
} else if (overBottom > 0) {
newOverTop =
position.top -
data.collisionPosition.marginTop +
myOffset +
atOffset +
offset -
offsetTop;
if (newOverTop > 0 || abs(newOverTop) < overBottom) {
position.top += myOffset + atOffset + offset;
}
}
},
},
flipfit: {
left(...args) {
collisions.flip.left.apply(this, args);
collisions.fit.left.apply(this, args);
},
top(...args) {
collisions.flip.top.apply(this, args);
collisions.fit.top.apply(this, args);
},
},
};
$.position = {
scrollbarWidth() {
if (cachedScrollbarWidth !== undefined) {
return cachedScrollbarWidth;
}
const div = $(
'<div ' +
"style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
"<div style='height:100px;width:auto;'></div></div>",
);
const innerDiv = div.children()[0];
$('body').append(div);
const w1 = innerDiv.offsetWidth;
div.css('overflow', 'scroll');
let w2 = innerDiv.offsetWidth;
if (w1 === w2) {
w2 = div[0].clientWidth;
}
div.remove();
cachedScrollbarWidth = w1 - w2;
return cachedScrollbarWidth;
},
getScrollInfo(within) {
const overflowX =
within.isWindow || within.isDocument
? ''
: within.element.css('overflow-x');
const overflowY =
within.isWindow || within.isDocument
? ''
: within.element.css('overflow-y');
const hasOverflowX =
overflowX === 'scroll' ||
(overflowX === 'auto' && within.width < within.element[0].scrollWidth);
const hasOverflowY =
overflowY === 'scroll' ||
(overflowY === 'auto' &&
within.height < within.element[0].scrollHeight);
return {
width: hasOverflowY ? $.position.scrollbarWidth() : 0,
height: hasOverflowX ? $.position.scrollbarWidth() : 0,
};
},
getWithinInfo(element) {
const withinElement = $(element || window);
const isWindow = $.isWindow(withinElement[0]);
const isDocument = !!withinElement[0] && withinElement[0].nodeType === 9;
const hasOffset = !isWindow && !isDocument;
return {
element: withinElement,
isWindow,
isDocument,
offset: hasOffset ? $(element).offset() : { left: 0, top: 0 },
scrollLeft: withinElement.scrollLeft(),
scrollTop: withinElement.scrollTop(),
width: withinElement.outerWidth(),
height: withinElement.outerHeight(),
};
},
};
// eslint-disable-next-line func-names
/**
* Positions an element relative to another.
*
* The following documentation is originally from
* {@link https://api.jqueryui.com/position/}.
*
* @param {Object} options - the options object.
* @param {string} options.my - Defines which position on the element being
* positioned to align with the target element: "horizontal vertical"
* alignment. A single value such as "right" will be normalized to "right
* center", "top" will be normalized to "center top" (following CSS
* convention). Acceptable horizontal values: "left", "center", "right".
* Acceptable vertical values: "top", "center", "bottom". Example: "left
* top" or "center center". Each dimension can also contain offsets, in
* pixels or percent, e.g., "right+10 top-25%". Percentage offsets are
* relative to the element being positioned. Default value is "center".
* @param {string} options.at - Defines which position on the target element
* to align the positioned element against: "horizontal vertical" alignment.
* See the `my` option for full details on possible values. Percentage
* offsets are relative to the target element. Default value is "center".
* @param {string|Element|jQuery|Event|null} options.of - Which element to
* position against. If you provide a selector or jQuery object, the first
* matching element will be used. If you provide an event object, the pageX
* and pageY properties will be used. Example: "#top-menu". Default value is
* null.
* @param {string} options.collision - When the positioned element overflows
* the window in some direction, move it to an alternative position. Similar
* to `my` and `at`, this accepts a single value or a pair for
* horizontal/vertical, e.g., "flip", "fit", "fit flip", "fit none". Default
* value is "flip". The options work as follows:
* - "flip": Flips the element to the opposite side of the target and the
* collision detection is run again to see if it will fit. Whichever side
* allows more of the element to be visible will be used.
* - "fit": Shift the element away from the edge of the window.
* - "flipfit": First applies the flip logic, placing the element on
* whichever side allows more of the element to be visible. Then the fit
* logic is applied to ensure as much of the element is visible as
* possible.
* "none": Does not apply any collision detection.
* @param {function|null} options.using - When specified, the actual property
* setting is delegated to this callback. Receives two parameters: The first
* is a hash of top and left values for the position that should be set and
* can be forwarded to .css() or .animate().The second provides feedback
* about the position and dimensions of both elements, as well as
* calculations to their relative position. Both target and element have
* these properties: element, left, top, width, height. In addition, there's
* horizontal, vertical and important, providing twelve potential directions
* like { horizontal: "center", vertical: "left", important: "horizontal" }.
* Default value is null.
* @param {string|Element|jQuery} options.within - Element to position within,
* affecting collision detection. If you provide a selector or jQuery
* object, the first matching element will be used. Default value is window.
*
* @return {jQuery}
* The jQuery object that called called this function.
*/
$.fn.position = function (options) {
if (!options || !options.of) {
// eslint-disable-next-line prefer-rest-params
return _position.apply(this, arguments);
}
// Make a copy, we don't want to modify arguments
options = $.extend({}, options);
const within = $.position.getWithinInfo(options.within);
const scrollInfo = $.position.getScrollInfo(within);
const collision = (options.collision || 'flip').split(' ');
const offsets = {};
const target = $(options.of);
const dimensions = getDimensions(target);
const targetWidth = dimensions.width;
const targetHeight = dimensions.height;
const targetOffset = dimensions.offset;
if (target[0].preventDefault) {
// Force left top to allow flipping
options.at = 'left top';
}
// Clone to reuse original targetOffset later
const basePosition = $.extend({}, targetOffset);
// Force my and at to have valid horizontal and vertical positions
// if a value is missing or invalid, it will be converted to center
// eslint-disable-next-line func-names
$.each(['my', 'at'], function () {
let pos = (options[this] || '').split(' ');
if (pos.length === 1) {
// eslint-disable-next-line no-nested-ternary
pos = regexHorizontal.test(pos[0])
? pos.concat(['center'])
: regexVertical.test(pos[0])
? ['center'].concat(pos)
: ['center', 'center'];
}
pos[0] = regexHorizontal.test(pos[0]) ? pos[0] : 'center';
pos[1] = regexVertical.test(pos[1]) ? pos[1] : 'center';
// Calculate offsets
const horizontalOffset = regexOffset.exec(pos[0]);
const verticalOffset = regexOffset.exec(pos[1]);
offsets[this] = [
horizontalOffset ? horizontalOffset[0] : 0,
verticalOffset ? verticalOffset[0] : 0,
];
// Reduce to just the positions without the offsets
options[this] = [
regexPosition.exec(pos[0])[0],
regexPosition.exec(pos[1])[0],
];
});
// Normalize collision option
if (collision.length === 1) {
// eslint-disable-next-line prefer-destructuring
collision[1] = collision[0];
}
if (options.at[0] === 'right') {
basePosition.left += targetWidth;
} else if (options.at[0] === 'center') {
basePosition.left += targetWidth / 2;
}
if (options.at[1] === 'bottom') {
basePosition.top += targetHeight;
} else if (options.at[1] === 'center') {
basePosition.top += targetHeight / 2;
}
const atOffset = getOffsets(offsets.at, targetWidth, targetHeight);
basePosition.left += atOffset[0];
basePosition.top += atOffset[1];
// eslint-disable-next-line func-names
return this.each(function () {
let using;
const elem = $(this);
const elemWidth = elem.outerWidth();
const elemHeight = elem.outerHeight();
const marginLeft = parseCss(this, 'marginLeft');
const marginTop = parseCss(this, 'marginTop');
const collisionWidth =
elemWidth +
marginLeft +
parseCss(this, 'marginRight') +
scrollInfo.width;
const collisionHeight =
elemHeight +
marginTop +
parseCss(this, 'marginBottom') +
scrollInfo.height;
const position = $.extend({}, basePosition);
const myOffset = getOffsets(
offsets.my,
elem.outerWidth(),