mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 10:06:17 +08:00
62d8c0c7cb
Close [BS-2743](https://linear.app/affine-design/issue/BS-2743/slash-menu插件化:调整文件夹结构) This PR move slash menu from `affine-root-block` to `widget-affine-slash-menu`, and make it as a `WidgetViewExtension`
140 lines
3.7 KiB
TypeScript
140 lines
3.7 KiB
TypeScript
import clamp from 'lodash-es/clamp';
|
|
|
|
type CollisionBox = {
|
|
/**
|
|
* The point that the objRect is positioned to.
|
|
*/
|
|
positioningPoint: { x: number; y: number };
|
|
/**
|
|
* The boundary rect of the obj that is being positioned.
|
|
*/
|
|
objRect?: { height: number; width: number };
|
|
/**
|
|
* The boundary rect of the container that the obj is in.
|
|
*/
|
|
boundaryRect?: DOMRect;
|
|
offsetX?: number;
|
|
offsetY?: number;
|
|
edgeGap?: number;
|
|
};
|
|
|
|
function calcSafeCoordinate({
|
|
positioningPoint,
|
|
objRect = { width: 0, height: 0 },
|
|
boundaryRect = document.body.getBoundingClientRect(),
|
|
offsetX = 0,
|
|
offsetY = 0,
|
|
edgeGap = 20,
|
|
}: CollisionBox) {
|
|
const safeX = clamp(
|
|
positioningPoint.x + offsetX,
|
|
edgeGap,
|
|
boundaryRect.width - objRect.width - edgeGap
|
|
);
|
|
const y = positioningPoint.y + offsetY;
|
|
// Not use clamp for y coordinate to avoid the quick bar always showing after scrolling
|
|
// const safeY = clamp(
|
|
// positioningPoint.y + offsetY,
|
|
// edgeGap,
|
|
// boundaryRect.height - objRect.height - edgeGap
|
|
// );
|
|
return {
|
|
x: safeX,
|
|
y,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Used to compare the space available
|
|
* at the top and bottom of an element within a container.
|
|
*
|
|
* Please give preference to {@link getPopperPosition}
|
|
*/
|
|
function compareTopAndBottomSpace(
|
|
obj: { getBoundingClientRect: () => DOMRect },
|
|
container = document.body,
|
|
gap = 20
|
|
) {
|
|
const objRect = obj.getBoundingClientRect();
|
|
const spaceRect = container.getBoundingClientRect();
|
|
const topSpace = objRect.top - spaceRect.top;
|
|
const bottomSpace = spaceRect.bottom - objRect.bottom;
|
|
const topOrBottom: 'top' | 'bottom' =
|
|
topSpace > bottomSpace ? 'top' : 'bottom';
|
|
return {
|
|
placement: topOrBottom,
|
|
// the height is the available space.
|
|
height: (topOrBottom === 'top' ? topSpace : bottomSpace) - gap,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the position of the popper element with flip.
|
|
* return null if the reference rect is all zero.
|
|
*/
|
|
export function getPopperPosition(
|
|
popper: {
|
|
getBoundingClientRect: () => DOMRect;
|
|
},
|
|
reference: {
|
|
getBoundingClientRect: () => DOMRect;
|
|
},
|
|
{ gap = 12, offsetY = 5 }: { gap?: number; offsetY?: number } = {}
|
|
) {
|
|
if (!popper) {
|
|
// foolproof, someone may use element with non-null assertion
|
|
console.warn(
|
|
'The popper element is not exist. Popper position maybe incorrect'
|
|
);
|
|
}
|
|
const { placement, height } = compareTopAndBottomSpace(
|
|
reference,
|
|
document.body,
|
|
gap + offsetY
|
|
);
|
|
|
|
const referenceRect = reference.getBoundingClientRect();
|
|
if (
|
|
referenceRect.x === 0 &&
|
|
referenceRect.y === 0 &&
|
|
referenceRect.width === 0 &&
|
|
referenceRect.height === 0
|
|
)
|
|
return null;
|
|
|
|
const positioningPoint = {
|
|
x: referenceRect.x,
|
|
y: referenceRect.y + (placement === 'bottom' ? referenceRect.height : 0),
|
|
};
|
|
|
|
// TODO maybe use the editor container as the boundary rect to avoid the format bar being covered by other elements
|
|
const boundaryRect = document.body.getBoundingClientRect();
|
|
// Note: the popperRect.height maybe incorrect
|
|
// because we are calculated its correct height
|
|
const popperRect = popper?.getBoundingClientRect();
|
|
|
|
const safeCoordinate = calcSafeCoordinate({
|
|
positioningPoint,
|
|
objRect: popperRect,
|
|
boundaryRect,
|
|
offsetY: placement === 'bottom' ? offsetY : -offsetY,
|
|
});
|
|
|
|
return {
|
|
placement,
|
|
/**
|
|
* The height is the available space height.
|
|
*
|
|
* Note: it's a max height, not the real height,
|
|
* because sometimes the popper's height is smaller than the available space.
|
|
*/
|
|
height,
|
|
x: `${safeCoordinate.x}px`,
|
|
y:
|
|
placement === 'bottom'
|
|
? `${safeCoordinate.y}px`
|
|
: // We need to use `calc(-100%)` since the height of popper maybe incorrect
|
|
`calc(${safeCoordinate.y}px - 100%)`,
|
|
};
|
|
}
|