curls
Version:
💪 Responsive, expressive UI primitives for React written with Style Hooks and Emotion
600 lines (514 loc) • 14.8 kB
JavaScript
'use strict'
exports.__esModule = true
exports.setPlacementStyle = void 0
const windowWidth = () =>
window.innerWidth || document.documentElement.clientWidth
const windowHeight = () =>
window.innerHeight || document.documentElement.clientHeight
const centerXPos = (triggerRect, popoverRect) => ({
left: 'auto',
right:
windowWidth() -
triggerRect.right -
(popoverRect.width - triggerRect.width) / 2,
})
const centerYPos = (triggerRect, popoverRect) => ({
top: 'auto',
bottom:
windowHeight() -
triggerRect.bottom -
(popoverRect.height - triggerRect.height) / 2,
})
const startXInnerPos = triggerRect => ({
left: triggerRect.left,
right: 'auto',
})
const startXOuterPos = triggerRect => ({
left: 'auto',
right: windowWidth() - triggerRect.left,
})
const endXOuterPos = triggerRect => ({
left: triggerRect.right,
right: 'auto',
})
const endXInnerPos = triggerRect => {
return {
left: 'auto',
right: windowWidth() - triggerRect.right,
}
}
const startYInnerPos = triggerRect => ({
top: triggerRect.top,
bottom: 'auto',
})
const startYOuterPos = triggerRect => ({
top: 'auto',
bottom: windowHeight() - triggerRect.top,
})
const endYInnerPos = triggerRect => ({
top: 'auto',
bottom: windowHeight() - triggerRect.bottom,
})
const endYOuterPos = triggerRect => ({
top: triggerRect.bottom,
bottom: 'auto',
})
const centerXRect = (triggerRect, popoverRect) => {
const rect = {
right: popoverRect.width / 2 - triggerRect.width / 2 + triggerRect.right,
}
rect.left = rect.right - popoverRect.width
return rect
}
const startXOuterRect = (triggerRect, popoverRect) => {
const rect = {
right: triggerRect.left,
}
rect.left = rect.right - popoverRect.width
return rect
}
const endXOuterRect = (triggerRect, popoverRect) => {
const rect = {
left: triggerRect.right,
}
rect.right = rect.left + popoverRect.width
return rect
}
const centerYRect = (triggerRect, popoverRect) => {
const rect = {
bottom:
popoverRect.height / 2 - triggerRect.height / 2 + triggerRect.bottom,
}
rect.top = rect.bottom - popoverRect.height
return rect
}
const startYOuterRect = (triggerRect, popoverRect) => {
const rect = {
bottom: triggerRect.top,
}
rect.top = rect.bottom - popoverRect.height
return rect
}
const endYOuterRect = (triggerRect, popoverRect) => {
const rect = {
top: triggerRect.bottom,
}
rect.bottom = rect.top + popoverRect.height
return rect
}
const startXInnerRect = (triggerRect, popoverRect) => {
const rect = {
left: triggerRect.left,
}
rect.right = rect.left + popoverRect.width
return rect
}
const endXInnerRect = (triggerRect, popoverRect) => {
const rect = {
right: triggerRect.right,
}
rect.left = rect.right - popoverRect.width
return rect
}
const startYInnerRect = (triggerRect, popoverRect) => {
const rect = {
top: triggerRect.top,
}
rect.bottom = rect.top + popoverRect.height
return rect
}
const endYInnerRect = (triggerRect, popoverRect) => {
const rect = {
bottom: triggerRect.bottom,
}
rect.top = rect.bottom - popoverRect.height
return rect
}
const assignY = (a, b) => {
a.top = b.top
a.bottom = b.bottom
return a
}
const calcIdealRect = (placement, triggerRect, popoverRect) => {
switch (placement) {
case 'top':
return assignY(
centerXRect(triggerRect, popoverRect),
startYOuterRect(triggerRect, popoverRect)
)
case 'topleft':
return assignY(
startXInnerRect(triggerRect, popoverRect),
startYOuterRect(triggerRect, popoverRect)
)
case 'topright':
return assignY(
endXInnerRect(triggerRect, popoverRect),
startYOuterRect(triggerRect, popoverRect)
)
case 'right':
return assignY(
endXOuterRect(triggerRect, popoverRect),
centerYRect(triggerRect, popoverRect)
)
case 'righttop':
return assignY(
endXOuterRect(triggerRect, popoverRect),
startYInnerRect(triggerRect, popoverRect)
)
case 'rightbottom':
return assignY(
endXOuterRect(triggerRect, popoverRect),
endYInnerRect(triggerRect, popoverRect)
)
case 'bottom':
return assignY(
centerXRect(triggerRect, popoverRect),
endYOuterRect(triggerRect, popoverRect)
)
case 'bottomleft':
return assignY(
startXInnerRect(triggerRect, popoverRect),
endYOuterRect(triggerRect, popoverRect)
)
case 'bottomright':
return assignY(
endXInnerRect(triggerRect, popoverRect),
endYOuterRect(triggerRect, popoverRect)
)
case 'left':
return assignY(
startXOuterRect(triggerRect, popoverRect),
centerYRect(triggerRect, popoverRect)
)
case 'lefttop':
return assignY(
startXOuterRect(triggerRect, popoverRect),
startYInnerRect(triggerRect, popoverRect)
)
case 'leftbottom':
return assignY(
startXOuterRect(triggerRect, popoverRect),
endYInnerRect(triggerRect, popoverRect)
)
case 'innerleft':
return assignY(
startXInnerRect(triggerRect, popoverRect),
centerYRect(triggerRect, popoverRect)
)
case 'innerright':
return assignY(
endXInnerRect(triggerRect, popoverRect),
centerYRect(triggerRect, popoverRect)
)
case 'innertop':
return assignY(
centerXRect(triggerRect, popoverRect),
startYInnerRect(triggerRect, popoverRect)
)
case 'innertopleft':
return assignY(
startXInnerRect(triggerRect, popoverRect),
startYInnerRect(triggerRect, popoverRect)
)
case 'innertopright':
return assignY(
endXInnerRect(triggerRect, popoverRect),
startYInnerRect(triggerRect, popoverRect)
)
case 'innerbottom':
return assignY(
centerXRect(triggerRect, popoverRect),
endYInnerRect(triggerRect, popoverRect)
)
case 'innerbottomleft':
return assignY(
startXInnerRect(triggerRect, popoverRect),
endYInnerRect(triggerRect, popoverRect)
)
case 'innerbottomright':
return assignY(
endXInnerRect(triggerRect, popoverRect),
endYInnerRect(triggerRect, popoverRect)
)
default:
return assignY(
centerXRect(triggerRect, popoverRect),
centerYRect(triggerRect, popoverRect)
)
}
}
const contain = placement => (triggerRect, popoverRect, containPolicy) => {
const flip = containPolicy === 'flip',
flipX = containPolicy === 'flipX',
flipY = containPolicy === 'flipY'
if (flip || flipX || flipY) {
const idealRect = calcIdealRect(placement, triggerRect, popoverRect) // center checks
if (!placement) {
if (flip || flipY) {
if (idealRect.bottom > windowHeight()) {
placement = 'top'
} else if (idealRect.top < 0) {
placement = 'bottom'
}
}
if (!placement && (flip || flipX)) {
if (idealRect.left < 0) {
placement = 'right'
} else if (idealRect.right > windowWidth()) {
placement = 'left'
}
}
} // order of these indexes matters... must be before placement === top check
const leftIdx = placement.indexOf('left'),
topIdx = placement.indexOf('top')
if (placement === 'top' || placement === 'bottom') {
if (flip || flipX) {
// handles center X-axis case
if (idealRect.left < 0) {
placement += 'left'
} else if (idealRect.right > windowWidth()) {
placement += 'right'
}
}
}
if (flip || flipX) {
// left checks
if (
(leftIdx === 0 && idealRect.left < 0) ||
(leftIdx > 0 && idealRect.right > windowWidth())
) {
placement = placement.replace('left', 'right')
} else {
const rightIdx = placement.indexOf('right') // right checks
if (
(rightIdx === 0 && idealRect.right > windowWidth()) ||
(rightIdx > 0 && idealRect.left < 0)
) {
placement = placement.replace('right', 'left')
}
}
} // handles center Y-axis case
if (flip || flipY) {
if (placement === 'left' || placement === 'right') {
if (idealRect.top < 0) {
placement += 'top'
} else if (idealRect.bottom > windowHeight()) {
placement += 'bottom'
}
} else if (placement === 'innerleft' || placement === 'innerright') {
if (idealRect.top < 0) {
placement = placement.replace('inner', 'innertop')
} else if (idealRect.bottom > windowHeight()) {
placement = placement.replace('inner', 'innerbottom')
}
}
}
if (flip || flipY) {
// top checks
if (
(topIdx === 0 && idealRect.top < 0) ||
(topIdx > 0 && idealRect.bottom > windowHeight())
) {
placement = placement.replace('top', 'bottom')
} else {
const bottomIdx = placement.indexOf('bottom') // bottom checks
if (
(bottomIdx === 0 && idealRect.bottom > windowHeight()) ||
(bottomIdx > 0 && idealRect.top < 0)
) {
placement = placement.replace('bottom', 'top')
}
}
}
} else if (typeof containPolicy === 'function') {
placement = contain(triggerRect, popoverRect)
if (typeof placement !== 'string') return placement
}
return calcPlacement(placement, triggerRect, popoverRect)
}
const calcPlacement = (placement, triggerRect, popoverRect) => {
switch (placement) {
case 'top':
return {
placement: 'top',
style: assignY(
centerXPos(triggerRect, popoverRect),
startYOuterPos(triggerRect)
),
}
case 'topleft':
return {
placement: 'topLeft',
style: assignY(
startXInnerPos(triggerRect),
startYOuterPos(triggerRect)
),
}
case 'topright':
return {
placement: 'topRight',
style: assignY(endXInnerPos(triggerRect), startYOuterPos(triggerRect)),
}
case 'right':
return {
placement: 'right',
style: assignY(
endXOuterPos(triggerRect),
centerYPos(triggerRect, popoverRect)
),
}
case 'righttop':
return {
placement: 'rightTop',
style: assignY(endXOuterPos(triggerRect), startYInnerPos(triggerRect)),
}
case 'rightbottom':
return {
placement: 'rightBottom',
style: assignY(endXOuterPos(triggerRect), endYInnerPos(triggerRect)),
}
case 'bottom':
return {
placement: 'bottom',
style: assignY(
centerXPos(triggerRect, popoverRect),
endYOuterPos(triggerRect)
),
}
case 'bottomleft':
return {
placement: 'bottomLeft',
style: assignY(startXInnerPos(triggerRect), endYOuterPos(triggerRect)),
}
case 'bottomright':
return {
placement: 'bottomRight',
style: assignY(endXInnerPos(triggerRect), endYOuterPos(triggerRect)),
}
case 'left':
return {
placement: 'left',
style: assignY(
startXOuterPos(triggerRect),
centerYPos(triggerRect, popoverRect)
),
}
case 'lefttop':
return {
placement: 'leftTop',
style: assignY(
startXOuterPos(triggerRect),
startYInnerPos(triggerRect)
),
}
case 'leftbottom':
return {
placement: 'leftBottom',
style: assignY(startXOuterPos(triggerRect), endYInnerPos(triggerRect)),
}
case 'innertop':
return {
placement: 'innerTop',
style: assignY(
centerXPos(triggerRect, popoverRect),
startYInnerPos(triggerRect)
),
}
case 'innertopleft':
return {
placement: 'innerTopLeft',
style: assignY(
startXInnerPos(triggerRect),
startYInnerPos(triggerRect)
),
}
case 'innertopright':
return {
placement: 'innerTopRight',
style: assignY(endXInnerPos(triggerRect), startYInnerPos(triggerRect)),
}
case 'innerright':
return {
placement: 'innerRight',
style: assignY(
endXInnerPos(triggerRect),
centerYPos(triggerRect, popoverRect)
),
}
case 'innerbottom':
return {
placement: 'innerBottom',
style: assignY(
centerXPos(triggerRect, popoverRect),
endYInnerPos(triggerRect)
),
}
case 'innerbottomright':
return {
placement: 'innerBottomRight',
style: assignY(endXInnerPos(triggerRect), endYInnerPos(triggerRect)),
}
case 'innerbottomleft':
return {
placement: 'innerBottomLeft',
style: assignY(startXInnerPos(triggerRect), endYInnerPos(triggerRect)),
}
case 'innerleft':
return {
placement: 'innerLeft',
style: assignY(
startXInnerPos(triggerRect),
centerYPos(triggerRect, popoverRect)
),
}
default:
return {
placement: 'center',
style: assignY(
centerXPos(triggerRect, popoverRect),
centerYPos(triggerRect, popoverRect)
),
}
}
}
const defaultPlacements = /outer|center/g
const setPlacementStyle = (
requestedPlacement,
trigger,
popover,
containPolicy
) => {
if (!trigger || !popover) return requestedPlacement
let result = {},
placement = requestedPlacement
let triggerRect = trigger.getBoundingClientRect(),
popoverRect = popover.getBoundingClientRect()
popoverRect.width = popover.offsetWidth
popoverRect.height = popover.offsetHeight
if (typeof placement === 'function') {
result = requestedPlacement(triggerRect, popoverRect, containPolicy)
if (typeof result === 'string') {
placement = result
} else {
if (process.env.NODE_ENV !== 'production') {
if (
typeof result.placement !== 'string' ||
typeof result.style !== 'object'
) {
throw new Error(
`[Popover] Placement functions must return an object of type:\n` +
`\n{\n placement: string,\n style: {}\n}\n`
)
}
}
}
}
if (typeof placement === 'string') {
const fn = contain(placement.toLowerCase().replace(defaultPlacements, ''))
result = fn(triggerRect, popoverRect, containPolicy)
}
result.requestedPlacement = requestedPlacement
return result
}
exports.setPlacementStyle = setPlacementStyle