diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index e366dc84c9af1bfe51913a3919fe9148073494a0..551929cd63a18185c2b349ae6dfeee85ac78829b 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -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:
diff --git a/core/misc/cspell/dictionary.txt b/core/misc/cspell/dictionary.txt
index de78c6c615fccbd7949c3f58a3781d85da72d4d4..d660d22c5f1a6398cd071610b068081b72b222b3 100644
--- a/core/misc/cspell/dictionary.txt
+++ b/core/misc/cspell/dictionary.txt
@@ -595,6 +595,7 @@ fixnull
 flexbox
 flexslider
 flickr
+flipfit
 floatingspace
 foaf
 foat
diff --git a/core/misc/position.es6.js b/core/misc/position.es6.js
new file mode 100644
index 0000000000000000000000000000000000000000..4bd6baa34213b18c3add6049beeac21b24aee592
--- /dev/null
+++ b/core/misc/position.es6.js
@@ -0,0 +1,625 @@
+/**
+ * @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(),
+        elem.outerHeight(),
+      );
+
+      if (options.my[0] === 'right') {
+        position.left -= elemWidth;
+      } else if (options.my[0] === 'center') {
+        position.left -= elemWidth / 2;
+      }
+
+      if (options.my[1] === 'bottom') {
+        position.top -= elemHeight;
+      } else if (options.my[1] === 'center') {
+        position.top -= elemHeight / 2;
+      }
+
+      position.left += myOffset[0];
+      position.top += myOffset[1];
+
+      const collisionPosition = {
+        marginLeft,
+        marginTop,
+      };
+
+      // eslint-disable-next-line func-names
+      $.each(['left', 'top'], function (i, dir) {
+        if (collisions[collision[i]]) {
+          collisions[collision[i]][dir](position, {
+            targetWidth,
+            targetHeight,
+            elemWidth,
+            elemHeight,
+            collisionPosition,
+            collisionWidth,
+            collisionHeight,
+            offset: [atOffset[0] + myOffset[0], atOffset[1] + myOffset[1]],
+            my: options.my,
+            at: options.at,
+            within,
+            elem,
+          });
+        }
+      });
+
+      if (options.using) {
+        // Adds feedback as second argument to using callback, if present
+        // eslint-disable-next-line func-names
+        using = function (props) {
+          const left = targetOffset.left - position.left;
+          const right = left + targetWidth - elemWidth;
+          const top = targetOffset.top - position.top;
+          const bottom = top + targetHeight - elemHeight;
+          const feedback = {
+            target: {
+              element: target,
+              left: targetOffset.left,
+              top: targetOffset.top,
+              width: targetWidth,
+              height: targetHeight,
+            },
+            element: {
+              element: elem,
+              left: position.left,
+              top: position.top,
+              width: elemWidth,
+              height: elemHeight,
+            },
+            // eslint-disable-next-line no-nested-ternary
+            horizontal: right < 0 ? 'left' : left > 0 ? 'right' : 'center',
+            // eslint-disable-next-line no-nested-ternary
+            vertical: bottom < 0 ? 'top' : top > 0 ? 'bottom' : 'middle',
+          };
+          if (targetWidth < elemWidth && abs(left + right) < targetWidth) {
+            feedback.horizontal = 'center';
+          }
+          if (targetHeight < elemHeight && abs(top + bottom) < targetHeight) {
+            feedback.vertical = 'middle';
+          }
+          if (max(abs(left), abs(right)) > max(abs(top), abs(bottom))) {
+            feedback.important = 'horizontal';
+          } else {
+            feedback.important = 'vertical';
+          }
+          options.using.call(this, props, feedback);
+        };
+      }
+
+      elem.offset($.extend(position, { using }));
+    });
+  };
+
+  // Although $.ui.position is not built to be called directly, some legacy code
+  // may have checks for the presence of $.ui.position, which can be used to
+  // confirm the presence of jQuery UI position's API, as opposed to the more
+  // limited version provided by jQuery.
+  if (!$.hasOwnProperty('ui')) {
+    $.ui = {};
+  }
+  $.ui.position = collisions;
+})(jQuery);
diff --git a/core/misc/position.js b/core/misc/position.js
new file mode 100644
index 0000000000000000000000000000000000000000..45d23e00518a50163aa250acb0c2744ae966dfec
--- /dev/null
+++ b/core/misc/position.js
@@ -0,0 +1,417 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function ($) {
+  var cachedScrollbarWidth = null;
+  var max = Math.max,
+      abs = Math.abs;
+  var regexHorizontal = /left|center|right/;
+  var regexVertical = /top|center|bottom/;
+  var regexOffset = /[+-]\d+(\.[\d]+)?%?/;
+  var regexPosition = /^\w+/;
+  var regexPercent = /%$/;
+  var _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) {
+    var 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()
+    };
+  }
+
+  var collisions = {
+    fit: {
+      left: function left(position, data) {
+        var within = data.within;
+        var withinOffset = within.isWindow ? within.scrollLeft : within.offset.left;
+        var outerWidth = within.width;
+        var collisionPosLeft = position.left - data.collisionPosition.marginLeft;
+        var overLeft = withinOffset - collisionPosLeft;
+        var overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset;
+        var newOverRight;
+
+        if (data.collisionWidth > outerWidth) {
+          if (overLeft > 0 && overRight <= 0) {
+            newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
+            position.left += overLeft - newOverRight;
+          } else if (overRight > 0 && overLeft <= 0) {
+            position.left = withinOffset;
+          } else if (overLeft > overRight) {
+            position.left = withinOffset + outerWidth - data.collisionWidth;
+          } else {
+            position.left = withinOffset;
+          }
+        } else if (overLeft > 0) {
+          position.left += overLeft;
+        } else if (overRight > 0) {
+          position.left -= overRight;
+        } else {
+          position.left = max(position.left - collisionPosLeft, position.left);
+        }
+      },
+      top: function top(position, data) {
+        var within = data.within;
+        var withinOffset = within.isWindow ? within.scrollTop : within.offset.top;
+        var outerHeight = data.within.height;
+        var collisionPosTop = position.top - data.collisionPosition.marginTop;
+        var overTop = withinOffset - collisionPosTop;
+        var overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset;
+        var newOverBottom;
+
+        if (data.collisionHeight > outerHeight) {
+          if (overTop > 0 && overBottom <= 0) {
+            newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
+            position.top += overTop - newOverBottom;
+          } else if (overBottom > 0 && overTop <= 0) {
+            position.top = withinOffset;
+          } else if (overTop > overBottom) {
+            position.top = withinOffset + outerHeight - data.collisionHeight;
+          } else {
+            position.top = withinOffset;
+          }
+        } else if (overTop > 0) {
+          position.top += overTop;
+        } else if (overBottom > 0) {
+          position.top -= overBottom;
+        } else {
+          position.top = max(position.top - collisionPosTop, position.top);
+        }
+      }
+    },
+    flip: {
+      left: function left(position, data) {
+        var within = data.within;
+        var withinOffset = within.offset.left + within.scrollLeft;
+        var outerWidth = within.width;
+        var offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left;
+        var collisionPosLeft = position.left - data.collisionPosition.marginLeft;
+        var overLeft = collisionPosLeft - offsetLeft;
+        var overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft;
+        var myOffset = data.my[0] === 'left' ? -data.elemWidth : data.my[0] === 'right' ? data.elemWidth : 0;
+        var atOffset = data.at[0] === 'left' ? data.targetWidth : data.at[0] === 'right' ? -data.targetWidth : 0;
+        var offset = -2 * data.offset[0];
+        var newOverRight;
+        var 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: function top(position, data) {
+        var within = data.within;
+        var withinOffset = within.offset.top + within.scrollTop;
+        var outerHeight = within.height;
+        var offsetTop = within.isWindow ? within.scrollTop : within.offset.top;
+        var collisionPosTop = position.top - data.collisionPosition.marginTop;
+        var overTop = collisionPosTop - offsetTop;
+        var overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop;
+        var top = data.my[1] === 'top';
+        var myOffset = top ? -data.elemHeight : data.my[1] === 'bottom' ? data.elemHeight : 0;
+        var atOffset = data.at[1] === 'top' ? data.targetHeight : data.at[1] === 'bottom' ? -data.targetHeight : 0;
+        var offset = -2 * data.offset[1];
+        var newOverTop;
+        var 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: function left() {
+        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+          args[_key] = arguments[_key];
+        }
+
+        collisions.flip.left.apply(this, args);
+        collisions.fit.left.apply(this, args);
+      },
+      top: function top() {
+        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+          args[_key2] = arguments[_key2];
+        }
+
+        collisions.flip.top.apply(this, args);
+        collisions.fit.top.apply(this, args);
+      }
+    }
+  };
+  $.position = {
+    scrollbarWidth: function scrollbarWidth() {
+      if (cachedScrollbarWidth !== undefined) {
+        return cachedScrollbarWidth;
+      }
+
+      var div = $('<div ' + "style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" + "<div style='height:100px;width:auto;'></div></div>");
+      var innerDiv = div.children()[0];
+      $('body').append(div);
+      var w1 = innerDiv.offsetWidth;
+      div.css('overflow', 'scroll');
+      var w2 = innerDiv.offsetWidth;
+
+      if (w1 === w2) {
+        w2 = div[0].clientWidth;
+      }
+
+      div.remove();
+      cachedScrollbarWidth = w1 - w2;
+      return cachedScrollbarWidth;
+    },
+    getScrollInfo: function getScrollInfo(within) {
+      var overflowX = within.isWindow || within.isDocument ? '' : within.element.css('overflow-x');
+      var overflowY = within.isWindow || within.isDocument ? '' : within.element.css('overflow-y');
+      var hasOverflowX = overflowX === 'scroll' || overflowX === 'auto' && within.width < within.element[0].scrollWidth;
+      var hasOverflowY = overflowY === 'scroll' || overflowY === 'auto' && within.height < within.element[0].scrollHeight;
+      return {
+        width: hasOverflowY ? $.position.scrollbarWidth() : 0,
+        height: hasOverflowX ? $.position.scrollbarWidth() : 0
+      };
+    },
+    getWithinInfo: function getWithinInfo(element) {
+      var withinElement = $(element || window);
+      var isWindow = $.isWindow(withinElement[0]);
+      var isDocument = !!withinElement[0] && withinElement[0].nodeType === 9;
+      var hasOffset = !isWindow && !isDocument;
+      return {
+        element: withinElement,
+        isWindow: isWindow,
+        isDocument: isDocument,
+        offset: hasOffset ? $(element).offset() : {
+          left: 0,
+          top: 0
+        },
+        scrollLeft: withinElement.scrollLeft(),
+        scrollTop: withinElement.scrollTop(),
+        width: withinElement.outerWidth(),
+        height: withinElement.outerHeight()
+      };
+    }
+  };
+
+  $.fn.position = function (options) {
+    if (!options || !options.of) {
+      return _position.apply(this, arguments);
+    }
+
+    options = $.extend({}, options);
+    var within = $.position.getWithinInfo(options.within);
+    var scrollInfo = $.position.getScrollInfo(within);
+    var collision = (options.collision || 'flip').split(' ');
+    var offsets = {};
+    var target = $(options.of);
+    var dimensions = getDimensions(target);
+    var targetWidth = dimensions.width;
+    var targetHeight = dimensions.height;
+    var targetOffset = dimensions.offset;
+
+    if (target[0].preventDefault) {
+      options.at = 'left top';
+    }
+
+    var basePosition = $.extend({}, targetOffset);
+    $.each(['my', 'at'], function () {
+      var pos = (options[this] || '').split(' ');
+
+      if (pos.length === 1) {
+        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';
+      var horizontalOffset = regexOffset.exec(pos[0]);
+      var verticalOffset = regexOffset.exec(pos[1]);
+      offsets[this] = [horizontalOffset ? horizontalOffset[0] : 0, verticalOffset ? verticalOffset[0] : 0];
+      options[this] = [regexPosition.exec(pos[0])[0], regexPosition.exec(pos[1])[0]];
+    });
+
+    if (collision.length === 1) {
+      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;
+    }
+
+    var atOffset = getOffsets(offsets.at, targetWidth, targetHeight);
+    basePosition.left += atOffset[0];
+    basePosition.top += atOffset[1];
+    return this.each(function () {
+      var using;
+      var elem = $(this);
+      var elemWidth = elem.outerWidth();
+      var elemHeight = elem.outerHeight();
+      var marginLeft = parseCss(this, 'marginLeft');
+      var marginTop = parseCss(this, 'marginTop');
+      var collisionWidth = elemWidth + marginLeft + parseCss(this, 'marginRight') + scrollInfo.width;
+      var collisionHeight = elemHeight + marginTop + parseCss(this, 'marginBottom') + scrollInfo.height;
+      var position = $.extend({}, basePosition);
+      var myOffset = getOffsets(offsets.my, elem.outerWidth(), elem.outerHeight());
+
+      if (options.my[0] === 'right') {
+        position.left -= elemWidth;
+      } else if (options.my[0] === 'center') {
+        position.left -= elemWidth / 2;
+      }
+
+      if (options.my[1] === 'bottom') {
+        position.top -= elemHeight;
+      } else if (options.my[1] === 'center') {
+        position.top -= elemHeight / 2;
+      }
+
+      position.left += myOffset[0];
+      position.top += myOffset[1];
+      var collisionPosition = {
+        marginLeft: marginLeft,
+        marginTop: marginTop
+      };
+      $.each(['left', 'top'], function (i, dir) {
+        if (collisions[collision[i]]) {
+          collisions[collision[i]][dir](position, {
+            targetWidth: targetWidth,
+            targetHeight: targetHeight,
+            elemWidth: elemWidth,
+            elemHeight: elemHeight,
+            collisionPosition: collisionPosition,
+            collisionWidth: collisionWidth,
+            collisionHeight: collisionHeight,
+            offset: [atOffset[0] + myOffset[0], atOffset[1] + myOffset[1]],
+            my: options.my,
+            at: options.at,
+            within: within,
+            elem: elem
+          });
+        }
+      });
+
+      if (options.using) {
+        using = function using(props) {
+          var left = targetOffset.left - position.left;
+          var right = left + targetWidth - elemWidth;
+          var top = targetOffset.top - position.top;
+          var bottom = top + targetHeight - elemHeight;
+          var feedback = {
+            target: {
+              element: target,
+              left: targetOffset.left,
+              top: targetOffset.top,
+              width: targetWidth,
+              height: targetHeight
+            },
+            element: {
+              element: elem,
+              left: position.left,
+              top: position.top,
+              width: elemWidth,
+              height: elemHeight
+            },
+            horizontal: right < 0 ? 'left' : left > 0 ? 'right' : 'center',
+            vertical: bottom < 0 ? 'top' : top > 0 ? 'bottom' : 'middle'
+          };
+
+          if (targetWidth < elemWidth && abs(left + right) < targetWidth) {
+            feedback.horizontal = 'center';
+          }
+
+          if (targetHeight < elemHeight && abs(top + bottom) < targetHeight) {
+            feedback.vertical = 'middle';
+          }
+
+          if (max(abs(left), abs(right)) > max(abs(top), abs(bottom))) {
+            feedback.important = 'horizontal';
+          } else {
+            feedback.important = 'vertical';
+          }
+
+          options.using.call(this, props, feedback);
+        };
+      }
+
+      elem.offset($.extend(position, {
+        using: using
+      }));
+    });
+  };
+
+  if (!$.hasOwnProperty('ui')) {
+    $.ui = {};
+  }
+
+  $.ui.position = collisions;
+})(jQuery);
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/position_shim_test/css/position.shim.test.css b/core/modules/system/tests/modules/position_shim_test/css/position.shim.test.css
new file mode 100644
index 0000000000000000000000000000000000000000..3ae288e5821e095c41433c585bc563f33465f6ff
--- /dev/null
+++ b/core/modules/system/tests/modules/position_shim_test/css/position.shim.test.css
@@ -0,0 +1,19 @@
+body {
+  overflow-x: hidden;
+}
+#position-reference-1 {
+  width: 200px;
+  height: 200px;
+  margin: 75px 100px;
+  border: 1px solid black;
+}
+
+.test-tip {
+  width: 75px;
+  height: 75px;
+  border: 1px solid blue;
+}
+
+[data-off-canvas-main-canvas] {
+  min-height: 1200px;
+}
diff --git a/core/modules/system/tests/modules/position_shim_test/position_shim_test.info.yml b/core/modules/system/tests/modules/position_shim_test/position_shim_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a8f2cc2228455e9ca1c6deb407f206da949820e5
--- /dev/null
+++ b/core/modules/system/tests/modules/position_shim_test/position_shim_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Position Shim Test'
+type: module
+description: 'Module for the testing jQuery UI position shim'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/position_shim_test/position_shim_test.libraries.yml b/core/modules/system/tests/modules/position_shim_test/position_shim_test.libraries.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c4741ecdb62a75d54bf01c21815f99ce72591738
--- /dev/null
+++ b/core/modules/system/tests/modules/position_shim_test/position_shim_test.libraries.yml
@@ -0,0 +1,5 @@
+position.shim.test:
+  version: VERSION
+  css:
+    component:
+      css/position.shim.test.css: {}
diff --git a/core/modules/system/tests/modules/position_shim_test/position_shim_test.routing.yml b/core/modules/system/tests/modules/position_shim_test/position_shim_test.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..47c9d04fed80f58a44a368da88042d30f10fcfce
--- /dev/null
+++ b/core/modules/system/tests/modules/position_shim_test/position_shim_test.routing.yml
@@ -0,0 +1,15 @@
+position_test_page:
+  path: '/position-shim-test'
+  defaults:
+    _controller: '\Drupal\position_shim_test\Controller\PositionShimTestController::build'
+    _title: 'position testing'
+  requirements:
+    _access: 'TRUE'
+
+position_test_ported_jqueryui:
+  path: '/position-shim-test-ported-from-jqueryui'
+  defaults:
+    _controller: '\Drupal\position_shim_test\Controller\PositionShimTestPortedJqueryTestsController::build'
+    _title: 'ported position testing'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/position_shim_test/src/Controller/PositionShimTestController.php b/core/modules/system/tests/modules/position_shim_test/src/Controller/PositionShimTestController.php
new file mode 100644
index 0000000000000000000000000000000000000000..da0c438753b3747f26cbdb16fc5268166cb9df29
--- /dev/null
+++ b/core/modules/system/tests/modules/position_shim_test/src/Controller/PositionShimTestController.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\position_shim_test\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+class PositionShimTestController extends ControllerBase {
+
+  /**
+   * Provides a page with the jQuery UI position library for testing.
+   *
+   * @return array
+   *   The render array.
+   */
+  public function build() {
+    return [
+      'reference1' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'position-reference-1',
+        ],
+      ],
+      '#attached' => [
+        'library' => [
+          'core/jquery.ui.position',
+          'position_shim_test/position.shim.test',
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/core/modules/system/tests/modules/position_shim_test/src/Controller/PositionShimTestPortedJqueryTestsController.php b/core/modules/system/tests/modules/position_shim_test/src/Controller/PositionShimTestPortedJqueryTestsController.php
new file mode 100644
index 0000000000000000000000000000000000000000..dde7b735202380c17859b152fee24408c8429bfd
--- /dev/null
+++ b/core/modules/system/tests/modules/position_shim_test/src/Controller/PositionShimTestPortedJqueryTestsController.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Drupal\position_shim_test\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+class PositionShimTestPortedJqueryTestsController extends ControllerBase {
+
+  /**
+   * Provides a page with the jQuery UI position library for testing.
+   *
+   * @return array
+   *   The render array.
+   */
+  public function build() {
+    return [
+      'el1' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'el1',
+          'style' => 'position: absolute; width: 6px; height: 6px;',
+        ],
+      ],
+      'el2' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'el2',
+          'style' => 'position: absolute; width: 6px; height: 6px;',
+        ],
+      ],
+      'parent' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'parent',
+          'style' => 'position: absolute; width: 6px; height: 6px; top: 4px; left: 4px;',
+        ],
+      ],
+      'within' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'within',
+          'style' => 'position: absolute; width: 12px; height: 12px; top: 2px; left: 0px;',
+        ],
+      ],
+      'scrollX' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'scrollX',
+          'style' => 'position: absolute; top: 0px; left: 0px;',
+        ],
+        'elx' => [
+          '#type' => 'container',
+          '#attributes' => [
+            'id' => 'elx',
+            'style' => 'position: absolute; width: 10px; height: 10px;',
+          ],
+        ],
+        'parentX' => [
+          '#type' => 'container',
+          '#attributes' => [
+            'id' => 'parentX',
+            'style' => 'position: absolute; width: 20px; height: 20px; top: 40px; left: 40px;',
+          ],
+        ],
+      ],
+      'largeBox' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'style' => 'position: absolute; height: 5000px; width: 5000px;',
+        ],
+      ],
+      'fractionsParent' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'fractions-parent',
+          'style' => 'position: absolute; left: 10.7432222px; top: 10.532325px; height: 30px; width: 201px;',
+        ],
+        'fractionsElement' => [
+          '#type' => 'container',
+          '#attributes' => [
+            'id' => 'fractions-element',
+          ],
+        ],
+      ],
+      'bug5280' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'bug-5280',
+          'style' => 'height: 30px; width: 201px;',
+        ],
+        'child' => [
+          '#type' => 'container',
+          '#attributes' => [
+            'style' => 'width: 50px; height: 10px;',
+          ],
+        ],
+      ],
+      'bug8710withinSmaller' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'bug-8710-within-smaller',
+          'style' => 'position: absolute; width: 100px; height: 99px; top: 0px; left: 0px;',
+        ],
+      ],
+      'bug8710withinBigger' => [
+        '#type' => 'container',
+        '#attributes' => [
+          'id' => 'bug-8710-within-bigger',
+          'style' => 'position: absolute; width: 100px; height: 101px; top: 0px; left: 0px;',
+        ],
+      ],
+      '#attached' => [
+        'library' => [
+          'core/jquery.ui.position',
+          'position_shim_test/position.shim.test',
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/core/tests/Drupal/FunctionalTests/Libraries/JqueryUiLibraryAssetsTest.php b/core/tests/Drupal/FunctionalTests/Libraries/JqueryUiLibraryAssetsTest.php
index 597dec65c9edb608e13ba5e32922b476eb2c790a..fe2cbcf1a720d3caf4e81b43b2668327bce49f19 100644
--- a/core/tests/Drupal/FunctionalTests/Libraries/JqueryUiLibraryAssetsTest.php
+++ b/core/tests/Drupal/FunctionalTests/Libraries/JqueryUiLibraryAssetsTest.php
@@ -64,7 +64,6 @@ protected function setUp(): void {
       'jquery.ui.dialog',
       'jquery.ui.menu',
       'jquery.ui.mouse',
-      'jquery.ui.position',
       'jquery.ui.resizable',
       'jquery.ui.widget',
     ];
@@ -125,7 +124,6 @@ public function testProperlySetWeights() {
         'core/assets/vendor/jquery.ui/ui/jquery-1-7-min.js',
         'core/assets/vendor/jquery.ui/ui/keycode-min.js',
         'core/assets/vendor/jquery.ui/ui/plugin-min.js',
-        'core/assets/vendor/jquery.ui/ui/position-min.js',
         'core/assets/vendor/jquery.ui/ui/safe-active-element-min.js',
         'core/assets/vendor/jquery.ui/ui/safe-blur-min.js',
         'core/assets/vendor/jquery.ui/ui/scroll-parent-min.js',
@@ -381,7 +379,6 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/safe-active-element-min.js',
           'core/assets/vendor/jquery.ui/ui/safe-blur-min.js',
           'core/assets/vendor/jquery.ui/ui/widget-min.js',
-          'core/assets/vendor/jquery.ui/ui/position-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/menu-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/autocomplete-min.js',
         ],
@@ -416,7 +413,6 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/ie-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/mouse-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/draggable-min.js',
-          'core/assets/vendor/jquery.ui/ui/position-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/resizable-min.js',
           'core/assets/vendor/jquery.ui/ui/form-reset-mixin-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/checkboxradio-min.js',
@@ -474,7 +470,6 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/safe-active-element-min.js',
           'core/assets/vendor/jquery.ui/ui/safe-blur-min.js',
           'core/assets/vendor/jquery.ui/ui/widget-min.js',
-          'core/assets/vendor/jquery.ui/ui/position-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/menu-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/autocomplete-min.js',
         ],
@@ -540,7 +535,6 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/ie-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/mouse-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/draggable-min.js',
-          'core/assets/vendor/jquery.ui/ui/position-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/resizable-min.js',
           'core/assets/vendor/jquery.ui/ui/form-reset-mixin-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/checkboxradio-min.js',
@@ -573,7 +567,6 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/safe-active-element-min.js',
           'core/assets/vendor/jquery.ui/ui/safe-blur-min.js',
           'core/assets/vendor/jquery.ui/ui/widget-min.js',
-          'core/assets/vendor/jquery.ui/ui/position-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/menu-min.js',
         ],
       ],
@@ -603,31 +596,6 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/widgets/mouse-min.js',
         ],
       ],
-      'jquery.ui.position' => [
-        'library' => 'jquery.ui.position',
-        'expected_css' => [
-          'core/assets/vendor/jquery.ui/themes/base/core.css',
-          'core/assets/vendor/jquery.ui/themes/base/theme.css',
-        ],
-        'expected_js' => [
-          'core/assets/vendor/jquery.ui/ui/data-min.js',
-          'core/assets/vendor/jquery.ui/ui/disable-selection-min.js',
-          'core/assets/vendor/jquery.ui/ui/form-min.js',
-          'core/assets/vendor/jquery.ui/ui/labels-min.js',
-          'core/assets/vendor/jquery.ui/ui/jquery-1-7-min.js',
-          'core/assets/vendor/jquery.ui/ui/scroll-parent-min.js',
-          'core/assets/vendor/jquery.ui/ui/unique-id-min.js',
-          'core/assets/vendor/jquery.ui/ui/version-min.js',
-          'core/assets/vendor/jquery.ui/ui/escape-selector-min.js',
-          'core/assets/vendor/jquery.ui/ui/focusable-min.js',
-          'core/assets/vendor/jquery.ui/ui/ie-min.js',
-          'core/assets/vendor/jquery.ui/ui/keycode-min.js',
-          'core/assets/vendor/jquery.ui/ui/plugin-min.js',
-          'core/assets/vendor/jquery.ui/ui/safe-active-element-min.js',
-          'core/assets/vendor/jquery.ui/ui/safe-blur-min.js',
-          'core/assets/vendor/jquery.ui/ui/position-min.js',
-        ],
-      ],
       'jquery.ui.resizable' => [
         'library' => 'jquery.ui.resizable',
         'expected_css' => [
@@ -738,7 +706,6 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/safe-active-element-min.js',
           'core/assets/vendor/jquery.ui/ui/safe-blur-min.js',
           'core/assets/vendor/jquery.ui/ui/widget-min.js',
-          'core/assets/vendor/jquery.ui/ui/position-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/menu-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/autocomplete-min.js',
           'core/assets/vendor/jquery.ui/ui/ie-min.js',
@@ -752,8 +719,8 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/widgets/dialog-min.js',
         ],
       ],
-      'jquery.ui.widget|jquery.ui.resizable|jquery.ui.position|jquery.ui.mouse|jquery.ui.menu|jquery.ui.dialog|jquery.ui.button|jquery.ui.autocomplete|jquery.ui|drupal.dialog|drupal.autocomplete' => [
-        'library' => 'jquery.ui.widget|jquery.ui.resizable|jquery.ui.position|jquery.ui.mouse|jquery.ui.menu|jquery.ui.dialog|jquery.ui.button|jquery.ui.autocomplete|jquery.ui|drupal.dialog|drupal.autocomplete',
+      'jquery.ui.widget|jquery.ui.resizable|jquery.ui.mouse|jquery.ui.menu|jquery.ui.dialog|jquery.ui.button|jquery.ui.autocomplete|jquery.ui|drupal.dialog|drupal.autocomplete' => [
+        'library' => 'jquery.ui.widget|jquery.ui.resizable|jquery.ui.mouse|jquery.ui.menu|jquery.ui.dialog|jquery.ui.button|jquery.ui.autocomplete|jquery.ui|drupal.dialog|drupal.autocomplete',
         'expected_css' => [
           'core/assets/vendor/jquery.ui/themes/base/core.css',
           'core/assets/vendor/jquery.ui/themes/base/resizable.css',
@@ -784,7 +751,6 @@ public function providerTestAssetLoading() {
           'core/assets/vendor/jquery.ui/ui/ie-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/mouse-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/resizable-min.js',
-          'core/assets/vendor/jquery.ui/ui/position-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/menu-min.js',
           'core/assets/vendor/jquery.ui/ui/widgets/draggable-min.js',
           'core/assets/vendor/jquery.ui/ui/form-reset-mixin-min.js',
diff --git a/core/tests/Drupal/KernelTests/Core/Asset/DeprecatedJqueryUiAssetsTest.php b/core/tests/Drupal/KernelTests/Core/Asset/DeprecatedJqueryUiAssetsTest.php
index 87fbd9432308f7f9ce69035766868f7a999297f3..9ee318f6e949a19da8a02fd83851703673aed0b9 100644
--- a/core/tests/Drupal/KernelTests/Core/Asset/DeprecatedJqueryUiAssetsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Asset/DeprecatedJqueryUiAssetsTest.php
@@ -23,11 +23,11 @@ public function testDeprecatedJqueryUi() {
       'jquery.ui' => '291c28f873a71cd6b3116218d1f5da22',
       'jquery.ui.autocomplete' => '153f2836f8f2da39767208b6e09cb5b4',
       'jquery.ui.button' => 'ad23e5de0fa1de1f511d10ba2e10d2dd',
-      'jquery.ui.dialog' => '729090e5ddcd8563ddade80c3dabc87c',
+      'jquery.ui.dialog' => '6521b8917536afe00f35055da4ec466c',
       'jquery.ui.draggable' => 'af0f2bdc8aa4ade1e3de8042f31a9312',
       'jquery.ui.menu' => '7d0c4d57f43d2f881d2cd5e5b79effbb',
       'jquery.ui.mouse' => '626bb203807fa2cdc62510412685df4a',
-      'jquery.ui.position' => '6d1759c7d3eb94accbed78416487469b',
+      'jquery.ui.position' => 'fec1ca376f2b1cb9b0ca3db36be848c2',
       'jquery.ui.resizable' => 'a2448fa87071a17a9756f39c9becb70d',
       'jquery.ui.widget' => 'eacd675de09572383b58e52309ba2245',
     ];
@@ -41,7 +41,7 @@ public function testDeprecatedJqueryUi() {
 
       // Confirm that the libraries extending jQuery UI functionality depend on
       // core/jquery.ui directly or via a dependency on core/jquery.ui.widget.
-      if ($library !== 'jquery.ui' && $library !== 'jquery.ui.dialog') {
+      if (!in_array($library, ['jquery.ui', 'jquery.ui.dialog', 'jquery.ui.position'])) {
         $has_main_or_widget = (in_array('core/jquery.ui', $library_definition['dependencies']) || in_array('core/jquery.ui.widget', $library_definition['dependencies']));
         $this->assertTrue($has_main_or_widget, "$library must depend on core/jquery.ui or core/jquery.ui.widget");
       }
diff --git a/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js b/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..5cb08eee361603a611042b09338e3c498533ef99
--- /dev/null
+++ b/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js
@@ -0,0 +1,2403 @@
+/**
+ * The testScenarios object is for testing a wide range of jQuery UI position
+ * configuration options. The object properties are:
+ * {
+ *   - How the `of:` option will be used. This option determines the element the
+ *     positioned element will attach to. This can be a selector, window, a
+ *     jQuery object, or a vanilla JS element.
+ *     - `my`: Sets the 'my' option for position().
+ *     - `at`: Sets the 'at' option for position().
+ *     - `x`: The expected X position of the element being positioned.
+ *     - `y`: The expected Y position of the element being positioned.
+ * }
+ * This covers every possible combination of `my:` and `at:` using fixed amounts
+ * (left, right, center, top, bottom), with additional scenarios that include
+ * offsets.
+ */
+/* cSpell:disable */
+const testScenarios = {
+  window: {
+    centerbottomcenterbottom: {
+      at: 'center bottom',
+      my: 'center bottom',
+      x: 38.5,
+      y: 77,
+    },
+    centerbottomcentercenter: {
+      at: 'center center',
+      my: 'center bottom',
+      x: 38.5,
+      y: 77,
+    },
+    centerbottomcentertop: {
+      at: 'center top',
+      my: 'center bottom',
+      x: 38.5,
+      y: -76.984375,
+    },
+    centerbottomleftbottom: {
+      at: 'left bottom',
+      my: 'center bottom',
+      x: -38.5,
+      y: 77,
+    },
+    centerbottomleftcenter: {
+      at: 'left center',
+      my: 'center bottom',
+      x: -38.5,
+      y: 77,
+    },
+    centerbottomlefttop: {
+      at: 'left top',
+      my: 'center bottom',
+      x: -38.5,
+      y: -76.984375,
+    },
+    centerbottomrightbottom: {
+      at: 'right bottom',
+      my: 'center bottom',
+      x: 38.5,
+      y: 77,
+    },
+    centerbottomrightcenter: {
+      at: 'right center',
+      my: 'center bottom',
+      x: 38.5,
+      y: 77,
+    },
+    centerbottomrightminus80bottomminus40: {
+      at: 'right-80 bottom-40',
+      my: 'center bottom',
+      x: 118.5,
+      y: 117,
+    },
+    centerbottomrighttop: {
+      at: 'right top',
+      my: 'center bottom',
+      x: 38.5,
+      y: -76.984375,
+    },
+    centerminus40topplus40leftplus20ptop: {
+      at: 'left+20 top',
+      my: 'center-40 top+40',
+      x: -58.5,
+      y: 40,
+    },
+    centerplus10perpbottomcenterminus10pertop: {
+      at: 'center+110 top',
+      my: 'center+150 bottom',
+      x: -221.5,
+      y: -76.984375,
+    },
+    centerplus20ptopplus20pcenterbottom: {
+      at: 'center bottom',
+      my: 'center+100 top-200',
+      x: -61.5,
+      y: 200,
+    },
+    centerplus40topminus15pcentercenterplus40: {
+      at: 'center center+40',
+      my: 'center+40 top+15',
+      x: -1.5,
+      y: -55,
+    },
+    centerplus80bottomminus90leftbottom: {
+      at: 'left bottom',
+      my: 'center+80 bottom-90',
+      x: 41.5,
+      y: 167,
+    },
+    centertopcenterbottom: {
+      at: 'center bottom',
+      my: 'center top',
+      x: 38.5,
+      y: 0,
+    },
+    centertopcentercenter: {
+      at: 'center center',
+      my: 'center top',
+      x: 38.5,
+      y: 0,
+    },
+    centertopcenterplus20ptopplus20p: {
+      at: 'center+70 top+60',
+      my: 'center top',
+      x: -31.5,
+      y: 60,
+    },
+    centertopcentertop: { at: 'center top', my: 'center top', x: 38.5, y: 0 },
+    centertopleftbottom: {
+      at: 'left bottom',
+      my: 'center top',
+      x: -38.5,
+      y: 0,
+    },
+    centertopleftcenter: {
+      at: 'left center',
+      my: 'center top',
+      x: -38.5,
+      y: 0,
+    },
+    centertoplefttop: { at: 'left top', my: 'center top', x: -38.5, y: 0 },
+    centertoprightbottom: {
+      at: 'right bottom',
+      my: 'center top',
+      x: 38.5,
+      y: 0,
+    },
+    centertoprightcenter: {
+      at: 'right center',
+      my: 'center top',
+      x: 38.5,
+      y: 0,
+    },
+    centertoprighttop: { at: 'right top', my: 'center top', x: 38.5, y: 0 },
+    leftbottomcenterbottom: {
+      at: 'center bottom',
+      my: 'left bottom',
+      x: 0,
+      y: 77,
+    },
+    leftbottomcentercenter: {
+      at: 'center center',
+      my: 'left bottom',
+      x: 0,
+      y: 77,
+    },
+    leftbottomcentertop: {
+      at: 'center top',
+      my: 'left bottom',
+      x: 0,
+      y: -76.984375,
+    },
+    leftbottomleftbottom: { at: 'left bottom', my: 'left bottom', x: 0, y: 77 },
+    leftbottomleftcenter: { at: 'left center', my: 'left bottom', x: 0, y: 77 },
+    leftbottomlefttop: {
+      at: 'left top',
+      my: 'left bottom',
+      x: 0,
+      y: -76.984375,
+    },
+    leftbottomrightbottom: {
+      at: 'right bottom',
+      my: 'left bottom',
+      x: 0,
+      y: 77,
+    },
+    leftbottomrightcenter: {
+      at: 'right center',
+      my: 'left bottom',
+      x: 0,
+      y: 77,
+    },
+    leftbottomrighttop: {
+      at: 'right top',
+      my: 'left bottom',
+      x: 0,
+      y: -76.984375,
+    },
+    leftcentercenterbottom: {
+      at: 'center bottom',
+      my: 'left center',
+      x: 0,
+      y: 38.5,
+    },
+    leftcentercentercenter: {
+      at: 'center center',
+      my: 'left center',
+      x: 0,
+      y: 38.5,
+    },
+    leftcentercentertop: {
+      at: 'center top',
+      my: 'left center',
+      x: 0,
+      y: -38.484375,
+    },
+    leftcenterleftbottom: {
+      at: 'left bottom',
+      my: 'left center',
+      x: 0,
+      y: 38.5,
+    },
+    leftcenterleftcenter: {
+      at: 'left center',
+      my: 'left center',
+      x: 0,
+      y: 38.5,
+    },
+    leftcenterlefttop: {
+      at: 'left top',
+      my: 'left center',
+      x: 0,
+      y: -38.484375,
+    },
+    leftcenterrightbottom: {
+      at: 'right bottom',
+      my: 'left center',
+      x: 0,
+      y: 38.5,
+    },
+    leftcenterrightcenter: {
+      at: 'right center',
+      my: 'left center',
+      x: 0,
+      y: 38.5,
+    },
+    leftcenterrighttop: {
+      at: 'right top',
+      my: 'left center',
+      x: 0,
+      y: -38.484375,
+    },
+    lefttopcenterbottom: { at: 'center bottom', my: 'left top', x: 0, y: 0 },
+    lefttopcentercenter: { at: 'center center', my: 'left top', x: 0, y: 0 },
+    lefttopcentertop: { at: 'center top', my: 'left top', x: 0, y: 0 },
+    lefttopleftbottom: { at: 'left bottom', my: 'left top', x: 0, y: 0 },
+    lefttopleftcenter: { at: 'left center', my: 'left top', x: 0, y: 0 },
+    lefttoplefttop: { at: 'left top', my: 'left top', x: 0, y: 0 },
+    lefttoprightbottom: { at: 'right bottom', my: 'left top', x: 0, y: 0 },
+    lefttoprightcenter: { at: 'right center', my: 'left top', x: 0, y: 0 },
+    lefttoprighttop: { at: 'right top', my: 'left top', x: 0, y: 0 },
+    rightbottomcenterbottom: {
+      at: 'center bottom',
+      my: 'right bottom',
+      x: 77,
+      y: 77,
+    },
+    rightbottomcentercenter: {
+      at: 'center center',
+      my: 'right bottom',
+      x: 77,
+      y: 77,
+    },
+    rightbottomcentertop: {
+      at: 'center top',
+      my: 'right bottom',
+      x: 77,
+      y: -76.984375,
+    },
+    rightbottomleftbottom: {
+      at: 'left bottom',
+      my: 'right bottom',
+      x: -77,
+      y: 77,
+    },
+    rightbottomleftcenter: {
+      at: 'left center',
+      my: 'right bottom',
+      x: -77,
+      y: 77,
+    },
+    rightbottomlefttop: {
+      at: 'left top',
+      my: 'right bottom',
+      x: -77,
+      y: -76.984375,
+    },
+    rightbottomrightbottom: {
+      at: 'right bottom',
+      my: 'right bottom',
+      x: 77,
+      y: 77,
+    },
+    rightbottomrightcenter: {
+      at: 'right center',
+      my: 'right bottom',
+      x: 77,
+      y: 77,
+    },
+    rightbottomrighttop: {
+      at: 'right top',
+      my: 'right bottom',
+      x: 77,
+      y: -76.984375,
+    },
+    rightcentercenterbottom: {
+      at: 'center bottom',
+      my: 'right center',
+      x: 77,
+      y: 38.5,
+    },
+    rightcentercentercenter: {
+      at: 'center center',
+      my: 'right center',
+      x: 77,
+      y: 38.5,
+    },
+    rightcentercentertop: {
+      at: 'center top',
+      my: 'right center',
+      x: 77,
+      y: -38.484375,
+    },
+    rightcenterleftbottom: {
+      at: 'left bottom',
+      my: 'right center',
+      x: -77,
+      y: 38.5,
+    },
+    rightcenterleftcenter: {
+      at: 'left center',
+      my: 'right center',
+      x: -77,
+      y: 38.5,
+    },
+    rightcenterlefttop: {
+      at: 'left top',
+      my: 'right center',
+      x: -77,
+      y: -38.484375,
+    },
+    rightcenterrightbottom: {
+      at: 'right bottom',
+      my: 'right center',
+      x: 77,
+      y: 38.5,
+    },
+    rightcenterrightcenter: {
+      at: 'right center',
+      my: 'right center',
+      x: 77,
+      y: 38.5,
+    },
+    rightcenterrighttop: {
+      at: 'right top',
+      my: 'right center',
+      x: 77,
+      y: -38.484375,
+    },
+    righttopcenterbottom: { at: 'center bottom', my: 'right top', x: 77, y: 0 },
+    righttopcentercenter: { at: 'center center', my: 'right top', x: 77, y: 0 },
+    righttopcentertop: { at: 'center top', my: 'right top', x: 77, y: 0 },
+    righttopleftbottom: { at: 'left bottom', my: 'right top', x: -77, y: 0 },
+    righttopleftcenter: { at: 'left center', my: 'right top', x: -77, y: 0 },
+    righttoplefttop: { at: 'left top', my: 'right top', x: -77, y: 0 },
+    righttoprightbottom: { at: 'right bottom', my: 'right top', x: 77, y: 0 },
+    righttoprightcenter: { at: 'right center', my: 'right top', x: 77, y: 0 },
+    righttoprighttop: { at: 'right top', my: 'right top', x: 77, y: 0 },
+  },
+  selector: {
+    centerbottomcenterbottom: {
+      at: 'center bottom',
+      my: 'center bottom',
+      x: 62.5,
+      y: 125,
+    },
+    centerbottomcentercenter: {
+      at: 'center center',
+      my: 'center bottom',
+      x: 62.5,
+      y: 24,
+    },
+    centerbottomcentertop: {
+      at: 'center top',
+      my: 'center bottom',
+      x: 62.5,
+      y: -77,
+    },
+    centerbottomleftbottom: {
+      at: 'left bottom',
+      my: 'center bottom',
+      x: -38.5,
+      y: 125,
+    },
+    centerbottomleftcenter: {
+      at: 'left center',
+      my: 'center bottom',
+      x: -38.5,
+      y: 24,
+    },
+    centerbottomlefttop: {
+      at: 'left top',
+      my: 'center bottom',
+      x: -38.5,
+      y: -77,
+    },
+    centerbottomrightbottom: {
+      at: 'right bottom',
+      my: 'center bottom',
+      x: 163.5,
+      y: 125,
+    },
+    centerbottomrightcenter: {
+      at: 'right center',
+      my: 'center bottom',
+      x: 163.5,
+      y: 24,
+    },
+    centerbottomrightplus40bottomminus40: {
+      at: 'right+40 bottom-40',
+      my: 'center bottom',
+      x: 203.5,
+      y: 85,
+    },
+    centerbottomrighttop: {
+      at: 'right top',
+      my: 'center bottom',
+      x: 163.5,
+      y: -77,
+    },
+    centerminus40topplus40leftminus20ptop: {
+      at: 'left-20% top',
+      my: 'center-40 top+40',
+      x: -118.890625,
+      y: 40,
+    },
+    centerplus10perpbottomcenterminus10pertop: {
+      at: 'center-20% top',
+      my: 'center+20% bottom',
+      x: 37.5,
+      y: -77,
+    },
+    centerplus40bottomminus40leftbottom: {
+      at: 'left bottom',
+      my: 'center+40 bottom-40',
+      x: 1.5,
+      y: 85,
+    },
+    centerplus40topminus15pcentercenterplus40: {
+      at: 'center center+40',
+      my: 'center+40 top-15%',
+      x: 102.5,
+      y: 129.4375,
+    },
+    centertopcenterbottom: {
+      at: 'center bottom',
+      my: 'center top',
+      x: 62.5,
+      y: 202,
+    },
+    centertopcentercenter: {
+      at: 'center center',
+      my: 'center top',
+      x: 62.5,
+      y: 101,
+    },
+    centertopcenterplus20ptopplus20p: {
+      at: 'center+20% top+20%',
+      my: 'center top',
+      x: 102.890625,
+      y: 40.390625,
+    },
+    centertopcentertop: { at: 'center top', my: 'center top', x: 62.5, y: 0 },
+    centertopleftbottom: {
+      at: 'left bottom',
+      my: 'center top',
+      x: -38.5,
+      y: 202,
+    },
+    centertopleftcenter: {
+      at: 'left center',
+      my: 'center top',
+      x: -38.5,
+      y: 101,
+    },
+    centertoplefttop: { at: 'left top', my: 'center top', x: -38.5, y: 0 },
+    centertoprightbottom: {
+      at: 'right bottom',
+      my: 'center top',
+      x: 163.5,
+      y: 202,
+    },
+    centertoprightcenter: {
+      at: 'right center',
+      my: 'center top',
+      x: 163.5,
+      y: 101,
+    },
+    centertoprighttop: { at: 'right top', my: 'center top', x: 163.5, y: 0 },
+    leftbottomcenterbottom: {
+      at: 'center bottom',
+      my: 'left bottom',
+      x: 101,
+      y: 125,
+    },
+    leftbottomcentercenter: {
+      at: 'center center',
+      my: 'left bottom',
+      x: 101,
+      y: 24,
+    },
+    leftbottomcentertop: {
+      at: 'center top',
+      my: 'left bottom',
+      x: 101,
+      y: -77,
+    },
+    leftbottomleftbottom: {
+      at: 'left bottom',
+      my: 'left bottom',
+      x: 0,
+      y: 125,
+    },
+    leftbottomleftcenter: { at: 'left center', my: 'left bottom', x: 0, y: 24 },
+    leftbottomlefttop: { at: 'left top', my: 'left bottom', x: 0, y: -77 },
+    leftbottomrightbottom: {
+      at: 'right bottom',
+      my: 'left bottom',
+      x: 202,
+      y: 125,
+    },
+    leftbottomrightcenter: {
+      at: 'right center',
+      my: 'left bottom',
+      x: 202,
+      y: 24,
+    },
+    leftbottomrighttop: { at: 'right top', my: 'left bottom', x: 202, y: -77 },
+    leftcentercenterbottom: {
+      at: 'center bottom',
+      my: 'left center',
+      x: 101,
+      y: 163.5,
+    },
+    leftcentercentercenter: {
+      at: 'center center',
+      my: 'left center',
+      x: 101,
+      y: 62.5,
+    },
+    leftcentercentertop: {
+      at: 'center top',
+      my: 'left center',
+      x: 101,
+      y: -38.5,
+    },
+    leftcenterleftbottom: {
+      at: 'left bottom',
+      my: 'left center',
+      x: 0,
+      y: 163.5,
+    },
+    leftcenterleftcenter: {
+      at: 'left center',
+      my: 'left center',
+      x: 0,
+      y: 62.5,
+    },
+    leftcenterlefttop: { at: 'left top', my: 'left center', x: 0, y: -38.5 },
+    leftcenterrightbottom: {
+      at: 'right bottom',
+      my: 'left center',
+      x: 202,
+      y: 163.5,
+    },
+    leftcenterrightcenter: {
+      at: 'right center',
+      my: 'left center',
+      x: 202,
+      y: 62.5,
+    },
+    leftcenterrighttop: {
+      at: 'right top',
+      my: 'left center',
+      x: 202,
+      y: -38.5,
+    },
+    lefttopcenterbottom: {
+      at: 'center bottom',
+      my: 'left top',
+      x: 101,
+      y: 202,
+    },
+    lefttopcentercenter: {
+      at: 'center center',
+      my: 'left top',
+      x: 101,
+      y: 101,
+    },
+    lefttopcentertop: { at: 'center top', my: 'left top', x: 101, y: 0 },
+    lefttopleftbottom: { at: 'left bottom', my: 'left top', x: 0, y: 202 },
+    lefttopleftcenter: { at: 'left center', my: 'left top', x: 0, y: 101 },
+    lefttoplefttop: { at: 'left top', my: 'left top', x: 0, y: 0 },
+    lefttoprightbottom: { at: 'right bottom', my: 'left top', x: 202, y: 202 },
+    lefttoprightcenter: { at: 'right center', my: 'left top', x: 202, y: 101 },
+    lefttoprighttop: { at: 'right top', my: 'left top', x: 202, y: 0 },
+    rightbottomcenterbottom: {
+      at: 'center bottom',
+      my: 'right bottom',
+      x: 24,
+      y: 125,
+    },
+    rightbottomcentercenter: {
+      at: 'center center',
+      my: 'right bottom',
+      x: 24,
+      y: 24,
+    },
+    rightbottomcentertop: {
+      at: 'center top',
+      my: 'right bottom',
+      x: 24,
+      y: -77,
+    },
+    rightbottomleftbottom: {
+      at: 'left bottom',
+      my: 'right bottom',
+      x: -77,
+      y: 125,
+    },
+    rightbottomleftcenter: {
+      at: 'left center',
+      my: 'right bottom',
+      x: -77,
+      y: 24,
+    },
+    rightbottomlefttop: { at: 'left top', my: 'right bottom', x: -77, y: -77 },
+    rightbottomrightbottom: {
+      at: 'right bottom',
+      my: 'right bottom',
+      x: 125,
+      y: 125,
+    },
+    rightbottomrightcenter: {
+      at: 'right center',
+      my: 'right bottom',
+      x: 125,
+      y: 24,
+    },
+    rightbottomrighttop: {
+      at: 'right top',
+      my: 'right bottom',
+      x: 125,
+      y: -77,
+    },
+    rightcentercenterbottom: {
+      at: 'center bottom',
+      my: 'right center',
+      x: 24,
+      y: 163.5,
+    },
+    rightcentercentercenter: {
+      at: 'center center',
+      my: 'right center',
+      x: 24,
+      y: 62.5,
+    },
+    rightcentercentertop: {
+      at: 'center top',
+      my: 'right center',
+      x: 24,
+      y: -38.5,
+    },
+    rightcenterleftbottom: {
+      at: 'left bottom',
+      my: 'right center',
+      x: -77,
+      y: 163.5,
+    },
+    rightcenterleftcenter: {
+      at: 'left center',
+      my: 'right center',
+      x: -77,
+      y: 62.5,
+    },
+    rightcenterlefttop: {
+      at: 'left top',
+      my: 'right center',
+      x: -77,
+      y: -38.5,
+    },
+    rightcenterrightbottom: {
+      at: 'right bottom',
+      my: 'right center',
+      x: 125,
+      y: 163.5,
+    },
+    rightcenterrightcenter: {
+      at: 'right center',
+      my: 'right center',
+      x: 125,
+      y: 62.5,
+    },
+    rightcenterrighttop: {
+      at: 'right top',
+      my: 'right center',
+      x: 125,
+      y: -38.5,
+    },
+    righttopcenterbottom: {
+      at: 'center bottom',
+      my: 'right top',
+      x: 24,
+      y: 202,
+    },
+    righttopcentercenter: {
+      at: 'center center',
+      my: 'right top',
+      x: 24,
+      y: 101,
+    },
+    righttopcentertop: { at: 'center top', my: 'right top', x: 24, y: 0 },
+    righttopleftbottom: { at: 'left bottom', my: 'right top', x: -77, y: 202 },
+    righttopleftcenter: { at: 'left center', my: 'right top', x: -77, y: 101 },
+    righttoplefttop: { at: 'left top', my: 'right top', x: -77, y: 0 },
+    righttoprightbottom: {
+      at: 'right bottom',
+      my: 'right top',
+      x: 125,
+      y: 202,
+    },
+    righttoprightcenter: {
+      at: 'right center',
+      my: 'right top',
+      x: 125,
+      y: 101,
+    },
+    righttoprighttop: { at: 'right top', my: 'right top', x: 125, y: 0 },
+  },
+};
+/* cSpell:enable */
+
+// Testing `of:` using jQuery or vanilla JS elements can use the same test
+// scenarios and expected values as those using a selector.
+testScenarios.jQuery = testScenarios.selector;
+testScenarios.element = testScenarios.selector;
+
+module.exports = {
+  '@tags': ['core'],
+  before(browser) {
+    browser.drupalInstall().drupalLoginAsAdmin(() => {
+      browser
+        .drupalRelativeURL('/admin/modules')
+        .setValue('input[type="search"]', 'position Shim Test')
+        .waitForElementVisible(
+          'input[name="modules[position_shim_test][enable]"]',
+          1000,
+        )
+        .click('input[name="modules[position_shim_test][enable]"]')
+        .click('input[type="submit"]');
+    });
+  },
+  after(browser) {
+    browser.drupalUninstall();
+  },
+  beforeEach(browser) {
+    if (browser.currentTest.name !== 'test position') {
+      browser
+        .drupalRelativeURL('/position-shim-test-ported-from-jqueryui')
+        .waitForElementVisible('#el1', 1000);
+    }
+  },
+  'test position': (browser) => {
+    browser
+      .resizeWindow(1200, 600)
+      .drupalRelativeURL('/position-shim-test')
+      .waitForElementPresent('#position-reference-1', 1000)
+      .executeAsync(
+        // eslint-disable-next-line func-names, prefer-arrow-callback
+        function (testIterations, done) {
+          const $ = jQuery;
+          const toReturn = {};
+
+          /**
+           * Confirms a coordinate is acceptably close to the expected value.
+           *
+           * @param {number} actual
+           *  The actual coordinate value.
+           * @param {number} expected
+           *  The expected coordinate value.
+           * @return {boolean}
+           *  True if the actual is within 3px of the expected.
+           */
+          const withinRange = (actual, expected) => {
+            return actual <= expected + 3 && actual >= expected - 3;
+          };
+
+          /**
+           * Parses a jQuery UI position config string for `at:` or `my:`.
+           *
+           * A position config string can contain both alignment and offset
+           * configuration. This string is parsed and returned as an object that
+           * separates horizontal and vertical alignment and their respective
+           * offsets into distinct object properties.
+           *
+           * This is a copy of the parseOffset function from the jQuery position
+           * API.
+           *
+           * @param {string} offset
+           *   Offset configuration in jQuery UI Position format.
+           * @param {Element} element
+           *   The element being positioned.
+           * @return {{horizontal: (*|string), verticalOffset: number, vertical: (*|string), horizontalOffset: number}}
+           *   The horizontal and vertical alignment and offset values for the element.
+           *
+           * @see core/misc/position.es6.js
+           */
+          const parseOffset = (offset, element) => {
+            const regexHorizontal = /left|center|right/;
+            const regexVertical = /top|center|bottom/;
+            const regexOffset = /[+-]\d+(\.[\d]+)?%?/;
+            const regexPosition = /^\w+/;
+            const regexPercent = /%$/;
+            let positions = offset.split(' ');
+            if (positions.length === 1) {
+              if (regexHorizontal.test(positions[0])) {
+                positions.push('center');
+              } else if (regexVertical.test(positions[0])) {
+                positions = ['center'].concat(positions);
+              }
+            }
+
+            const horizontalOffset = regexOffset.exec(positions[0]);
+            const verticalOffset = regexOffset.exec(positions[1]);
+            positions = positions.map((pos) => regexPosition.exec(pos)[0]);
+
+            return {
+              horizontalOffset: horizontalOffset
+                ? parseFloat(horizontalOffset[0]) *
+                  (regexPercent.test(horizontalOffset[0])
+                    ? element.offsetWidth / 100
+                    : 1)
+                : 0,
+              verticalOffset: verticalOffset
+                ? parseFloat(verticalOffset[0]) *
+                  (regexPercent.test(verticalOffset[0])
+                    ? element.offsetWidth / 100
+                    : 1)
+                : 0,
+              horizontal: positions[0],
+              vertical: positions[1],
+            };
+          };
+
+          /**
+           * Checks the position of an element.
+           *
+           * The position values of an element are based on their distance
+           * relative to the element they're positioned against.
+           *
+           * @param {jQuery} tip
+           *  The element being positioned.
+           * @param {Object} options
+           *  The position options.
+           * @param {string} attachToType
+           *  A string representing the data type used for the value of the `of`
+           *  option. This could be 'selector', 'window', 'jQuery', 'element'.
+           *
+           * @param {string} idKey
+           *   The unique id of the element indicating the use case scenario.
+           *
+           * @return {Promise}
+           *   Resolve after the tip position is calculated.
+           */
+          const checkPosition = (tip, options, attachToType, idKey) =>
+            new Promise((resolve) => {
+              setTimeout(() => {
+                const box = tip[0].getBoundingClientRect();
+                let { x, y } = box;
+                // If the tip is attaching to the window, X and Y are measured
+                // based on their distance from the closest window boundary.
+                if (attachToType === 'window') {
+                  // Parse options.at to get the configured the horizontal and
+                  // vertical positioning within the window. This will be used
+                  // to get the tip distance relative to the configured position
+                  // within the window. This provides a reliable way of
+                  // getting position info that doesn't rely on an exact
+                  // viewport width.
+                  const atOffsets = parseOffset(options.at, tip[0]);
+
+                  if (atOffsets.horizontal === 'center') {
+                    x = document.documentElement.clientWidth / 2 - x;
+                  } else if (atOffsets.horizontal === 'right') {
+                    x = document.documentElement.clientWidth - x;
+                  }
+                  if (atOffsets.vertical === 'center') {
+                    y = document.documentElement.clientHeight / 2 - y;
+                  } else if (atOffsets.vertical === 'bottom') {
+                    y = document.documentElement.clientHeight - y;
+                  } else {
+                    y += window.pageYOffset;
+                  }
+                } else {
+                  // Measure the distance of the tip from the reference element.
+                  const refRect = document
+                    .querySelector('#position-reference-1')
+                    .getBoundingClientRect();
+                  x -= refRect.x;
+                  y -= refRect.y;
+                }
+                if (!withinRange(x, options.x) || !withinRange(y, options.y)) {
+                  toReturn[
+                    idKey
+                  ] = `${idKey} EXPECTED x:${options.x} y:${options.y} ACTUAL x:${x} y:${y}`;
+                } else {
+                  toReturn[idKey] = true;
+                }
+
+                resolve();
+              }, 25);
+            });
+
+          const attachScenarios = {
+            selector: '#position-reference-1',
+            window,
+            jQuery: $('#position-reference-1'),
+            element: document.querySelector('#position-reference-1'),
+          };
+
+          // Loop through testScenarios and attachScenarios to get config for a
+          // positioned tip.
+          (async function iterate() {
+            const attachToTypes = Object.keys(attachScenarios);
+            for (let i = 0; i < attachToTypes.length; i++) {
+              const attachToType = attachToTypes[i];
+              const scenarios = Object.keys(testIterations[attachToType]);
+              for (let j = 0; j < scenarios.length; j++) {
+                const key = scenarios[j];
+                const options = testIterations[attachToType][key];
+                options.of = attachScenarios[attachToType];
+                options.collision = 'none';
+                const idKey = `${attachToType}${key}`;
+
+                // eslint-disable-next-line no-await-in-loop
+                const tip = await new Promise((resolve) => {
+                  const addedTip = $(
+                    `<div class="test-tip"  style="position:${
+                      attachToType === 'window' ? 'fixed' : 'absolute'
+                    }" id="${idKey}">${idKey}</div>`,
+                  ).appendTo('main');
+                  addedTip.position(options);
+                  setTimeout(() => {
+                    resolve(addedTip);
+                  });
+                });
+                // eslint-disable-next-line no-await-in-loop
+                await checkPosition(tip, options, attachToType, idKey);
+                tip.remove();
+              }
+            }
+            done(toReturn);
+          })();
+        },
+        [testScenarios],
+        (result) => {
+          let numberOfScenarios = 0;
+          Object.keys(testScenarios).forEach((scenario) => {
+            numberOfScenarios += Object.keys(testScenarios[scenario]).length;
+          });
+          const valueKeys = Object.keys(result.value);
+          browser.assert.equal(valueKeys.length, numberOfScenarios);
+          valueKeys.forEach((item) => {
+            browser.assert.equal(
+              result.value[item],
+              true,
+              `expected position: ${item}`,
+            );
+          });
+        },
+      );
+  },
+  // The remaining tests are ported from jQuery UI's QUnit tests.
+  'my, at, of': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'left top',
+          at: 'left top',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn['left top, left top'] = {
+          actual: $elx.offset(),
+          expected: { top: 40, left: 40 },
+        };
+        $elx.position({
+          my: 'left top',
+          at: 'left bottom',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn['left top, left bottom'] = {
+          actual: $elx.offset(),
+          expected: { top: 60, left: 40 },
+        };
+        $elx.position({
+          my: 'left',
+          at: 'bottom',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn['left, bottom'] = {
+          actual: $elx.offset(),
+          expected: { top: 55, left: 50 },
+        };
+        $elx.position({
+          my: 'left foo',
+          at: 'bar baz',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn['left foo, bar baz'] = {
+          actual: $elx.offset(),
+          expected: { top: 45, left: 50 },
+        };
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 4);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'multiple elements': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const elements = $('#el1, #el2');
+        const result = elements.position({
+          my: 'left top',
+          at: 'left bottom',
+          of: '#parent',
+          collision: 'none',
+        });
+        toReturn['elements return'] = {
+          actual: result,
+          expected: elements,
+        };
+        // eslint-disable-next-line func-names
+        elements.each(function (index) {
+          toReturn[`element${index}`] = {
+            actual: $(this).offset(),
+            expected: { top: 10, left: 4 },
+          };
+        });
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 3);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  positions: (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+
+        const offsets = {
+          left: 0,
+          center: 3,
+          right: 6,
+          top: 0,
+          bottom: 6,
+        };
+        const start = { left: 4, top: 4 };
+        const el = $('#el1');
+
+        $.each([0, 1], (my) => {
+          $.each(['top', 'center', 'bottom'], (vIndex, vertical) => {
+            // eslint-disable-next-line max-nested-callbacks
+            $.each(['left', 'center', 'right'], (hIndex, horizontal) => {
+              const _my = my ? `${horizontal} ${vertical}` : 'left top';
+              const _at = !my ? `${horizontal} ${vertical}` : 'left top';
+              el.position({
+                my: _my,
+                at: _at,
+                of: '#parent',
+                collision: 'none',
+              });
+              toReturn[`my: ${_my} at: ${_at}`] = {
+                actual: el.offset(),
+                expected: {
+                  top: start.top + offsets[vertical] * (my ? -1 : 1),
+                  left: start.left + offsets[horizontal] * (my ? -1 : 1),
+                },
+              };
+            });
+          });
+        });
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 17);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  of: (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        const $parentX = $('#parentX');
+        const win = $(window);
+        let event;
+
+        // eslint-disable-next-line func-names
+        let scrollTopSupport = function () {
+          const support = win.scrollTop(1).scrollTop() === 1;
+          win.scrollTop(0);
+          // eslint-disable-next-line func-names
+          scrollTopSupport = function () {
+            return support;
+          };
+          return support;
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'left top',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn.selector = {
+          actual: $elx.offset(),
+          expected: { top: 40, left: 40 },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'left bottom',
+          of: $parentX,
+          collision: 'none',
+        });
+        toReturn['jQuery object'] = {
+          actual: $elx.offset(),
+          expected: { top: 60, left: 40 },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'left top',
+          of: $parentX[0],
+          collision: 'none',
+        });
+        toReturn['DOM element'] = {
+          actual: $elx.offset(),
+          expected: { top: 40, left: 40 },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'right bottom',
+          of: document,
+          collision: 'none',
+        });
+        toReturn.document = {
+          actual: $elx.offset(),
+          expected: {
+            top: $(document).height() - 10,
+            left: $(document).width() - 10,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'right bottom',
+          of: $(document),
+          collision: 'none',
+        });
+        toReturn['document as jQuery object'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: $(document).height() - 10,
+            left: $(document).width() - 10,
+          },
+        };
+
+        win.scrollTop(0);
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'right bottom',
+          of: window,
+          collision: 'none',
+        });
+
+        toReturn.window = {
+          actual: $elx.offset(),
+          expected: {
+            top: win.height() - 10,
+            left: win.width() - 10,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'right bottom',
+          of: win,
+          collision: 'none',
+        });
+        toReturn['window as jQuery object'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: win.height() - 10,
+            left: win.width() - 10,
+          },
+        };
+
+        if (scrollTopSupport()) {
+          win.scrollTop(500).scrollLeft(200);
+          $elx.position({
+            my: 'right bottom',
+            at: 'right bottom',
+            of: window,
+            collision: 'none',
+          });
+
+          toReturn['window, scrolled'] = {
+            actual: $elx.offset(),
+            expected: {
+              top: win.height() + 500 - 10,
+              left: win.width() + 200 - 10,
+            },
+          };
+
+          win.scrollTop(0).scrollLeft(0);
+        }
+
+        event = $.extend($.Event('someEvent'), { pageX: 200, pageY: 300 });
+        $elx.position({
+          my: 'left top',
+          at: 'left top',
+          of: event,
+          collision: 'none',
+        });
+        toReturn['event - left top, left top'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 300,
+            left: 200,
+          },
+        };
+
+        event = $.extend($.Event('someEvent'), { pageX: 400, pageY: 600 });
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: event,
+          collision: 'none',
+        });
+        toReturn['event - left top, right bottom'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 600,
+            left: 400,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 10);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  offsets: (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {
+          deepEquals: {},
+          trues: {},
+        };
+        const $elx = $('#elx');
+        let offset;
+
+        $elx.position({
+          my: 'left top',
+          at: 'left+10 bottom+10',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn.deepEquals['offsets in at'] = {
+          actual: $elx.offset(),
+          expected: { top: 70, left: 50 },
+        };
+
+        $elx.position({
+          my: 'left+10 top-10',
+          at: 'left bottom',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn.deepEquals['offsets in my'] = {
+          actual: $elx.offset(),
+          expected: { top: 50, left: 50 },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'left+50% bottom-10%',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn.deepEquals['percentage offsets in at'] = {
+          actual: $elx.offset(),
+          expected: { top: 58, left: 50 },
+        };
+
+        $elx.position({
+          my: 'left-30% top+50%',
+          at: 'left bottom',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn.deepEquals['percentage offsets in my'] = {
+          actual: $elx.offset(),
+          expected: { top: 65, left: 37 },
+        };
+
+        $elx.position({
+          my: 'left-30.001% top+50.0%',
+          at: 'left bottom',
+          of: '#parentX',
+          collision: 'none',
+        });
+        offset = $elx.offset();
+        toReturn.trues['decimal percentage top offsets in my'] =
+          Math.round(offset.top) === 65;
+        toReturn.trues['decimal percentage left offsets in my'] =
+          Math.round(offset.left) === 37;
+
+        $elx.position({
+          my: 'left+10.4 top-10.6',
+          at: 'left bottom',
+          of: '#parentX',
+          collision: 'none',
+        });
+        offset = $elx.offset();
+        toReturn.trues['decimal top offsets in my'] =
+          Math.round(offset.top) === 49;
+        toReturn.trues['decimal left offsets in my'] =
+          Math.round(offset.left) === 50;
+
+        $elx.position({
+          my: 'left+right top-left',
+          at: 'left-top bottom-bottom',
+          of: '#parentX',
+          collision: 'none',
+        });
+        toReturn.deepEquals['invalid offsets'] = {
+          actual: $elx.offset(),
+          expected: { top: 60, left: 40 },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value.trues).length, 4);
+        browser.assert.equal(Object.keys(result.value.deepEquals).length, 5);
+        Object.entries(result.value.deepEquals).forEach(([key, value]) => {
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+        Object.entries(result.value.trues).forEach(([key, value]) => {
+          browser.assert.equal(value, true, key);
+        });
+      },
+    );
+  },
+  using: (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        let count = 0;
+        const elems = $('#el1, #el2');
+        const of = $('#parentX');
+        const expectedPosition = { top: 60, left: 60 };
+        const expectedFeedback = {
+          target: {
+            element: of,
+            width: 20,
+            height: 20,
+            left: 40,
+            top: 40,
+          },
+          element: {
+            width: 6,
+            height: 6,
+            left: 60,
+            top: 60,
+          },
+          horizontal: 'left',
+          vertical: 'top',
+          important: 'vertical',
+        };
+        const originalPosition = elems
+          .position({
+            my: 'right bottom',
+            at: 'right bottom',
+            of: '#parentX',
+            collision: 'none',
+          })
+          .offset();
+
+        elems.position({
+          my: 'left top',
+          at: 'center+10 bottom',
+          of: '#parentX',
+          using(position, feedback) {
+            toReturn[`correct context for call #${count}`] = {
+              actual: this,
+              expected: elems[count],
+            };
+            toReturn[`correct position for call #${count}`] = {
+              actual: position,
+              expected: expectedPosition,
+            };
+            toReturn[`feedback and element match for call #${count}`] = {
+              actual: feedback.element.element[0],
+              expected: elems[count],
+            };
+            // assert.deepEqual(feedback.element.element[0], elems[count]);
+            delete feedback.element.element;
+            toReturn[`expected feedback after delete for call #${count}`] = {
+              actual: feedback,
+              expected: expectedFeedback,
+            };
+            count += 1;
+          },
+        });
+
+        // eslint-disable-next-line func-names
+        elems.each(function (index) {
+          toReturn[`elements not moved: ${index}`] = {
+            actual: $(this).offset(),
+            expected: originalPosition,
+          };
+        });
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 10);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: fit, no collision': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          collision: 'fit',
+        });
+
+        toReturn['no offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'right+2 bottom+3',
+          of: '#parent',
+          collision: 'fit',
+        });
+
+        toReturn['with offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 13,
+            left: 12,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 2);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: fit, collision': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        const win = $(window);
+        // eslint-disable-next-line func-names
+        let scrollTopSupport = function () {
+          const support = win.scrollTop(1).scrollTop() === 1;
+          win.scrollTop(0);
+          // eslint-disable-next-line func-names
+          scrollTopSupport = function () {
+            return support;
+          };
+          return support;
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          collision: 'fit',
+        });
+
+        toReturn['no offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 0,
+            left: 0,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left+2 top+3',
+          of: '#parent',
+          collision: 'fit',
+        });
+
+        toReturn['with offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 0,
+            left: 0,
+          },
+        };
+
+        if (scrollTopSupport()) {
+          win.scrollTop(300).scrollLeft(200);
+          $elx.position({
+            my: 'right bottom',
+            at: 'left top',
+            of: '#parent',
+            collision: 'fit',
+          });
+          toReturn['window scrolled'] = {
+            actual: $elx.offset(),
+            expected: {
+              top: 300,
+              left: 200,
+            },
+          };
+
+          win.scrollTop(0).scrollLeft(0);
+        }
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 3);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: flip, no collision': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          collision: 'flip',
+        });
+
+        toReturn['no offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'right+2 bottom+3',
+          of: '#parent',
+          collision: 'flip',
+        });
+
+        toReturn['with offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 13,
+            left: 12,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 2);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: flip, collision': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          collision: 'flip',
+        });
+
+        toReturn['no offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left+2 top+3',
+          of: '#parent',
+          collision: 'flip',
+        });
+
+        toReturn['with offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 7,
+            left: 8,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 2);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: flipfit, no collision': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          collision: 'flipfit',
+        });
+
+        toReturn['no offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'right+2 bottom+3',
+          of: '#parent',
+          collision: 'flipfit',
+        });
+
+        toReturn['with offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 13,
+            left: 12,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 2);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: flipfit, collision': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          collision: 'flipfit',
+        });
+
+        toReturn['no offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left+2 top+3',
+          of: '#parent',
+          collision: 'flipfit',
+        });
+
+        toReturn['with offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 7,
+            left: 8,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 2);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: none, no collision': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          collision: 'none',
+        });
+
+        toReturn['no offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'right+2 bottom+3',
+          of: '#parent',
+          collision: 'none',
+        });
+
+        toReturn['with offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 13,
+            left: 12,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 2);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: none, collision': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          collision: 'none',
+        });
+
+        toReturn['no offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: -6,
+            left: -6,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left+2 top+3',
+          of: '#parent',
+          collision: 'none',
+        });
+
+        toReturn['with offset'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: -3,
+            left: -4,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 2);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: fit, with margin': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx').css({
+          marginTop: 6,
+          marginLeft: 4,
+        });
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          collision: 'fit',
+        });
+
+        toReturn['right bottom'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          collision: 'fit',
+        });
+
+        toReturn['left top'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 6,
+            left: 4,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 2);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'collision: flip, with margin': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx').css({
+          marginTop: 6,
+          marginLeft: 4,
+        });
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          collision: 'flip',
+        });
+
+        toReturn['left top'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          collision: 'flip',
+        });
+
+        toReturn['right bottom'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'left top',
+          of: '#parent',
+          collision: 'flip',
+        });
+
+        toReturn['left top left top'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 0,
+            left: 4,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 3);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  within: (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          within: document,
+        });
+
+        toReturn['within document'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: 10,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          collision: 'fit',
+
+          within: '#within',
+        });
+
+        toReturn['fit - right bottom'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 4,
+            left: 2,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          within: '#within',
+          collision: 'fit',
+        });
+
+        toReturn['fit - left top'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 2,
+            left: 0,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          within: '#within',
+          collision: 'flip',
+        });
+
+        toReturn['flip - right bottom'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: -6,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          within: '#within',
+          collision: 'flip',
+        });
+
+        toReturn['flip - left top'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 10,
+            left: -6,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#parent',
+          within: '#within',
+          collision: 'flipfit',
+        });
+
+        toReturn['flipfit - right bottom'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 4,
+            left: 0,
+          },
+        };
+
+        $elx.position({
+          my: 'right bottom',
+          at: 'left top',
+          of: '#parent',
+          within: '#within',
+          collision: 'flipfit',
+        });
+
+        toReturn['flipfit - left top'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 4,
+            left: 0,
+          },
+        };
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 7);
+        Object.entries(result.value).forEach(([key, value]) => {
+          browser.assert.equal(typeof value, 'object');
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'with scrollbars': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+
+        const $scrollX = $('#scrollX');
+        $scrollX.css({
+          width: 100,
+          height: 100,
+          left: 0,
+          top: 0,
+        });
+
+        const $elx = $('#elx').position({
+          my: 'left top',
+          at: 'right bottom',
+          of: '#scrollX',
+          within: '#scrollX',
+          collision: 'fit',
+        });
+
+        toReturn.visible = {
+          actual: $elx.offset(),
+          expected: {
+            top: 90,
+            left: 90,
+          },
+        };
+
+        const scrollbarInfo = $.position.getScrollInfo(
+          $.position.getWithinInfo($('#scrollX')),
+        );
+
+        $elx.position({
+          of: '#scrollX',
+          collision: 'fit',
+          within: '#scrollX',
+          my: 'left top',
+          at: 'right bottom',
+        });
+
+        toReturn.scroll = {
+          actual: $elx.offset(),
+          expected: {
+            top: 90 - scrollbarInfo.height,
+            left: 90 - scrollbarInfo.width,
+          },
+        };
+
+        $scrollX.css({
+          overflow: 'auto',
+        });
+
+        toReturn['auto, no scroll"'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 90,
+            left: 90,
+          },
+        };
+
+        $scrollX
+          .css({
+            overflow: 'auto',
+          })
+          .append($('<div>').height(300).width(300));
+
+        $elx.position({
+          of: '#scrollX',
+          collision: 'fit',
+          within: '#scrollX',
+          my: 'left top',
+          at: 'right bottom',
+        });
+
+        toReturn['auto, with scroll'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 90 - scrollbarInfo.height,
+            left: 90 - scrollbarInfo.width,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 4);
+        Object.entries(result.value).forEach((key, value) => {
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  fractions: (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $fractionElement = $('#fractions-element').position({
+          my: 'left top',
+          at: 'left top',
+          of: '#fractions-parent',
+          collision: 'none',
+        });
+        toReturn['left top, left top'] = {
+          actual: $fractionElement.offset(),
+          expected: $('#fractions-parent').offset(),
+        };
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 1);
+        Object.entries(result.value).forEach((key, value) => {
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'bug #5280: consistent results (avoid fractional values)': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const wrapper = $('#bug-5280');
+        const elem = wrapper.children();
+        const offset1 = elem
+          .position({
+            my: 'center',
+            at: 'center',
+            of: wrapper,
+            collision: 'none',
+          })
+          .offset();
+        const offset2 = elem
+          .position({
+            my: 'center',
+            at: 'center',
+            of: wrapper,
+            collision: 'none',
+          })
+          .offset();
+        toReturn['offsets consistent'] = {
+          actual: offset1,
+          expected: offset2,
+        };
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 1);
+        Object.entries(result.value).forEach((key, value) => {
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+  'bug #8710: flip if flipped position fits more': (browser) => {
+    browser.execute(
+      // eslint-disable-next-line func-names
+      function () {
+        const $ = jQuery;
+        const toReturn = {};
+        const $elx = $('#elx');
+        $elx.position({
+          my: 'left top',
+          within: '#bug-8710-within-smaller',
+          of: '#parentX',
+          collision: 'flip',
+          at: 'right bottom+30',
+        });
+
+        toReturn['flip - top fits all'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 0,
+            left: 60,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          within: '#bug-8710-within-smaller',
+          of: '#parentX',
+          collision: 'flip',
+          at: 'right bottom+32',
+        });
+        toReturn['flip - top fits more'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: -2,
+            left: 60,
+          },
+        };
+
+        $elx.position({
+          my: 'left top',
+          within: '#bug-8710-within-bigger',
+          of: '#parentX',
+          collision: 'flip',
+          at: 'right bottom+32',
+        });
+        toReturn['no flip - top fits less'] = {
+          actual: $elx.offset(),
+          expected: {
+            top: 92,
+            left: 60,
+          },
+        };
+
+        return toReturn;
+      },
+      [],
+      (result) => {
+        browser.assert.equal(Object.keys(result.value).length, 3);
+        Object.entries(result.value).forEach((key, value) => {
+          browser.assert.deepEqual(value.actual, value.expected, key);
+        });
+      },
+    );
+  },
+};