placement.js
Advanced tools
Comparing version 1.0.0-beta.2 to 1.0.0-beta.3
@@ -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 @@ |
234
src/index.ts
@@ -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; | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
15247
6
140
63