Commit 404edad2 authored by xjm's avatar xjm

Issue #3074267 by finnsky, bnjmnm, Wim Leers, zrpnr, lauriii, xjm, catch,...

Issue #3074267 by finnsky, bnjmnm, Wim Leers, zrpnr, lauriii, xjm, catch, droplet, justafish, FezVrasta: Replace jQuery UI position() with PopperJS

(cherry picked from commit c0e8fed3)
parent d1fca822
......@@ -19,6 +19,7 @@
"matchMedia": true,
"Backbone": true,
"Modernizr": true,
"Popper": true,
"CKEDITOR": true
},
"rules": {
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -934,6 +934,15 @@ picturefill:
dependencies:
- core/matchmedia
popperjs:
version: "1.15.0"
license:
name: MIT
url: https://github.com/FezVrasta/popper.js/blob/v1.15.0/LICENSE.md
gpl-compatible: true
js:
assets/vendor/popperjs/popper.min.js: { minified: true }
underscore:
remote: https://github.com/jashkenas/underscore
version: "1.8.3"
......
......@@ -3,7 +3,7 @@
* A Backbone View that provides an entity level toolbar.
*/
(function($, _, Backbone, Drupal, debounce) {
(function($, _, Backbone, Drupal, debounce, Popper) {
Drupal.quickedit.EntityToolbarView = Backbone.View.extend(
/** @lends Drupal.quickedit.EntityToolbarView# */ {
/**
......@@ -198,7 +198,7 @@
},
/**
* Uses the jQuery.ui.position() method to position the entity toolbar.
* Uses the Popper() method to position the entity toolbar.
*
* @param {HTMLElement} [element]
* The element against which the entity toolbar is positioned.
......@@ -291,82 +291,83 @@
} while (!of);
/**
* Refines the positioning algorithm of jquery.ui.position().
* Refines popper positioning.
*
* Invoked as the 'using' callback of jquery.ui.position() in
* positionToolbar().
*
* @param {*} view
* The view the positions will be calculated from.
* @param {object} suggested
* A hash of top and left values for the position that should be set. It
* can be forwarded to .css() or .animate().
* @param {object} info
* The position and dimensions of both the 'my' element and the 'of'
* elements, as well as calculations to their relative position. This
* object contains the following properties:
* @param {object} info.element
* A hash that contains information about the HTML element that will be
* positioned. Also known as the 'my' element.
* @param {object} info.target
* A hash that contains information about the HTML element that the
* 'my' element will be positioned against. Also known as the 'of'
* element.
* @param {object} data
* Data object containing popper and target data.
*/
function refinePosition(view, suggested, info) {
function refinePopper(data) {
// Determine if the pointer should be on the top or bottom.
const isBelow = suggested.top > info.target.top;
info.element.element.toggleClass(
const isBelow = data.offsets.popper.top > data.offsets.reference.top;
data.instance.popper.classList.toggle(
'quickedit-toolbar-pointer-top',
isBelow,
);
// Don't position the toolbar past the first or last editable field if
// the entity is the target.
if (view.$entity[0] === info.target.element[0]) {
if (that.$entity[0] === data.instance.reference) {
// Get the first or last field according to whether the toolbar is
// above or below the entity.
const $field = view.$entity
const $field = that.$entity
.find('.quickedit-editable')
.eq(isBelow ? -1 : 0);
if ($field.length > 0) {
suggested.top = isBelow
data.offsets.popper.top = isBelow
? $field.offset().top + $field.outerHeight(true)
: $field.offset().top - info.element.element.outerHeight(true);
: $field.offset().top -
$(data.instance.reference).outerHeight(true);
}
}
// Don't let the toolbar go outside the fence.
const fenceTop = view.$fence.offset().top;
const fenceHeight = view.$fence.height();
const toolbarHeight = info.element.element.outerHeight(true);
if (suggested.top < fenceTop) {
suggested.top = fenceTop;
} else if (suggested.top + toolbarHeight > fenceTop + fenceHeight) {
suggested.top = fenceTop + fenceHeight - toolbarHeight;
const fenceTop = that.$fence.offset().top;
const fenceHeight = that.$fence.height();
const toolbarHeight = $(data.instance.popper).outerHeight(true);
if (data.offsets.popper.top < fenceTop) {
data.offsets.popper.top = fenceTop;
} else if (
data.offsets.popper.top + toolbarHeight >
fenceTop + fenceHeight
) {
data.offsets.popper.top = fenceTop + fenceHeight - toolbarHeight;
}
// Position the toolbar.
info.element.element.css({
left: Math.floor(suggested.left),
top: Math.floor(suggested.top),
});
}
/**
* Calls the jquery.ui.position() method on the $el of this view.
* Calls the Popper() method on the $el of this view.
*/
function positionToolbar() {
const popperElement = that.el;
const referenceElement = of;
const boundariesElement = that.$fence[0];
const popperedge = edge === 'left' ? 'start' : 'end';
if (referenceElement !== undefined) {
if (!popperElement.classList.contains('js-popper-processed')) {
that.popper = new Popper(referenceElement, popperElement, {
placement: `top-${popperedge}`,
modifiers: {
flip: {
behavior: ['top', 'bottom'],
},
computeStyle: {
gpuAcceleration: false,
},
preventOverflow: {
boundariesElement,
},
},
onCreate: refinePopper,
onUpdate: refinePopper,
});
popperElement.classList.add('js-popper-processed');
} else {
that.popper.options.placement = `top-${popperedge}`;
that.popper.reference = referenceElement[0]
? referenceElement[0]
: referenceElement;
that.popper.update();
}
}
that.$el
.position({
my: `${edge} bottom`,
// Move the toolbar 1px towards the start edge of the 'of' element,
// plus any horizontal padding that may have been added to the
// element that is being added, to prevent unwanted horizontal
// movement.
at: `${edge}+${1 + horizontalPadding} top`,
of,
collision: 'flipfit',
using: refinePosition.bind(null, that),
within: that.$fence,
})
// Resize the toolbar to match the dimensions of the field, up to a
// maximum width that is equal to 90% of the field's width.
.css({
......@@ -579,4 +580,4 @@
},
},
);
})(jQuery, _, Backbone, Drupal, Drupal.debounce);
})(jQuery, _, Backbone, Drupal, Drupal.debounce, Popper);
......@@ -5,7 +5,7 @@
* @preserve
**/
(function ($, _, Backbone, Drupal, debounce) {
(function ($, _, Backbone, Drupal, debounce, Popper) {
Drupal.quickedit.EntityToolbarView = Backbone.View.extend({
_fieldToolbarRoot: null,
......@@ -163,42 +163,59 @@
check++;
} while (!of);
function refinePosition(view, suggested, info) {
var isBelow = suggested.top > info.target.top;
info.element.element.toggleClass('quickedit-toolbar-pointer-top', isBelow);
function refinePopper(data) {
var isBelow = data.offsets.popper.top > data.offsets.reference.top;
data.instance.popper.classList.toggle('quickedit-toolbar-pointer-top', isBelow);
if (view.$entity[0] === info.target.element[0]) {
var $field = view.$entity.find('.quickedit-editable').eq(isBelow ? -1 : 0);
if (that.$entity[0] === data.instance.reference) {
var $field = that.$entity.find('.quickedit-editable').eq(isBelow ? -1 : 0);
if ($field.length > 0) {
suggested.top = isBelow ? $field.offset().top + $field.outerHeight(true) : $field.offset().top - info.element.element.outerHeight(true);
data.offsets.popper.top = isBelow ? $field.offset().top + $field.outerHeight(true) : $field.offset().top - $(data.instance.reference).outerHeight(true);
}
}
var fenceTop = view.$fence.offset().top;
var fenceHeight = view.$fence.height();
var toolbarHeight = info.element.element.outerHeight(true);
if (suggested.top < fenceTop) {
suggested.top = fenceTop;
} else if (suggested.top + toolbarHeight > fenceTop + fenceHeight) {
suggested.top = fenceTop + fenceHeight - toolbarHeight;
var fenceTop = that.$fence.offset().top;
var fenceHeight = that.$fence.height();
var toolbarHeight = $(data.instance.popper).outerHeight(true);
if (data.offsets.popper.top < fenceTop) {
data.offsets.popper.top = fenceTop;
} else if (data.offsets.popper.top + toolbarHeight > fenceTop + fenceHeight) {
data.offsets.popper.top = fenceTop + fenceHeight - toolbarHeight;
}
info.element.element.css({
left: Math.floor(suggested.left),
top: Math.floor(suggested.top)
});
}
function positionToolbar() {
that.$el.position({
my: edge + ' bottom',
at: edge + '+' + (1 + horizontalPadding) + ' top',
of: of,
collision: 'flipfit',
using: refinePosition.bind(null, that),
within: that.$fence
}).css({
var popperElement = that.el;
var referenceElement = of;
var boundariesElement = that.$fence[0];
var popperedge = edge === 'left' ? 'start' : 'end';
if (referenceElement !== undefined) {
if (!popperElement.classList.contains('js-popper-processed')) {
that.popper = new Popper(referenceElement, popperElement, {
placement: 'top-' + popperedge,
modifiers: {
flip: {
behavior: ['top', 'bottom']
},
computeStyle: {
gpuAcceleration: false
},
preventOverflow: {
boundariesElement: boundariesElement
}
},
onCreate: refinePopper,
onUpdate: refinePopper
});
popperElement.classList.add('js-popper-processed');
} else {
that.popper.options.placement = 'top-' + popperedge;
that.popper.reference = referenceElement[0] ? referenceElement[0] : referenceElement;
that.popper.update();
}
}
that.$el.css({
'max-width': document.documentElement.clientWidth < 450 ? document.documentElement.clientWidth : 450,
'min-width': document.documentElement.clientWidth < 240 ? document.documentElement.clientWidth : 240,
......@@ -292,4 +309,4 @@
this.$el.removeClass('quickedit-animate-invisible');
}
});
})(jQuery, _, Backbone, Drupal, Drupal.debounce);
\ No newline at end of file
})(jQuery, _, Backbone, Drupal, Drupal.debounce, Popper);
\ No newline at end of file
......@@ -32,7 +32,6 @@ quickedit:
- core/underscore
- core/backbone
- core/jquery.form
- core/jquery.ui.position
- core/drupal
- core/drupal.displace
- core/drupal.form
......@@ -40,6 +39,7 @@ quickedit:
- core/drupal.debounce
- core/drupalSettings
- core/drupal.dialog
- core/popperjs
quickedit.inPlaceEditor.form:
version: VERSION
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment