positioning
Version:
The ng-bootstrap Positioning class as a standalone module
304 lines (298 loc) • 13.2 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["positioning"] = factory();
else
root["positioning"] = factory();
})(typeof self !== 'undefined' ? self : this, () => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Positioning: () => (/* binding */ Positioning),
/* harmony export */ positionElements: () => (/* binding */ positionElements)
/* harmony export */ });
// previous version:
// https://github.com/angular-ui/bootstrap/blob/07c31d0731f7cb068a1932b8e01d2312b796b4ec/src/position/position.js
class Positioning {
getAllStyles(element) { return window.getComputedStyle(element); }
getStyle(element, prop) { return this.getAllStyles(element)[prop]; }
isStaticPositioned(element) {
return (this.getStyle(element, 'position') || 'static') === 'static';
}
offsetParent(element) {
let offsetParentEl = element.offsetParent || document.documentElement;
while (offsetParentEl && offsetParentEl !== document.documentElement && this.isStaticPositioned(offsetParentEl)) {
offsetParentEl = offsetParentEl.offsetParent;
}
return offsetParentEl || document.documentElement;
}
position(element, round = true) {
let elPosition;
let parentOffset = { width: 0, height: 0, top: 0, bottom: 0, left: 0, right: 0 };
if (this.getStyle(element, 'position') === 'fixed') {
elPosition = element.getBoundingClientRect();
elPosition = {
top: elPosition.top,
bottom: elPosition.bottom,
left: elPosition.left,
right: elPosition.right,
height: elPosition.height,
width: elPosition.width
};
}
else {
const offsetParentEl = this.offsetParent(element);
elPosition = this.offset(element, false);
if (offsetParentEl !== document.documentElement) {
parentOffset = this.offset(offsetParentEl, false);
}
parentOffset.top += offsetParentEl.clientTop;
parentOffset.left += offsetParentEl.clientLeft;
}
elPosition.top -= parentOffset.top;
elPosition.bottom -= parentOffset.top;
elPosition.left -= parentOffset.left;
elPosition.right -= parentOffset.left;
if (round) {
elPosition.top = Math.round(elPosition.top);
elPosition.bottom = Math.round(elPosition.bottom);
elPosition.left = Math.round(elPosition.left);
elPosition.right = Math.round(elPosition.right);
}
return elPosition;
}
offset(element, round = true) {
const elBcr = element.getBoundingClientRect();
const viewportOffset = {
top: window.pageYOffset - document.documentElement.clientTop,
left: window.pageXOffset - document.documentElement.clientLeft
};
let elOffset = {
height: elBcr.height || element.offsetHeight,
width: elBcr.width || element.offsetWidth,
top: elBcr.top + viewportOffset.top,
bottom: elBcr.bottom + viewportOffset.top,
left: elBcr.left + viewportOffset.left,
right: elBcr.right + viewportOffset.left
};
if (round) {
elOffset.height = Math.round(elOffset.height);
elOffset.width = Math.round(elOffset.width);
elOffset.top = Math.round(elOffset.top);
elOffset.bottom = Math.round(elOffset.bottom);
elOffset.left = Math.round(elOffset.left);
elOffset.right = Math.round(elOffset.right);
}
return elOffset;
}
/*
Return false if the element to position is outside the viewport
*/
positionElements(hostElement, targetElement, placement, appendToBody) {
const [placementPrimary = 'top', placementSecondary = 'center'] = placement.split('-');
const hostElPosition = appendToBody ? this.offset(hostElement, false) : this.position(hostElement, false);
const targetElStyles = this.getAllStyles(targetElement);
const marginTop = parseFloat(targetElStyles.marginTop);
const marginBottom = parseFloat(targetElStyles.marginBottom);
const marginLeft = parseFloat(targetElStyles.marginLeft);
const marginRight = parseFloat(targetElStyles.marginRight);
let topPosition = 0;
let leftPosition = 0;
switch (placementPrimary) {
case 'top':
topPosition = (hostElPosition.top - (targetElement.offsetHeight + marginTop + marginBottom));
break;
case 'bottom':
topPosition = (hostElPosition.top + hostElPosition.height);
break;
case 'left':
leftPosition = (hostElPosition.left - (targetElement.offsetWidth + marginLeft + marginRight));
break;
case 'right':
leftPosition = (hostElPosition.left + hostElPosition.width);
break;
}
switch (placementSecondary) {
case 'top':
topPosition = hostElPosition.top;
break;
case 'bottom':
topPosition = hostElPosition.top + hostElPosition.height - targetElement.offsetHeight;
break;
case 'left':
leftPosition = hostElPosition.left;
break;
case 'right':
leftPosition = hostElPosition.left + hostElPosition.width - targetElement.offsetWidth;
break;
case 'center':
if (placementPrimary === 'top' || placementPrimary === 'bottom') {
leftPosition = (hostElPosition.left + hostElPosition.width / 2 - targetElement.offsetWidth / 2);
}
else {
topPosition = (hostElPosition.top + hostElPosition.height / 2 - targetElement.offsetHeight / 2);
}
break;
}
/// The translate3d/gpu acceleration render a blurry text on chrome, the next line is commented until a browser fix
// targetElement.style.transform = `translate3d(${Math.round(leftPosition)}px, ${Math.floor(topPosition)}px, 0px)`;
targetElement.style.transform = `translate(${Math.round(leftPosition)}px, ${Math.round(topPosition)}px)`;
// Check if the targetElement is inside the viewport
const targetElBCR = targetElement.getBoundingClientRect();
const html = document.documentElement;
const windowHeight = window.innerHeight || html.clientHeight;
const windowWidth = window.innerWidth || html.clientWidth;
return targetElBCR.left >= 0 && targetElBCR.top >= 0 && targetElBCR.right <= windowWidth &&
targetElBCR.bottom <= windowHeight;
}
}
const placementSeparator = /\s+/;
const positionService = new Positioning();
/*
* Accept the placement array and applies the appropriate placement dependent on the viewport.
* Returns the applied placement.
* In case of auto placement, placements are selected in order
* 'top', 'bottom', 'left', 'right',
* 'top-left', 'top-right',
* 'bottom-left', 'bottom-right',
* 'left-top', 'left-bottom',
* 'right-top', 'right-bottom'.
* */
function positionElements(hostElement, targetElement, placement, appendToBody, baseClass) {
let placementVals = Array.isArray(placement) ? placement : placement.split(placementSeparator);
const allowedPlacements = [
'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right', 'left-top', 'left-bottom',
'right-top', 'right-bottom'
];
const classList = targetElement.classList;
const addClassesToTarget = (targetPlacement) => {
const [primary, secondary] = targetPlacement.split('-');
const classes = [];
if (baseClass) {
classes.push(`${baseClass}-${primary}`);
if (secondary) {
classes.push(`${baseClass}-${primary}-${secondary}`);
}
classes.forEach((classname) => { classList.add(classname); });
}
return classes;
};
// Remove old placement classes to avoid issues
if (baseClass) {
allowedPlacements.forEach((placementToRemove) => { classList.remove(`${baseClass}-${placementToRemove}`); });
}
// replace auto placement with other placements
let hasAuto = placementVals.findIndex(val => val === 'auto');
if (hasAuto >= 0) {
allowedPlacements.forEach(function (obj) {
if (placementVals.find(val => val.search('^' + obj) !== -1) == null) {
placementVals.splice(hasAuto++, 1, obj);
}
});
}
// coordinates where to position
// Required for transform:
const style = targetElement.style;
style.position = 'absolute';
style.top = '0';
style.left = '0';
style['will-change'] = 'transform';
let testPlacement;
let isInViewport = false;
for (testPlacement of placementVals) {
let addedClasses = addClassesToTarget(testPlacement);
if (positionService.positionElements(hostElement, targetElement, testPlacement, appendToBody)) {
isInViewport = true;
break;
}
// Remove the baseClasses for further calculation
if (baseClass) {
addedClasses.forEach((classname) => { classList.remove(classname); });
}
}
if (!isInViewport) {
// If nothing match, the first placement is the default one
testPlacement = placementVals[0];
addClassesToTarget(testPlacement);
positionService.positionElements(hostElement, targetElement, testPlacement, appendToBody);
}
return testPlacement;
}
/***/ })
/******/ ]);
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk.
(() => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ positionElements: () => (/* reexport safe */ _positioning_js__WEBPACK_IMPORTED_MODULE_0__.positionElements)
/* harmony export */ });
/* harmony import */ var _positioning_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
})();
/******/ return __webpack_exports__;
/******/ })()
;
});