UNPKG

positioning

Version:

The ng-bootstrap Positioning class as a standalone module

304 lines (298 loc) 13.2 kB
(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__; /******/ })() ; });