placement.js
Advanced tools
Comparing version 1.0.0-beta.3 to 1.0.0-beta.4
@@ -8,3 +8,6 @@ # Changelog | ||
## [Unreleased] | ||
## [1.0.0-beta.4] - 2021-05-09 | ||
### Changed | ||
- Reverted to `default` export. | ||
- Refactored code to be more reliable and concise. | ||
@@ -11,0 +14,0 @@ ## [1.0.0-beta.3] - 2021-03-02 |
@@ -1,6 +0,8 @@ | ||
declare type Options = { | ||
export declare type Options = { | ||
placement?: Placement; | ||
flip?: boolean; | ||
cap?: boolean; | ||
}; | ||
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 default function (anchor: HTMLElement, overlay: HTMLElement, options: Options): void; | ||
export {}; |
@@ -1,1 +0,1 @@ | ||
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}; | ||
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,i){var o;const a=n.style;Object.assign(a,{position:"absolute",maxWidth:"",maxHeight:""});let[s="bottom",d="center"]=i.placement.split("-");const r=["top","bottom"].includes(s)?"y":"x";let c=s===t[r].start?t[r].end:t[r].start;const l="x"===r?"y":"x",p=e.getBoundingClientRect(),g=(null===(o=function(t){for(;(t=t.parentNode)&&t instanceof Element;){const e=getComputedStyle(t).overflow;if(["auto","scroll"].includes(e))return t}}(n))||void 0===o?void 0:o.getBoundingClientRect())||new DOMRect(0,0,window.innerWidth,window.innerHeight),f=n.offsetParent||document.body,m=f===document.body?new DOMRect(0,-pageYOffset,window.innerWidth,window.innerHeight):f.getBoundingClientRect(),h=getComputedStyle(f),u=getComputedStyle(n);if(i.flip||void 0===i.flip){const e=t=>Math.abs(p[t]-g[t]),i=e(s);n["offset"+t[r].Size]>i&&e(c)>i&&([s,c]=[c,s])}if(n.dataset.placement=`${s}-${d}`,i.cap||void 0===i.cap){const e=(e,i)=>{const o=u["max"+t[e].Size];i-=parseInt(u["margin"+t[e].Start])+parseInt(u["margin"+t[e].End]),("none"===o||i<parseInt(o))&&(n.style["max"+t[e].Size]=i+"px")};e(r,Math.abs(g[s]-p[s])),e(l,g[t[l].size])}Object.assign(a,{[s]:"auto",[c]:(s===t[r].start?m[t[r].end]-p[t[r].start]:p[t[r].end]-m[t[r].start])-parseInt(h["border"+t[r].Start+"Width"])+"px"});const S="end"===d?"end":"start",b="end"===d?"start":"end",x=p[l]-m[l],w=p[t[l].size],z=n["offset"+t[l].Size],y="end"===d?-1:1;Object.assign(a,{[t[l][b]]:"auto",[t[l][S]]:Math.max(y*(g[t[l][S]]-m[t[l][S]]),Math.min("end"===d?m[t[l].size]-x-w:x+("start"!==d?w/2-z/2:0),y*(g[t[l][b]]-m[t[l][S]])-z))-parseInt(h["border"+t[l].Start+"Width"])+"px"})}export default e; |
@@ -1,1 +0,1 @@ | ||
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}({}); | ||
var placement=function(){"use strict";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"}};return function(e,n,i){var o;const a=n.style;Object.assign(a,{position:"absolute",maxWidth:"",maxHeight:""});let[s="bottom",d="center"]=i.placement.split("-");const r=["top","bottom"].includes(s)?"y":"x";let c=s===t[r].start?t[r].end:t[r].start;const l="x"===r?"y":"x",p=e.getBoundingClientRect(),g=(null===(o=function(t){for(;(t=t.parentNode)&&t instanceof Element;){const e=getComputedStyle(t).overflow;if(["auto","scroll"].includes(e))return t}}(n))||void 0===o?void 0:o.getBoundingClientRect())||new DOMRect(0,0,window.innerWidth,window.innerHeight),f=n.offsetParent||document.body,m=f===document.body?new DOMRect(0,-pageYOffset,window.innerWidth,window.innerHeight):f.getBoundingClientRect(),u=getComputedStyle(f),h=getComputedStyle(n);if(i.flip||void 0===i.flip){const e=t=>Math.abs(p[t]-g[t]),i=e(s);n["offset"+t[r].Size]>i&&e(c)>i&&([s,c]=[c,s])}if(n.dataset.placement=`${s}-${d}`,i.cap||void 0===i.cap){const e=(e,i)=>{const o=h["max"+t[e].Size];i-=parseInt(h["margin"+t[e].Start])+parseInt(h["margin"+t[e].End]),("none"===o||i<parseInt(o))&&(n.style["max"+t[e].Size]=i+"px")};e(r,Math.abs(g[s]-p[s])),e(l,g[t[l].size])}Object.assign(a,{[s]:"auto",[c]:(s===t[r].start?m[t[r].end]-p[t[r].start]:p[t[r].end]-m[t[r].start])-parseInt(u["border"+t[r].Start+"Width"])+"px"});const S="end"===d?"end":"start",b="end"===d?"start":"end",w=p[l]-m[l],x=p[t[l].size],z=n["offset"+t[l].Size],y="end"===d?-1:1;Object.assign(a,{[t[l][b]]:"auto",[t[l][S]]:Math.max(y*(g[t[l][S]]-m[t[l][S]]),Math.min("end"===d?m[t[l].size]-w-x:w+("start"!==d?x/2-z/2:0),y*(g[t[l][b]]-m[t[l][S]])-z))-parseInt(u["border"+t[l].Start+"Width"])+"px"})}}(); |
{ | ||
"name": "placement.js", | ||
"version": "1.0.0-beta.3", | ||
"version": "1.0.0-beta.4", | ||
"description": "A tiny library for positioning overlays. Useful for tooltips, popovers etc.", | ||
"main": "./dist/index.cjs.js", | ||
"module": "./dist/index.es.js", | ||
@@ -12,3 +11,4 @@ "unpkg": "./dist/index.js", | ||
"build": "rollup -c", | ||
"build:watch": "rollup -cw" | ||
"build:watch": "rollup -cw", | ||
"release": "release-it" | ||
}, | ||
@@ -31,4 +31,7 @@ "repository": { | ||
], | ||
"sideEffects": false, | ||
"devDependencies": { | ||
"@release-it/keep-a-changelog": "^2.2.2", | ||
"@rollup/plugin-typescript": "^8.1.0", | ||
"release-it": "^14.6.1", | ||
"rollup": "^2.36.2", | ||
@@ -39,3 +42,16 @@ "rollup-plugin-filesize": "^9.1.1", | ||
"typescript": "^4.1.3" | ||
}, | ||
"release-it": { | ||
"github": { | ||
"release": true | ||
}, | ||
"plugins": { | ||
"@release-it/keep-a-changelog": { | ||
"filename": "CHANGELOG.md" | ||
} | ||
}, | ||
"hooks": { | ||
"after:bump": "npm run build" | ||
} | ||
} | ||
} |
# Placement.js | ||
> A tiny library for positioning overlays. Useful for tooltips, popovers etc. | ||
**A tiny library for positioning overlays. Useful for tooltips, popovers etc.** | ||
@@ -27,5 +27,5 @@ ![Size](https://img.shields.io/bundlephobia/minzip/placement.js) | ||
```ts | ||
import { place } from 'placement.js'; | ||
import placement from 'placement.js'; | ||
place( | ||
placement( | ||
anchor: HTMLElement, | ||
@@ -37,22 +37,28 @@ overlay: HTMLElement, | ||
type Options = { | ||
placement?: Placement // defaults to 'bottom' | ||
// The overlay placement relative to the anchor. | ||
// Defaults to 'bottom'. | ||
placement?: | ||
| 'top' | ||
| 'top-start' | ||
| 'top-end' | ||
| 'bottom' | ||
| 'bottom-start' | ||
| 'bottom-end' | ||
| 'right' | ||
| 'right-start' | ||
| 'right-end' | ||
| 'left' | ||
| 'left-start' | ||
| 'left-end', | ||
// Whether or not the overlay can flip to the other side if there's more | ||
// room available. Defaults to true. | ||
flip?: boolean, | ||
// Whether or not the overlay size should be capped to the available space. | ||
// Defaults to true. | ||
cap?: 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 it in action. | ||
## Contributing | ||
@@ -59,0 +65,0 @@ |
171
src/index.ts
@@ -1,5 +0,12 @@ | ||
type Options = { | ||
placement?: Placement | ||
export type Options = { | ||
placement?: Placement, | ||
flip?: boolean, | ||
cap?: boolean, | ||
}; | ||
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 Placement = | ||
@@ -19,8 +26,5 @@ | 'top' | ||
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 Axis = 'x' | 'y'; | ||
export function place( | ||
export default function( | ||
anchor: HTMLElement, | ||
@@ -30,98 +34,87 @@ overlay: HTMLElement, | ||
) { | ||
const placement = options.placement || 'bottom'; | ||
let [side, align] = placement.split('-'); | ||
const mainAxis = ['top', 'bottom'].includes(side) ? 'y' : 'x'; | ||
const altAxis = mainAxis === 'y' ? 'x' : 'y'; | ||
const mainProps = PROPS[mainAxis]; | ||
const altProps = PROPS[altAxis]; | ||
// Reset the position and uncap the maximum size of the popup so that we | ||
// can reliably determine if the popup is "too big" below. | ||
// Reset | ||
const overlayStyle = overlay.style; | ||
overlayStyle.position = 'absolute'; | ||
overlayStyle.maxWidth = overlayStyle.maxHeight = ''; | ||
Object.assign(overlayStyle, { | ||
position: 'absolute', | ||
maxWidth: '', | ||
maxHeight: '' | ||
}); | ||
// 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. | ||
let [side = 'bottom', align = 'center'] = options.placement.split('-'); | ||
const axisSide = ['top', 'bottom'].includes(side) ? 'y' : 'x'; | ||
let oppositeSide = side === PROPS[axisSide].start ? PROPS[axisSide].end : PROPS[axisSide].start; | ||
const axisAlign = axisSide === 'x' ? 'y' : 'x'; | ||
const anchorRect = anchor.getBoundingClientRect(); | ||
const boundRect = scrollParent(overlay)?.getBoundingClientRect() || createRect(0, 0, window.innerWidth, window.innerHeight); | ||
const boundRect = scrollParent(overlay)?.getBoundingClientRect() || new DOMRect(0, 0, window.innerWidth, window.innerHeight); | ||
const offsetParent = overlay.offsetParent || document.body; | ||
const offsetParentRect = offsetParent === document.body ? new DOMRect(0, -pageYOffset, window.innerWidth, window.innerHeight) : offsetParent.getBoundingClientRect(); | ||
const offsetParentComputed = getComputedStyle(offsetParent); | ||
const overlayComputed = getComputedStyle(overlay); | ||
// Constrain the maximum size of the popup along the alignment axis. | ||
overlayStyle['max' + altProps.Size] = boundRect[altProps.size] + 'px'; | ||
// Flip | ||
if (options.flip || typeof options.flip === 'undefined') { | ||
// 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 flip it to the side with more room. | ||
const room = side => Math.abs(anchorRect[side] - boundRect[side]); | ||
const roomThisSide = room(side); | ||
const overlaySize = overlay['offset' + PROPS[axisSide].Size]; | ||
// 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 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 (overlay['offset' + mainProps.Size] > room[side]) { | ||
side = room[mainProps.start] > room[mainProps.end] ? mainProps.start : mainProps.end; | ||
if (overlaySize > roomThisSide && room(oppositeSide) > roomThisSide) { | ||
[side, oppositeSide] = [oppositeSide, side]; | ||
} | ||
} | ||
// Constrain the maximum size of the popup along the main axis. | ||
overlayStyle['max' + mainProps.Size] = room[side] + 'px'; | ||
// Data attribute | ||
overlay.dataset.placement = `${side}-${align}`; | ||
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']); | ||
} | ||
// Cap | ||
if (options.cap || typeof options.cap === 'undefined') { | ||
const cap = (axis: Axis, room: number) => { | ||
const intrinsicMaxSize = overlayComputed['max' + PROPS[axis].Size]; | ||
room -= parseInt(overlayComputed['margin' + PROPS[axis].Start]) + parseInt(overlayComputed['margin' + PROPS[axis].End]); | ||
if (intrinsicMaxSize === 'none' || room < parseInt(intrinsicMaxSize)) { | ||
overlay.style['max' + PROPS[axis].Size] = room + 'px'; | ||
} | ||
}; | ||
const pos = (pos, axis) => { | ||
return Math.max( | ||
boundRect[PROPS[axis].start], | ||
Math.min( | ||
pos, | ||
boundRect[PROPS[axis].end] - overlay['offset' + PROPS[axis].Size] | ||
) | ||
) - (offset ? offset(axis) : 0); | ||
}; | ||
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 | ||
overlayStyle[mainProps.start] = pos(anchorRect[mainProps.end], mainAxis) + 'px'; | ||
overlayStyle[mainProps.end] = 'auto'; | ||
cap(axisSide, Math.abs(boundRect[side] - anchorRect[side])); | ||
cap(axisAlign, boundRect[PROPS[axisAlign].size]); | ||
} | ||
// 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; | ||
} | ||
// Side | ||
Object.assign(overlayStyle, { | ||
[side]: 'auto', | ||
[oppositeSide]: ( | ||
(side === PROPS[axisSide].start | ||
? offsetParentRect[PROPS[axisSide].end] - anchorRect[PROPS[axisSide].start] | ||
: anchorRect[PROPS[axisSide].end] - offsetParentRect[PROPS[axisSide].start]) | ||
- parseInt(offsetParentComputed['border' + PROPS[axisSide].Start + 'Width']) | ||
) + 'px' | ||
}); | ||
overlayStyle[altProps.start] = pos(anchorRect[altProps.start] + offset, altAxis) + 'px'; | ||
overlayStyle[altProps.end] = 'auto'; | ||
} | ||
overlay.dataset.placement = side + (align ? '-' + align : ''); | ||
// Align | ||
const fromAlign = align === 'end' ? 'end' : 'start'; | ||
const oppositeAlign = align === 'end' ? 'start' : 'end'; | ||
const anchorAlign = anchorRect[axisAlign] - offsetParentRect[axisAlign]; | ||
const anchorSize = anchorRect[PROPS[axisAlign].size]; | ||
const overlaySize = overlay['offset' + PROPS[axisAlign].Size]; | ||
const factor = align === 'end' ? -1 : 1; | ||
Object.assign(overlayStyle, { | ||
[PROPS[axisAlign][oppositeAlign]]: 'auto', | ||
[PROPS[axisAlign][fromAlign]]: ( | ||
Math.max( | ||
factor * (boundRect[PROPS[axisAlign][fromAlign]] - offsetParentRect[PROPS[axisAlign][fromAlign]]), | ||
Math.min( | ||
align === 'end' | ||
? offsetParentRect[PROPS[axisAlign].size] - anchorAlign - anchorSize | ||
: anchorAlign + (align !== 'start' ? anchorSize / 2 - overlaySize / 2 : 0), | ||
factor * (boundRect[PROPS[axisAlign][oppositeAlign]] - offsetParentRect[PROPS[axisAlign][fromAlign]]) - overlaySize | ||
) | ||
) | ||
- parseInt(offsetParentComputed['border' + PROPS[axisAlign].Start + 'Width']) | ||
) + 'px' | ||
}); | ||
} | ||
function createRect(top, left, width, height): ClientRect { | ||
return { | ||
top, | ||
left, | ||
right: width, | ||
bottom: height, | ||
width, | ||
height | ||
}; | ||
} | ||
function scrollParent(node) { | ||
@@ -128,0 +121,0 @@ while ((node = node.parentNode) && node instanceof Element) { |
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
69
14953
8
8
135