Socket
Socket
Sign inDemoInstall

placement.js

Package Overview
Dependencies
0
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.0-beta.2 to 1.0.0-beta.3

CHANGELOG.md

2

dist/index.cjs.js

@@ -1,1 +0,1 @@

"use strict";var e={size:["height","width"],clientSize:["clientHeight","clientWidth"],offsetSize:["offsetHeight","offsetWidth"],maxSize:["maxHeight","maxWidth"],before:["top","left"],marginBefore:["marginTop","marginLeft"],after:["bottom","right"],marginAfter:["marginBottom","marginRight"],scrollOffset:["pageYOffset","pageXOffset"]};function t(e){return{top:e.top,bottom:e.bottom,left:e.left,right:e.right}}module.exports=function(o,r,f,i,a){void 0===f&&(f="bottom"),void 0===i&&(i="center"),void 0===a&&(a={}),(r instanceof Element||r instanceof Range)&&(r=t(r.getBoundingClientRect()));var n=Object.assign({top:r.bottom,bottom:r.top,left:r.right,right:r.left},r),s=window.visualViewport,l={top:0,left:0,bottom:s?s.height:window.innerHeight,right:s?s.width:window.innerWidth};a.bound&&((a.bound instanceof Element||a.bound instanceof Range)&&(a.bound=t(a.bound.getBoundingClientRect())),Object.assign(l,a.bound));var m=getComputedStyle(o),b={},d={};for(var g in e)b[g]=e[g]["top"===f||"bottom"===f?0:1],d[g]=e[g]["top"===f||"bottom"===f?1:0];o.style.position=a.fixed?"fixed":"absolute",o.style.maxWidth="",o.style.maxHeight="";var h=parseInt(m[d.marginBefore]),p=parseInt(m[d.marginAfter]),u=h+p,c=l[d.after]-l[d.before]-u,x=parseInt(m[d.maxSize]);(!x||c<x)&&(o.style[d.maxSize]=c+"px");var y=parseInt(m[b.marginBefore])+parseInt(m[b.marginAfter]),z=n[b.before]-l[b.before]-y,S=l[b.after]-n[b.after]-y;(f===b.before&&o[b.offsetSize]>z||f===b.after&&o[b.offsetSize]>S)&&(f=z>S?b.before:b.after);var v=f===b.before?z:S,w=parseInt(m[b.maxSize]);(!w||v<w)&&(o.style[b.maxSize]=v+"px");var O=a.fixed?0:window[b.scrollOffset],B=function(e){return Math.max(l[b.before],Math.min(e,l[b.after]-o[b.offsetSize]-y))};f===b.before?(o.style[b.before]=O+B(n[b.before]-o[b.offsetSize]-y)+"px",o.style[b.after]="auto"):(o.style[b.before]=O+B(n[b.after])+"px",o.style[b.after]="auto");var I=a.fixed?0:window[d.scrollOffset],H=function(e){return Math.max(l[d.before],Math.min(e,l[d.after]-o[d.offsetSize]-u))};switch(i){case"start":o.style[d.before]=I+H(n[d.before]-h)+"px",o.style[d.after]="auto";break;case"end":o.style[d.before]="auto",o.style[d.after]=I+H(document.documentElement[d.clientSize]-n[d.after]-p)+"px";break;default:var R=n[d.after]-n[d.before];o.style[d.before]=I+H(n[d.before]+R/2-o[d.offsetSize]/2-h)+"px",o.style[d.after]="auto"}o.dataset.side=f,o.dataset.align=i};
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const t={x:{start:"left",Start:"Left",end:"right",End:"Right",size:"width",Size:"Width"},y:{start:"top",Start:"Top",end:"bottom",End:"Bottom",size:"height",Size:"Height"}};exports.place=function(e,n,o){var i;const s=o.placement||"bottom";let[a,d]=s.split("-");const r=["top","bottom"].includes(a)?"y":"x",l="y"===r?"x":"y",c=t[r],u=t[l],p=n.style;p.position="absolute",p.maxWidth=p.maxHeight="";const f=e.getBoundingClientRect(),m=(null===(i=function(t){for(;(t=t.parentNode)&&t instanceof Element;){const e=getComputedStyle(t).overflow;if(["auto","scroll"].includes(e))return t}}(n))||void 0===i?void 0:i.getBoundingClientRect())||(h=0,g=0,x=window.innerWidth,S=window.innerHeight,{top:h,left:g,right:x,bottom:S,width:x,height:S});var h,g,x,S;p["max"+u.Size]=m[u.size]+"px";const z={[c.start]:f[c.start]-m[c.start],[c.end]:m[c.end]-f[c.end]};let y;n["offset"+c.Size]>z[a]&&(a=z[c.start]>z[c.end]?c.start:c.end),p["max"+c.Size]=z[a]+"px";const b=n.offsetParent;if(b&&b!==document.body){const e=b.getBoundingClientRect(),n=getComputedStyle(b);y=o=>e[t[o].start]+parseInt(n["border"+t[o].Start+"Width"])}const w=(e,o)=>Math.max(m[t[o].start],Math.min(e,m[t[o].end]-n["offset"+t[o].Size]))-(y?y(o):0),v=document.documentElement;if(a===c.start?(p[c.start]="auto",p[c.end]=w(v["client"+c.Size]-f[c.start],r)+"px"):(p[c.start]=w(f[c.end],r)+"px",p[c.end]="auto"),"end"===d)p[u.start]="auto",p[u.end]=w(v["client"+u.Size]-f[u.end],l)+"px";else{let t=0;if("start"!==d){t=f[u.size]/2-n["offset"+u.Size]/2}p[u.start]=w(f[u.start]+t,l)+"px",p[u.end]="auto"}n.dataset.placement=a+(d?"-"+d:"")};

@@ -1,14 +0,6 @@

declare type Side = 'top' | 'bottom' | 'left' | 'right';
declare type Align = 'start' | 'center' | 'end';
declare type Coordinates = {
top?: number;
bottom?: number;
left?: number;
right?: number;
};
declare type Options = {
bound?: Element | Range | Coordinates;
fixed?: boolean;
placement?: Placement;
};
export default function (overlay: HTMLElement, anchor: Element | Range | Coordinates, side?: Side, align?: Align, options?: Options): void;
declare type Placement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'right' | 'right-start' | 'right-end' | 'left' | 'left-start' | 'left-end';
export declare function place(anchor: HTMLElement, overlay: HTMLElement, options: Options): void;
export {};

@@ -1,1 +0,1 @@

var e={size:["height","width"],clientSize:["clientHeight","clientWidth"],offsetSize:["offsetHeight","offsetWidth"],maxSize:["maxHeight","maxWidth"],before:["top","left"],marginBefore:["marginTop","marginLeft"],after:["bottom","right"],marginAfter:["marginBottom","marginRight"],scrollOffset:["pageYOffset","pageXOffset"]};function t(e){return{top:e.top,bottom:e.bottom,left:e.left,right:e.right}}function o(o,f,r,i,a){void 0===r&&(r="bottom"),void 0===i&&(i="center"),void 0===a&&(a={}),(f instanceof Element||f instanceof Range)&&(f=t(f.getBoundingClientRect()));var n=Object.assign({top:f.bottom,bottom:f.top,left:f.right,right:f.left},f),s=window.visualViewport,l={top:0,left:0,bottom:s?s.height:window.innerHeight,right:s?s.width:window.innerWidth};a.bound&&((a.bound instanceof Element||a.bound instanceof Range)&&(a.bound=t(a.bound.getBoundingClientRect())),Object.assign(l,a.bound));var b=getComputedStyle(o),m={},d={};for(var g in e)m[g]=e[g]["top"===r||"bottom"===r?0:1],d[g]=e[g]["top"===r||"bottom"===r?1:0];o.style.position=a.fixed?"fixed":"absolute",o.style.maxWidth="",o.style.maxHeight="";var h=parseInt(b[d.marginBefore]),p=parseInt(b[d.marginAfter]),u=h+p,c=l[d.after]-l[d.before]-u,x=parseInt(b[d.maxSize]);(!x||c<x)&&(o.style[d.maxSize]=c+"px");var y=parseInt(b[m.marginBefore])+parseInt(b[m.marginAfter]),z=n[m.before]-l[m.before]-y,S=l[m.after]-n[m.after]-y;(r===m.before&&o[m.offsetSize]>z||r===m.after&&o[m.offsetSize]>S)&&(r=z>S?m.before:m.after);var v=r===m.before?z:S,w=parseInt(b[m.maxSize]);(!w||v<w)&&(o.style[m.maxSize]=v+"px");var O=a.fixed?0:window[m.scrollOffset],B=function(e){return Math.max(l[m.before],Math.min(e,l[m.after]-o[m.offsetSize]-y))};r===m.before?(o.style[m.before]=O+B(n[m.before]-o[m.offsetSize]-y)+"px",o.style[m.after]="auto"):(o.style[m.before]=O+B(n[m.after])+"px",o.style[m.after]="auto");var I=a.fixed?0:window[d.scrollOffset],H=function(e){return Math.max(l[d.before],Math.min(e,l[d.after]-o[d.offsetSize]-u))};switch(i){case"start":o.style[d.before]=I+H(n[d.before]-h)+"px",o.style[d.after]="auto";break;case"end":o.style[d.before]="auto",o.style[d.after]=I+H(document.documentElement[d.clientSize]-n[d.after]-p)+"px";break;default:var R=n[d.after]-n[d.before];o.style[d.before]=I+H(n[d.before]+R/2-o[d.offsetSize]/2-h)+"px",o.style[d.after]="auto"}o.dataset.side=r,o.dataset.align=i}export default o;
const t={x:{start:"left",Start:"Left",end:"right",End:"Right",size:"width",Size:"Width"},y:{start:"top",Start:"Top",end:"bottom",End:"Bottom",size:"height",Size:"Height"}};function e(e,n,o){var i;const s=o.placement||"bottom";let[a,d]=s.split("-");const r=["top","bottom"].includes(a)?"y":"x",l="y"===r?"x":"y",c=t[r],f=t[l],m=n.style;m.position="absolute",m.maxWidth=m.maxHeight="";const u=e.getBoundingClientRect(),p=(null===(i=function(t){for(;(t=t.parentNode)&&t instanceof Element;){const e=getComputedStyle(t).overflow;if(["auto","scroll"].includes(e))return t}}(n))||void 0===i?void 0:i.getBoundingClientRect())||(h=0,g=0,x=window.innerWidth,S=window.innerHeight,{top:h,left:g,right:x,bottom:S,width:x,height:S});var h,g,x,S;m["max"+f.Size]=p[f.size]+"px";const z={[c.start]:u[c.start]-p[c.start],[c.end]:p[c.end]-u[c.end]};let y;n["offset"+c.Size]>z[a]&&(a=z[c.start]>z[c.end]?c.start:c.end),m["max"+c.Size]=z[a]+"px";const b=n.offsetParent;if(b&&b!==document.body){const e=b.getBoundingClientRect(),n=getComputedStyle(b);y=o=>e[t[o].start]+parseInt(n["border"+t[o].Start+"Width"])}const w=(e,o)=>Math.max(p[t[o].start],Math.min(e,p[t[o].end]-n["offset"+t[o].Size]))-(y?y(o):0),v=document.documentElement;if(a===c.start?(m[c.start]="auto",m[c.end]=w(v["client"+c.Size]-u[c.start],r)+"px"):(m[c.start]=w(u[c.end],r)+"px",m[c.end]="auto"),"end"===d)m[f.start]="auto",m[f.end]=w(v["client"+f.Size]-u[f.end],l)+"px";else{let t=0;if("start"!==d){t=u[f.size]/2-n["offset"+f.Size]/2}m[f.start]=w(u[f.start]+t,l)+"px",m[f.end]="auto"}n.dataset.placement=a+(d?"-"+d:"")}export{e as place};

@@ -1,1 +0,1 @@

var placement=function(){"use strict";var e={size:["height","width"],clientSize:["clientHeight","clientWidth"],offsetSize:["offsetHeight","offsetWidth"],maxSize:["maxHeight","maxWidth"],before:["top","left"],marginBefore:["marginTop","marginLeft"],after:["bottom","right"],marginAfter:["marginBottom","marginRight"],scrollOffset:["pageYOffset","pageXOffset"]};function t(e){return{top:e.top,bottom:e.bottom,left:e.left,right:e.right}}return function(o,r,f,i,a){void 0===f&&(f="bottom"),void 0===i&&(i="center"),void 0===a&&(a={}),(r instanceof Element||r instanceof Range)&&(r=t(r.getBoundingClientRect()));var n=Object.assign({top:r.bottom,bottom:r.top,left:r.right,right:r.left},r),s=window.visualViewport,l={top:0,left:0,bottom:s?s.height:window.innerHeight,right:s?s.width:window.innerWidth};a.bound&&((a.bound instanceof Element||a.bound instanceof Range)&&(a.bound=t(a.bound.getBoundingClientRect())),Object.assign(l,a.bound));var m=getComputedStyle(o),b={},g={};for(var d in e)b[d]=e[d]["top"===f||"bottom"===f?0:1],g[d]=e[d]["top"===f||"bottom"===f?1:0];o.style.position=a.fixed?"fixed":"absolute",o.style.maxWidth="",o.style.maxHeight="";var u=parseInt(m[g.marginBefore]),c=parseInt(m[g.marginAfter]),h=u+c,p=l[g.after]-l[g.before]-h,x=parseInt(m[g.maxSize]);(!x||p<x)&&(o.style[g.maxSize]=p+"px");var y=parseInt(m[b.marginBefore])+parseInt(m[b.marginAfter]),v=n[b.before]-l[b.before]-y,z=l[b.after]-n[b.after]-y;(f===b.before&&o[b.offsetSize]>v||f===b.after&&o[b.offsetSize]>z)&&(f=v>z?b.before:b.after);var S=f===b.before?v:z,w=parseInt(m[b.maxSize]);(!w||S<w)&&(o.style[b.maxSize]=S+"px");var O=a.fixed?0:window[b.scrollOffset],B=function(e){return Math.max(l[b.before],Math.min(e,l[b.after]-o[b.offsetSize]-y))};f===b.before?(o.style[b.before]=O+B(n[b.before]-o[b.offsetSize]-y)+"px",o.style[b.after]="auto"):(o.style[b.before]=O+B(n[b.after])+"px",o.style[b.after]="auto");var I=a.fixed?0:window[g.scrollOffset],H=function(e){return Math.max(l[g.before],Math.min(e,l[g.after]-o[g.offsetSize]-h))};switch(i){case"start":o.style[g.before]=I+H(n[g.before]-u)+"px",o.style[g.after]="auto";break;case"end":o.style[g.before]="auto",o.style[g.after]=I+H(document.documentElement[g.clientSize]-n[g.after]-c)+"px";break;default:var R=n[g.after]-n[g.before];o.style[g.before]=I+H(n[g.before]+R/2-o[g.offsetSize]/2-u)+"px",o.style[g.after]="auto"}o.dataset.side=f,o.dataset.align=i}}();
var placement=function(t){"use strict";const e={x:{start:"left",Start:"Left",end:"right",End:"Right",size:"width",Size:"Width"},y:{start:"top",Start:"Top",end:"bottom",End:"Bottom",size:"height",Size:"Height"}};return t.place=function(t,n,o){var i;const s=o.placement||"bottom";let[a,d]=s.split("-");const r=["top","bottom"].includes(a)?"y":"x",l="y"===r?"x":"y",c=e[r],u=e[l],f=n.style;f.position="absolute",f.maxWidth=f.maxHeight="";const m=t.getBoundingClientRect(),p=(null===(i=function(t){for(;(t=t.parentNode)&&t instanceof Element;){const e=getComputedStyle(t).overflow;if(["auto","scroll"].includes(e))return t}}(n))||void 0===i?void 0:i.getBoundingClientRect())||(h=0,g=0,x=window.innerWidth,S=window.innerHeight,{top:h,left:g,right:x,bottom:S,width:x,height:S});var h,g,x,S;f["max"+u.Size]=p[u.size]+"px";const z={[c.start]:m[c.start]-p[c.start],[c.end]:p[c.end]-m[c.end]};let y;n["offset"+c.Size]>z[a]&&(a=z[c.start]>z[c.end]?c.start:c.end),f["max"+c.Size]=z[a]+"px";const b=n.offsetParent;if(b&&b!==document.body){const t=b.getBoundingClientRect(),n=getComputedStyle(b);y=o=>t[e[o].start]+parseInt(n["border"+e[o].Start+"Width"])}const v=(t,o)=>Math.max(p[e[o].start],Math.min(t,p[e[o].end]-n["offset"+e[o].Size]))-(y?y(o):0),w=document.documentElement;if(a===c.start?(f[c.start]="auto",f[c.end]=v(w["client"+c.Size]-m[c.start],r)+"px"):(f[c.start]=v(m[c.end],r)+"px",f[c.end]="auto"),"end"===d)f[u.start]="auto",f[u.end]=v(w["client"+u.Size]-m[u.end],l)+"px";else{let t=0;if("start"!==d){t=m[u.size]/2-n["offset"+u.Size]/2}f[u.start]=v(m[u.start]+t,l)+"px",f[u.end]="auto"}n.dataset.placement=a+(d?"-"+d:"")},Object.defineProperty(t,"__esModule",{value:!0}),t}({});
{
"name": "placement.js",
"version": "1.0.0-beta.2",
"description": "A tiny library for positioning overlays. Useful for drop-downs, tooltips, and popovers.",
"version": "1.0.0-beta.3",
"description": "A tiny library for positioning overlays. Useful for tooltips, popovers etc.",
"main": "./dist/index.cjs.js",

@@ -27,8 +27,10 @@ "module": "./dist/index.es.js",

"src",
"README.md"
"README.md",
"!**/.DS_Store"
],
"devDependencies": {
"@rollup/plugin-typescript": "^8.1.0",
"rollup": "^2.36.2",
"rollup-plugin-filesize": "^9.1.1",
"rollup-plugin-terser": "^7.0.2",
"@rollup/plugin-typescript": "^8.1.0",
"tslib": "^2.1.0",

@@ -35,0 +37,0 @@ "typescript": "^4.1.3"

# Placement.js
> A tiny library for positioning overlays. Useful for dropdowns, tooltips, popovers etc.
> A tiny library for positioning overlays. Useful for tooltips, popovers etc.
![Size](https://badge-size.now.sh/https://unpkg.com/placement.js/index.iife.js?compression=gzip)
![Size](https://img.shields.io/bundlephobia/minzip/placement.js)
[**Demo**](https://tobyzerner.github.io/placement.js/demo.html)
**Why?** Positioning UI overlays like dropdown menus and tooltips can be challenging. If you position them with CSS, then at some stage you are likely to run into problems with z-indices, parent overflows, or content going off the edge of the screen. Ideally overlays should be appended to the `<body>`, but then they need to be absolutely positioned, taking into account the viewport edges and content size.
**Why?** Positioning UI overlays like popovers and tooltips can be challenging. If you position them with CSS, then at some stage you are likely to run into problems with z-indices, parent overflows, or content going off the edge of the screen. JavaScript to the rescue!
**Why not [Popper.js](https://github.com/FezVrasta/popper.js)?** Because 7kb min+gzip just to position a tooltip is too much. Popper.js is powerful and addresses a lot of use-cases, but as a result it is relatively large and can be difficult to configure. When you just need something simple...
**Placement.js** addresses a single use-case: positioning an overlay to one side of an anchor element, optimizing the placement depending on the size of the overlay:
* If the overlay is too big, it may swap to the other side if there is more room available.
* The position and dimensions of the overlay will be capped so that it will never go off-screen.
* If the overlay is too big, it may **flip** to the other side if there is more room available.
* The dimensions of the overlay will be **capped** so that it will never go off-screen.
Small filesize is a priority for Placement.js so that it can be included in web components. For a more powerful, configurable library, check out [Popper.js](https://popper.js.org).
## Installation

@@ -27,44 +27,31 @@

```ts
import placement from 'placement.js';
import { place } from 'placement.js';
placement(
place(
anchor: HTMLElement,
overlay: HTMLElement,
anchor: Element | Range | Coordinates,
side: 'top' | 'bottom' | 'left' | 'right',
align: 'start' | 'center' | 'end',
options?: Options
);
type Coordinates = {
top?: number,
bottom?: number,
left?: number,
right?: number
type Options = {
placement?: Placement // defaults to 'bottom'
};
type Options = {
// Constrain the overlay position (defaults to the viewport)
bound?: Element | Range | Coordinates,
// Use fixed positioning instead of absolute
fixed?: boolean
};
type Placement =
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'right'
| 'right-start'
| 'right-end'
| 'left'
| 'left-start'
| 'left-end';
```
Check out the [demo](https://tobyzerner.github.io/placement.js/demo.html) to see what these parameters do.
Check out the [demo](https://tobyzerner.github.io/placement.js/demo.html) to see it in action.
### FAQ
#### My overlay isn't being positioned correctly. What gives?
When calculating the overlay position, Placement.js does not account for positioned ancestors (ie. containing elements with a position of `relative`, `absolute`, or `fixed`), because it would add extra complexity. Instead, you should change your DOM so that your overlay is appended directly onto the document body.
#### How do I update the position of the popup when things move (anchor position, window scroll, resize, etc)?
Placement.js purposely doesn't handle this for you. You can consider the following options:
* **Prevent this from happening in the first place.** For example, while a dropdown menu is open, scrolling the entire page doesn't have much utility. So just disable scrolling when a menu is open by applying `overflow: hidden` to the body and then you won't need to worry about the position.
* **Hide the overlay.** For example, if you scroll the page while you've got your mouse over an element showing a tooltip, then you should just hide the tooltip rather than trying to update its position.
* **Listen for these events yourself** and call `placement` again with the same parameters. The [demo](https://tobyzerner.github.io/placement.js/demo.html) is an example of this.
## Contributing

@@ -71,0 +58,0 @@

@@ -1,164 +0,132 @@

const NAMES = {
size: ['height', 'width'],
clientSize: ['clientHeight', 'clientWidth'],
offsetSize: ['offsetHeight', 'offsetWidth'],
maxSize: ['maxHeight', 'maxWidth'],
before: ['top', 'left'],
marginBefore: ['marginTop', 'marginLeft'],
after: ['bottom', 'right'],
marginAfter: ['marginBottom', 'marginRight'],
scrollOffset: ['pageYOffset', 'pageXOffset'],
type Options = {
placement?: Placement
};
type Side = 'top' | 'bottom' | 'left' | 'right';
type Align = 'start' | 'center' | 'end';
type Placement =
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'right'
| 'right-start'
| 'right-end'
| 'left'
| 'left-start'
| 'left-end';
type Coordinates = {
top?: number,
bottom?: number,
left?: number,
right?: number
const PROPS = {
x: { start: 'left', Start: 'Left', end: 'right', End: 'Right', size: 'width', Size: 'Width' },
y: { start: 'top', Start: 'Top', end: 'bottom', End: 'Bottom', size: 'height', Size: 'Height' }
};
type Options = {
bound?: Element | Range | Coordinates,
fixed?: boolean
};
function normalizeRect(rect: DOMRect | ClientRect): Coordinates {
return {
top: rect.top,
bottom: rect.bottom,
left: rect.left,
right: rect.right
};
}
export default function(
export function place(
anchor: HTMLElement,
overlay: HTMLElement,
anchor: Element | Range | Coordinates,
side: Side = 'bottom',
align: Align = 'center',
options: Options = {}
options: Options
) {
if (anchor instanceof Element || anchor instanceof Range) {
anchor = normalizeRect(anchor.getBoundingClientRect());
}
const placement = options.placement || 'bottom';
let [side, align] = placement.split('-');
const anchorRect = Object.assign({
top: anchor.bottom,
bottom: anchor.top,
left: anchor.right,
right: anchor.left,
}, anchor);
const mainAxis = ['top', 'bottom'].includes(side) ? 'y' : 'x';
const altAxis = mainAxis === 'y' ? 'x' : 'y';
const mainProps = PROPS[mainAxis];
const altProps = PROPS[altAxis];
const visualViewport = (window as any).visualViewport;
const boundRect = {
top: 0,
left: 0,
bottom: visualViewport ? visualViewport.height : window.innerHeight,
right: visualViewport ? visualViewport.width : window.innerWidth
};
// Reset the position and uncap the maximum size of the popup so that we
// can reliably determine if the popup is "too big" below.
const overlayStyle = overlay.style;
overlayStyle.position = 'absolute';
overlayStyle.maxWidth = overlayStyle.maxHeight = '';
if (options.bound) {
if (options.bound instanceof Element || options.bound instanceof Range) {
options.bound = normalizeRect(options.bound.getBoundingClientRect());
}
Object.assign(boundRect, options.bound);
}
// Get the rectangles defining the anchor element and the overflow boundary.
// To ensure a reliable calculation, this comes after resetting the overlay
// position to absolute.
const anchorRect = anchor.getBoundingClientRect();
const boundRect = scrollParent(overlay)?.getBoundingClientRect() || createRect(0, 0, window.innerWidth, window.innerHeight);
const overlayStyle = getComputedStyle(overlay);
const primary = {} as any;
const secondary = {} as any;
// Constrain the maximum size of the popup along the alignment axis.
overlayStyle['max' + altProps.Size] = boundRect[altProps.size] + 'px';
for (const key in NAMES) {
primary[key] = NAMES[key][side === 'top' || side === 'bottom' ? 0 : 1];
secondary[key] = NAMES[key][side === 'top' || side === 'bottom' ? 1 : 0];
}
overlay.style.position = options.fixed ? 'fixed' : 'absolute';
overlay.style.maxWidth = '';
overlay.style.maxHeight = '';
// Constrain the maximum size of the popup along the secondary axis.
const secondaryMarginBefore = parseInt(overlayStyle[secondary.marginBefore]);
const secondaryMarginAfter = parseInt(overlayStyle[secondary.marginAfter]);
const secondaryMargin = secondaryMarginBefore + secondaryMarginAfter;
const secondaryMaxSize = boundRect[secondary.after] - boundRect[secondary.before] - secondaryMargin;
const styledSecondaryMaxSize = parseInt(overlayStyle[secondary.maxSize]);
if (! styledSecondaryMaxSize || secondaryMaxSize < styledSecondaryMaxSize) {
overlay.style[secondary.maxSize] = secondaryMaxSize + 'px';
}
// Calculate the available room on either side of the anchor element. If
// the size of the popup is more than is available on the given side, then we
// will switch to the side with more room.
const margin = parseInt(overlayStyle[primary.marginBefore]) + parseInt(overlayStyle[primary.marginAfter]);
const roomBefore = anchorRect[primary.before] - boundRect[primary.before] - margin;
const roomAfter = boundRect[primary.after] - anchorRect[primary.after] - margin;
// the size of the popup is more than is available on the given side, then
// we will flip it to the side with more room.
const room = {
[mainProps.start]: anchorRect[mainProps.start] - boundRect[mainProps.start],
[mainProps.end]: boundRect[mainProps.end] - anchorRect[mainProps.end]
};
if ((side === primary.before && overlay[primary.offsetSize] > roomBefore)
|| (side === primary.after && overlay[primary.offsetSize] > roomAfter)) {
side = roomBefore > roomAfter ? primary.before : primary.after;
if (overlay['offset' + mainProps.Size] > room[side]) {
side = room[mainProps.start] > room[mainProps.end] ? mainProps.start : mainProps.end;
}
// If the size of the popup exceeds the room available on this side, then
// we will give the popup an explicit size so that it doesn't go off-screen.
const primaryMaxSize = side === primary.before ? roomBefore : roomAfter;
const styledPrimaryMaxSize = parseInt(overlayStyle[primary.maxSize]);
// Constrain the maximum size of the popup along the main axis.
overlayStyle['max' + mainProps.Size] = room[side] + 'px';
if (! styledPrimaryMaxSize || primaryMaxSize < styledPrimaryMaxSize) {
overlay.style[primary.maxSize] = primaryMaxSize + 'px';
let offset;
const offsetParent = overlay.offsetParent;
if (offsetParent && offsetParent !== document.body) {
const parentRect = offsetParent.getBoundingClientRect();
const parentStyle = getComputedStyle(offsetParent);
offset = axis => parentRect[PROPS[axis].start] + parseInt(parentStyle['border' + PROPS[axis].Start + 'Width']);
}
// Set the position of the popup element along the primary axis using the
// anchor's bounding rect. If we are working in the context of position:
// absolute, then we will need to add the window's scroll position as well.
const scrollOffset = options.fixed ? 0 : window[primary.scrollOffset] as unknown as number;
const boundPrimaryPos = pos => {
const pos = (pos, axis) => {
return Math.max(
boundRect[primary.before],
Math.min(pos, boundRect[primary.after] - overlay[primary.offsetSize] - margin)
);
boundRect[PROPS[axis].start],
Math.min(
pos,
boundRect[PROPS[axis].end] - overlay['offset' + PROPS[axis].Size]
)
) - (offset ? offset(axis) : 0);
};
if (side === primary.before) { // top or left
overlay.style[primary.before] = (scrollOffset + boundPrimaryPos(anchorRect[primary.before] - overlay[primary.offsetSize] - margin)) + 'px';
overlay.style[primary.after] = 'auto';
const dde = document.documentElement;
// Set the position of the popup along the main axis.
if (side === mainProps.start) { // top or left
overlayStyle[mainProps.start] = 'auto';
overlayStyle[mainProps.end] = pos(dde['client' + mainProps.Size] - anchorRect[mainProps.start], mainAxis) + 'px';
} else { // bottom or right
overlay.style[primary.before] = (scrollOffset + boundPrimaryPos(anchorRect[primary.after])) + 'px';
overlay.style[primary.after] = 'auto';
overlayStyle[mainProps.start] = pos(anchorRect[mainProps.end], mainAxis) + 'px';
overlayStyle[mainProps.end] = 'auto';
}
// Set the position of the popup element along the secondary axis.
const secondaryScrollOffset = options.fixed ? 0 : window[secondary.scrollOffset] as unknown as number;
// Set the position of the popup along the secondary axis.
if (align === 'end') {
overlayStyle[altProps.start] = 'auto';
overlayStyle[altProps.end] = pos(dde['client' + altProps.Size] - anchorRect[altProps.end], altAxis) + 'px';
} else {
let offset = 0;
if (align !== 'start') {
const anchorSize = anchorRect[altProps.size];
offset = anchorSize / 2 - overlay['offset' + altProps.Size] / 2;
}
const boundSecondaryPos = pos => {
return Math.max(
boundRect[secondary.before],
Math.min(pos, boundRect[secondary.after] - overlay[secondary.offsetSize] - secondaryMargin)
);
};
overlayStyle[altProps.start] = pos(anchorRect[altProps.start] + offset, altAxis) + 'px';
overlayStyle[altProps.end] = 'auto';
}
switch (align) {
case 'start':
overlay.style[secondary.before] = (secondaryScrollOffset + boundSecondaryPos(anchorRect[secondary.before] - secondaryMarginBefore)) + 'px';
overlay.style[secondary.after] = 'auto';
break;
overlay.dataset.placement = side + (align ? '-' + align : '');
}
case 'end':
overlay.style[secondary.before] = 'auto';
overlay.style[secondary.after] = (secondaryScrollOffset + boundSecondaryPos(document.documentElement[secondary.clientSize] - anchorRect[secondary.after] - secondaryMarginAfter)) + 'px';
break;
function createRect(top, left, width, height): ClientRect {
return {
top,
left,
right: width,
bottom: height,
width,
height
};
}
default: // center
const anchorSize = anchorRect[secondary.after] - anchorRect[secondary.before];
overlay.style[secondary.before] = (secondaryScrollOffset + boundSecondaryPos(anchorRect[secondary.before] + anchorSize / 2 - overlay[secondary.offsetSize] / 2 - secondaryMarginBefore)) + 'px';
overlay.style[secondary.after] = 'auto';
function scrollParent(node) {
while ((node = node.parentNode) && node instanceof Element) {
const overflow = getComputedStyle(node).overflow;
if (['auto', 'scroll'].includes(overflow)) {
return node;
}
}
overlay.dataset.side = side;
overlay.dataset.align = align;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc