UNPKG

contain-by-screen

Version:

Position a dropdown element near a button in a way that fits on the screen.

283 lines (278 loc) 11.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.containByScreen = containByScreen; exports.getContainByScreenResults = getContainByScreenResults; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _flatten = _interopRequireDefault(require("lodash/flatten")); var _uniq = _interopRequireDefault(require("lodash/uniq")); var _isNotNil = require("./isNotNil"); function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /** * This option is used when position is "cover", "top", or "bottom". * It controls the horizontal alignment of the element relative to the anchor. * "center" means the element's center is aligned with the anchor's center. * "left" and "right" means that edge of the element is aligned with the same edge of the anchor. * "unaligned" means the element may not be aligned with the anchor at all. Currently * this works by doing the same thing as "center" and then adjusting the result to fit on screen. */ /** * Similar to {@link HAlignOption}, except this controls the vertical alignment of the element * relative to the anchor when the position is "cover", "left", or "right". */ function containByScreen(element, anchorPoint, options) { var choiceAndCoord = getContainByScreenResults(element, anchorPoint, options); element.style.top = "".concat(choiceAndCoord.coordinates.top, "px"); element.style.left = "".concat(choiceAndCoord.coordinates.left, "px"); return choiceAndCoord.choice; } function getContainByScreenResults(element, anchorPoint, options) { if (process.env.NODE_ENV !== "production" && window.getComputedStyle) { var style = window.getComputedStyle(element); if (style.position !== "fixed") { // eslint-disable-next-line no-console console.error("containByScreen only works on fixed position elements", element); } } var elRect = getBoundingClientRect(element); var anchorRect = getBoundingClientRect(anchorPoint); var buffers = { all: options.buffer || 0, top: options.topBuffer || 0, bottom: options.bottomBuffer || 0, left: options.leftBuffer || 0, right: options.rightBuffer || 0 }; var optionPositions = Array.isArray(options.position) ? options.position : [options.position].filter(_isNotNil.isNotNil); var optionHAligns = Array.isArray(options.hAlign) ? options.hAlign : [options.hAlign].filter(_isNotNil.isNotNil); var optionVAligns = Array.isArray(options.vAlign) ? options.vAlign : [options.vAlign].filter(_isNotNil.isNotNil); var positions = optionPositions.length > 0 && options.forcePosition ? optionPositions : (0, _uniq["default"])(optionPositions.concat(["top", "bottom", "left", "right"])); var hAligns = optionHAligns.length > 0 && options.forceHAlign ? optionHAligns : (0, _uniq["default"])(optionHAligns.concat(["center", "left", "right"])); var vAligns = optionVAligns.length > 0 && options.forceVAlign ? optionVAligns : (0, _uniq["default"])(optionVAligns.concat(["center", "top", "bottom"])); var allPossibleChoices = (0, _flatten["default"])(positions.map(function (position) { return position === "cover" ? (0, _flatten["default"])(hAligns.map(function (hAlign) { return vAligns.map(function (vAlign) { return { position: position, hAlign: hAlign, vAlign: vAlign }; }); })) : position === "top" || position === "bottom" ? hAligns.map(function (hAlign) { return { position: position, hAlign: hAlign, vAlign: "center" }; }) : vAligns.map(function (vAlign) { return { position: position, hAlign: "center", vAlign: vAlign }; }); })); // Try unaligned versions at the end if (!hAligns.includes("unaligned")) { allPossibleChoices.push.apply(allPossibleChoices, (0, _toConsumableArray2["default"])((0, _flatten["default"])(positions.map(function (position) { return !["cover", "top", "bottom"].includes(position) ? [] : vAligns.map(function (vAlign) { return { position: position, hAlign: "unaligned", vAlign: vAlign }; }); })))); } if (!vAligns.includes("unaligned")) { allPossibleChoices.push.apply(allPossibleChoices, (0, _toConsumableArray2["default"])((0, _flatten["default"])(positions.map(function (position) { return !["cover", "left", "right"].includes(position) ? [] : hAligns.map(function (hAlign) { return { position: position, hAlign: hAlign, vAlign: "unaligned" }; }); })))); } var choiceAndCoord = null; for (var i = 0; i < allPossibleChoices.length; i++) { var choice = allPossibleChoices[i]; var coordinates = positionAndAlign(elRect, anchorRect, choice, buffers); var top = coordinates.top, left = coordinates.left; var ignoreHorizontalConstraints = choice.hAlign === "unaligned" && ["cover", "top", "bottom"].includes(choice.position); var ignoreVerticalConstraints = choice.vAlign === "unaligned" && ["cover", "left", "right"].includes(choice.position); var hasHorizontalFit = ignoreHorizontalConstraints || left - buffers.all - buffers.left >= 0 && left + elRect.width + buffers.all + buffers.right <= window.innerWidth; var hasVerticalFit = ignoreVerticalConstraints || top - buffers.all - buffers.top >= 0 && top + elRect.height + buffers.all + buffers.bottom <= window.innerHeight; if (hasHorizontalFit && hasVerticalFit) { choiceAndCoord = { choice: choice, coordinates: coordinates }; break; } } // Fallback if we failed to find a choice that fit on the screen. if (!choiceAndCoord) { var _choice = { position: "cover", hAlign: "unaligned", vAlign: "unaligned" }; choiceAndCoord = { choice: _choice, coordinates: positionAndAlign(elRect, anchorRect, _choice, buffers) }; } return choiceAndCoord; } function getBoundingClientRect(el) { var rect = el.getBoundingClientRect(); if (!("width" in rect)) { // IE <9 support rect = _objectSpread({ width: rect.right - rect.left, height: rect.bottom - rect.top }, rect); } return rect; } function positionAndAlign(elRect, anchorRect, _ref, buffers) { var position = _ref.position, hAlign = _ref.hAlign, vAlign = _ref.vAlign; var top = 0, left = 0; if (position === "cover") { switch (hAlign) { case "center": left = Math.round((anchorRect.left + anchorRect.right - elRect.width) / 2); break; case "left": left = Math.floor(anchorRect.left); break; case "right": left = Math.ceil(anchorRect.right - elRect.width); break; case "unaligned": { left = Math.round((anchorRect.left + anchorRect.right - elRect.width) / 2); var overhang = Math.ceil(left + elRect.width + buffers.all + buffers.right - window.innerWidth); if (overhang > 0) { left -= overhang; } left = Math.max(buffers.all + buffers.left, left); break; } default: hAlign; throw new Error("Should not happen"); } switch (vAlign) { case "center": top = Math.round((anchorRect.top + anchorRect.bottom - elRect.height) / 2); break; case "top": top = Math.floor(anchorRect.top); break; case "bottom": top = Math.ceil(anchorRect.bottom - elRect.height); break; case "unaligned": { top = Math.round((anchorRect.top + anchorRect.bottom - elRect.height) / 2); var _overhang = Math.ceil(top + elRect.height + buffers.all + buffers.bottom - window.innerHeight); if (_overhang > 0) { top -= _overhang; } top = Math.max(buffers.all + buffers.top, top); break; } default: vAlign; throw new Error("Should not happen"); } } else if (position === "top" || position === "bottom") { switch (position) { case "top": top = Math.floor(anchorRect.top - elRect.height - buffers.all - buffers.bottom); break; case "bottom": top = Math.ceil(anchorRect.bottom + buffers.all + buffers.top); break; default: position; throw new Error("Should not happen"); } switch (hAlign) { case "center": left = Math.round((anchorRect.left + anchorRect.right - elRect.width) / 2); break; case "left": left = Math.round(anchorRect.left); break; case "right": left = Math.round(anchorRect.right - elRect.width); break; case "unaligned": { left = Math.round((anchorRect.left + anchorRect.right - elRect.width) / 2); var _overhang2 = Math.ceil(left + elRect.width + buffers.all + buffers.right - window.innerWidth); if (_overhang2 > 0) { left -= _overhang2; } left = Math.max(buffers.all + buffers.left, left); break; } default: hAlign; throw new Error("Should not happen"); } } else { switch (position) { case "left": left = Math.floor(anchorRect.left - elRect.width - buffers.all - buffers.right); break; case "right": left = Math.ceil(anchorRect.right + buffers.all + buffers.left); break; default: position; throw new Error("Should not happen"); } switch (vAlign) { case "center": top = Math.round((anchorRect.top + anchorRect.bottom - elRect.height) / 2); break; case "top": top = Math.round(anchorRect.top); break; case "bottom": top = Math.round(anchorRect.bottom - elRect.height); break; case "unaligned": { top = Math.round((anchorRect.top + anchorRect.bottom - elRect.height) / 2); var _overhang3 = Math.ceil(top + elRect.height + buffers.all + buffers.bottom - window.innerHeight); if (_overhang3 > 0) { top -= _overhang3; } top = Math.max(buffers.all + buffers.top, top); break; } default: vAlign; throw new Error("Should not happen"); } } return { top: top, left: left }; } //# sourceMappingURL=index.js.map