431 lines
18 KiB
JavaScript
431 lines
18 KiB
JavaScript
|
const oppositionPositions = {
|
||
|
top: 'bottom',
|
||
|
bottom: 'top',
|
||
|
left: 'right',
|
||
|
right: 'left'
|
||
|
};
|
||
|
const oppositeAligns = {
|
||
|
start: 'end',
|
||
|
center: 'center',
|
||
|
end: 'start'
|
||
|
};
|
||
|
const propToCompare = {
|
||
|
top: 'height',
|
||
|
bottom: 'height',
|
||
|
left: 'width',
|
||
|
right: 'width'
|
||
|
};
|
||
|
const transformOrigins = {
|
||
|
'bottom-start': 'top left',
|
||
|
bottom: 'top center',
|
||
|
'bottom-end': 'top right',
|
||
|
'top-start': 'bottom left',
|
||
|
top: 'bottom center',
|
||
|
'top-end': 'bottom right',
|
||
|
'right-start': 'top left',
|
||
|
right: 'center left',
|
||
|
'right-end': 'bottom left',
|
||
|
'left-start': 'top right',
|
||
|
left: 'center right',
|
||
|
'left-end': 'bottom right'
|
||
|
};
|
||
|
const overlapTransformOrigin = {
|
||
|
'bottom-start': 'bottom left',
|
||
|
bottom: 'bottom center',
|
||
|
'bottom-end': 'bottom right',
|
||
|
'top-start': 'top left',
|
||
|
top: 'top center',
|
||
|
'top-end': 'top right',
|
||
|
'right-start': 'top right',
|
||
|
right: 'center right',
|
||
|
'right-end': 'bottom right',
|
||
|
'left-start': 'top left',
|
||
|
left: 'center left',
|
||
|
'left-end': 'bottom left'
|
||
|
};
|
||
|
const oppositeAlignCssPositionProps = {
|
||
|
'bottom-start': 'right',
|
||
|
'bottom-end': 'left',
|
||
|
'top-start': 'right',
|
||
|
'top-end': 'left',
|
||
|
'right-start': 'bottom',
|
||
|
'right-end': 'top',
|
||
|
'left-start': 'bottom',
|
||
|
'left-end': 'top'
|
||
|
};
|
||
|
const keepOffsetDirection = {
|
||
|
top: true,
|
||
|
bottom: false,
|
||
|
left: true,
|
||
|
right: false // left--
|
||
|
};
|
||
|
const cssPositionToOppositeAlign = {
|
||
|
top: 'end',
|
||
|
bottom: 'start',
|
||
|
left: 'end',
|
||
|
right: 'start'
|
||
|
};
|
||
|
export function getPlacementAndOffsetOfFollower(placement, targetRect, followerRect, shift, flip, overlap) {
|
||
|
if (!flip || overlap) {
|
||
|
return { placement: placement, top: 0, left: 0 };
|
||
|
}
|
||
|
const [position, align] = placement.split('-');
|
||
|
let properAlign = align !== null && align !== void 0 ? align : 'center';
|
||
|
let properOffset = {
|
||
|
top: 0,
|
||
|
left: 0
|
||
|
};
|
||
|
const deriveOffset = (oppositeAlignCssSizeProp, alignCssPositionProp, offsetVertically) => {
|
||
|
let left = 0;
|
||
|
let top = 0;
|
||
|
const diff = followerRect[oppositeAlignCssSizeProp] -
|
||
|
targetRect[alignCssPositionProp] -
|
||
|
targetRect[oppositeAlignCssSizeProp];
|
||
|
if (diff > 0 && shift) {
|
||
|
if (offsetVertically) {
|
||
|
// screen border
|
||
|
// |-----------------------------------------|
|
||
|
// | | f | |
|
||
|
// | | o | |
|
||
|
// | | l | |
|
||
|
// | | l |---- |
|
||
|
// | | o |tar | |
|
||
|
// | | w |get | |
|
||
|
// | | e | | |
|
||
|
// | | r |---- |
|
||
|
// | ---- |
|
||
|
// |-----------------------------------------|
|
||
|
top = keepOffsetDirection[alignCssPositionProp] ? diff : -diff;
|
||
|
}
|
||
|
else {
|
||
|
// screen border
|
||
|
// |----------------------------------------|
|
||
|
// | |
|
||
|
// | ---------- |
|
||
|
// | | target | |
|
||
|
// | ----------------------------------
|
||
|
// | | follower |
|
||
|
// | ----------------------------------
|
||
|
// | |
|
||
|
// |----------------------------------------|
|
||
|
left = keepOffsetDirection[alignCssPositionProp] ? diff : -diff;
|
||
|
}
|
||
|
}
|
||
|
return {
|
||
|
left,
|
||
|
top
|
||
|
};
|
||
|
};
|
||
|
const offsetVertically = position === 'left' || position === 'right';
|
||
|
// choose proper placement for non-center align
|
||
|
if (properAlign !== 'center') {
|
||
|
const oppositeAlignCssPositionProp = oppositeAlignCssPositionProps[placement];
|
||
|
const currentAlignCssPositionProp = oppositionPositions[oppositeAlignCssPositionProp];
|
||
|
const oppositeAlignCssSizeProp = propToCompare[oppositeAlignCssPositionProp];
|
||
|
// if follower rect is larger than target rect in align direction
|
||
|
// ----------[ target ]---------|
|
||
|
// ----------[ follower ]
|
||
|
if (followerRect[oppositeAlignCssSizeProp] >
|
||
|
targetRect[oppositeAlignCssSizeProp]) {
|
||
|
if (
|
||
|
// current space is not enough
|
||
|
// ----------[ target ]---------|
|
||
|
// -------[ follower ]
|
||
|
targetRect[oppositeAlignCssPositionProp] +
|
||
|
targetRect[oppositeAlignCssSizeProp] <
|
||
|
followerRect[oppositeAlignCssSizeProp]) {
|
||
|
const followerOverTargetSize = (followerRect[oppositeAlignCssSizeProp] -
|
||
|
targetRect[oppositeAlignCssSizeProp]) /
|
||
|
2;
|
||
|
if (targetRect[oppositeAlignCssPositionProp] < followerOverTargetSize ||
|
||
|
targetRect[currentAlignCssPositionProp] < followerOverTargetSize) {
|
||
|
// opposite align has larger space
|
||
|
// -------[ target ]-----------|
|
||
|
// -------[ follower ]-|
|
||
|
if (targetRect[oppositeAlignCssPositionProp] <
|
||
|
targetRect[currentAlignCssPositionProp]) {
|
||
|
properAlign = oppositeAligns[align];
|
||
|
properOffset = deriveOffset(oppositeAlignCssSizeProp, currentAlignCssPositionProp, offsetVertically);
|
||
|
}
|
||
|
else {
|
||
|
// ----------------[ target ]----|
|
||
|
// --------[ follower ]----|
|
||
|
properOffset = deriveOffset(oppositeAlignCssSizeProp, oppositeAlignCssPositionProp, offsetVertically);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// 'center' align is better
|
||
|
// ------------[ target ]--------|
|
||
|
// -------[ follower ]--|
|
||
|
properAlign = 'center';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (followerRect[oppositeAlignCssSizeProp] <
|
||
|
targetRect[oppositeAlignCssSizeProp]) {
|
||
|
// TODO: maybe center is better
|
||
|
if (targetRect[currentAlignCssPositionProp] < 0 &&
|
||
|
// opposite align has larger space
|
||
|
// ------------[ target ]
|
||
|
// ----------------[follower]
|
||
|
targetRect[oppositeAlignCssPositionProp] >
|
||
|
targetRect[currentAlignCssPositionProp]) {
|
||
|
properAlign = oppositeAligns[align];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
const possibleAlternativeAlignCssPositionProp1 = position === 'bottom' || position === 'top' ? 'left' : 'top';
|
||
|
const possibleAlternativeAlignCssPositionProp2 = oppositionPositions[possibleAlternativeAlignCssPositionProp1];
|
||
|
const alternativeAlignCssSizeProp = propToCompare[possibleAlternativeAlignCssPositionProp1];
|
||
|
const followerOverTargetSize = (followerRect[alternativeAlignCssSizeProp] -
|
||
|
targetRect[alternativeAlignCssSizeProp]) /
|
||
|
2;
|
||
|
if (
|
||
|
// center is not enough
|
||
|
// ----------- [ target ]--|
|
||
|
// -------[ follower ]
|
||
|
targetRect[possibleAlternativeAlignCssPositionProp1] <
|
||
|
followerOverTargetSize ||
|
||
|
targetRect[possibleAlternativeAlignCssPositionProp2] <
|
||
|
followerOverTargetSize) {
|
||
|
// alternative 2 position's space is larger
|
||
|
if (targetRect[possibleAlternativeAlignCssPositionProp1] >
|
||
|
targetRect[possibleAlternativeAlignCssPositionProp2]) {
|
||
|
properAlign =
|
||
|
cssPositionToOppositeAlign[possibleAlternativeAlignCssPositionProp1];
|
||
|
properOffset = deriveOffset(alternativeAlignCssSizeProp, possibleAlternativeAlignCssPositionProp1, offsetVertically);
|
||
|
}
|
||
|
else {
|
||
|
// alternative 1 position's space is larger
|
||
|
properAlign =
|
||
|
cssPositionToOppositeAlign[possibleAlternativeAlignCssPositionProp2];
|
||
|
properOffset = deriveOffset(alternativeAlignCssSizeProp, possibleAlternativeAlignCssPositionProp2, offsetVertically);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
let properPosition = position;
|
||
|
if (
|
||
|
// space is not enough
|
||
|
targetRect[position] < followerRect[propToCompare[position]] &&
|
||
|
// opposite position's space is larger
|
||
|
targetRect[position] < targetRect[oppositionPositions[position]]) {
|
||
|
properPosition = oppositionPositions[position];
|
||
|
}
|
||
|
return {
|
||
|
placement: properAlign !== 'center'
|
||
|
? `${properPosition}-${properAlign}`
|
||
|
: properPosition,
|
||
|
left: properOffset.left,
|
||
|
top: properOffset.top
|
||
|
};
|
||
|
}
|
||
|
export function getProperTransformOrigin(placement, overlap) {
|
||
|
if (overlap)
|
||
|
return overlapTransformOrigin[placement];
|
||
|
return transformOrigins[placement];
|
||
|
}
|
||
|
// ------------
|
||
|
// | offset |
|
||
|
// | |
|
||
|
// | [target] |
|
||
|
// | |
|
||
|
// ------------
|
||
|
// TODO: refactor it to remove dup logic
|
||
|
export function getOffset(placement, offsetRect, targetRect, offsetTopToStandardPlacement, offsetLeftToStandardPlacement, overlap) {
|
||
|
if (overlap) {
|
||
|
switch (placement) {
|
||
|
case 'bottom-start':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
|
||
|
transform: 'translateY(-100%)'
|
||
|
};
|
||
|
case 'bottom-end':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
|
||
|
transform: 'translateX(-100%) translateY(-100%)'
|
||
|
};
|
||
|
case 'top-start':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
|
||
|
transform: ''
|
||
|
};
|
||
|
case 'top-end':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
|
||
|
transform: 'translateX(-100%)'
|
||
|
};
|
||
|
case 'right-start':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
|
||
|
transform: 'translateX(-100%)'
|
||
|
};
|
||
|
case 'right-end':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
|
||
|
transform: 'translateX(-100%) translateY(-100%)'
|
||
|
};
|
||
|
case 'left-start':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
|
||
|
transform: ''
|
||
|
};
|
||
|
case 'left-end':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
|
||
|
transform: 'translateY(-100%)'
|
||
|
};
|
||
|
case 'top':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width / 2)}px`,
|
||
|
transform: 'translateX(-50%)'
|
||
|
};
|
||
|
case 'right':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height / 2)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
|
||
|
transform: 'translateX(-100%) translateY(-50%)'
|
||
|
};
|
||
|
case 'left':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height / 2)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
|
||
|
transform: 'translateY(-50%)'
|
||
|
};
|
||
|
case 'bottom':
|
||
|
default:
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width / 2)}px`,
|
||
|
transform: 'translateX(-50%) translateY(-100%)'
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
switch (placement) {
|
||
|
case 'bottom-start':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top -
|
||
|
offsetRect.top +
|
||
|
targetRect.height +
|
||
|
offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
|
||
|
transform: ''
|
||
|
};
|
||
|
case 'bottom-end':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top -
|
||
|
offsetRect.top +
|
||
|
targetRect.height +
|
||
|
offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left -
|
||
|
offsetRect.left +
|
||
|
targetRect.width +
|
||
|
offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateX(-100%)'
|
||
|
};
|
||
|
case 'top-start':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateY(-100%)'
|
||
|
};
|
||
|
case 'top-end':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left -
|
||
|
offsetRect.left +
|
||
|
targetRect.width +
|
||
|
offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateX(-100%) translateY(-100%)'
|
||
|
};
|
||
|
case 'right-start':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left -
|
||
|
offsetRect.left +
|
||
|
targetRect.width +
|
||
|
offsetLeftToStandardPlacement)}px`,
|
||
|
transform: ''
|
||
|
};
|
||
|
case 'right-end':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top -
|
||
|
offsetRect.top +
|
||
|
targetRect.height +
|
||
|
offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left -
|
||
|
offsetRect.left +
|
||
|
targetRect.width +
|
||
|
offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateY(-100%)'
|
||
|
};
|
||
|
case 'left-start':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateX(-100%)'
|
||
|
};
|
||
|
case 'left-end':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top -
|
||
|
offsetRect.top +
|
||
|
targetRect.height +
|
||
|
offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateX(-100%) translateY(-100%)'
|
||
|
};
|
||
|
case 'top':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left -
|
||
|
offsetRect.left +
|
||
|
targetRect.width / 2 +
|
||
|
offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateY(-100%) translateX(-50%)'
|
||
|
};
|
||
|
case 'right':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top -
|
||
|
offsetRect.top +
|
||
|
targetRect.height / 2 +
|
||
|
offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left -
|
||
|
offsetRect.left +
|
||
|
targetRect.width +
|
||
|
offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateY(-50%)'
|
||
|
};
|
||
|
case 'left':
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top -
|
||
|
offsetRect.top +
|
||
|
targetRect.height / 2 +
|
||
|
offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateY(-50%) translateX(-100%)'
|
||
|
};
|
||
|
case 'bottom':
|
||
|
default:
|
||
|
return {
|
||
|
top: `${Math.round(targetRect.top -
|
||
|
offsetRect.top +
|
||
|
targetRect.height +
|
||
|
offsetTopToStandardPlacement)}px`,
|
||
|
left: `${Math.round(targetRect.left -
|
||
|
offsetRect.left +
|
||
|
targetRect.width / 2 +
|
||
|
offsetLeftToStandardPlacement)}px`,
|
||
|
transform: 'translateX(-50%)'
|
||
|
};
|
||
|
}
|
||
|
}
|