calckey/packages/client/src/scripts/popup-position.ts

175 lines
4.4 KiB
TypeScript
Raw Normal View History

2023-01-13 04:40:33 +00:00
import { Ref } from "vue";
export function calcPopupPosition(
el: HTMLElement,
props: {
anchorElement: HTMLElement | null;
innerMargin: number;
direction: "top" | "bottom" | "left" | "right";
align: "top" | "bottom" | "left" | "right" | "center";
alignOffset?: number;
x?: number;
y?: number;
},
): { top: number; left: number; transformOrigin: string } {
2022-07-17 12:06:33 +00:00
const contentWidth = el.offsetWidth;
const contentHeight = el.offsetHeight;
let rect: DOMRect;
if (props.anchorElement) {
rect = props.anchorElement.getBoundingClientRect();
}
const calcPosWhenTop = () => {
let left: number;
let top: number;
if (props.anchorElement) {
2023-01-13 04:40:33 +00:00
left =
rect.left + window.pageXOffset + props.anchorElement.offsetWidth / 2;
top = rect.top + window.pageYOffset - contentHeight - props.innerMargin;
2022-07-17 12:06:33 +00:00
} else {
left = props.x;
2023-01-13 04:40:33 +00:00
top = props.y - contentHeight - props.innerMargin;
2022-07-17 12:06:33 +00:00
}
2023-01-13 04:40:33 +00:00
left -= el.offsetWidth / 2;
2022-07-17 12:06:33 +00:00
if (left + contentWidth - window.pageXOffset > window.innerWidth) {
left = window.innerWidth - contentWidth + window.pageXOffset - 1;
}
return [left, top];
};
const calcPosWhenBottom = () => {
let left: number;
let top: number;
if (props.anchorElement) {
2023-01-13 04:40:33 +00:00
left =
rect.left + window.pageXOffset + props.anchorElement.offsetWidth / 2;
top =
rect.top +
window.pageYOffset +
props.anchorElement.offsetHeight +
props.innerMargin;
2022-07-17 12:06:33 +00:00
} else {
left = props.x;
2023-01-13 04:40:33 +00:00
top = props.y + props.innerMargin;
2022-07-17 12:06:33 +00:00
}
2023-01-13 04:40:33 +00:00
left -= el.offsetWidth / 2;
2022-07-17 12:06:33 +00:00
if (left + contentWidth - window.pageXOffset > window.innerWidth) {
left = window.innerWidth - contentWidth + window.pageXOffset - 1;
}
return [left, top];
};
const calcPosWhenLeft = () => {
let left: number;
let top: number;
if (props.anchorElement) {
2023-01-13 04:40:33 +00:00
left = rect.left + window.pageXOffset - contentWidth - props.innerMargin;
top =
rect.top + window.pageYOffset + props.anchorElement.offsetHeight / 2;
2022-07-17 12:06:33 +00:00
} else {
2023-01-13 04:40:33 +00:00
left = props.x - contentWidth - props.innerMargin;
2022-07-17 12:06:33 +00:00
top = props.y;
}
2023-01-13 04:40:33 +00:00
top -= el.offsetHeight / 2;
2022-07-17 12:06:33 +00:00
if (top + contentHeight - window.pageYOffset > window.innerHeight) {
top = window.innerHeight - contentHeight + window.pageYOffset - 1;
}
return [left, top];
};
const calcPosWhenRight = () => {
let left: number;
let top: number;
if (props.anchorElement) {
2023-01-13 04:40:33 +00:00
left =
rect.left +
props.anchorElement.offsetWidth +
window.pageXOffset +
props.innerMargin;
2022-07-17 12:06:33 +00:00
2023-01-13 04:40:33 +00:00
if (props.align === "top") {
2022-07-17 12:06:33 +00:00
top = rect.top + window.pageYOffset;
if (props.alignOffset != null) top += props.alignOffset;
2023-01-13 04:40:33 +00:00
} else if (props.align === "bottom") {
2022-07-17 12:06:33 +00:00
// TODO
2023-01-13 04:40:33 +00:00
} else {
// center
top =
rect.top + window.pageYOffset + props.anchorElement.offsetHeight / 2;
top -= el.offsetHeight / 2;
2022-07-17 12:06:33 +00:00
}
} else {
left = props.x + props.innerMargin;
top = props.y;
2023-01-13 04:40:33 +00:00
top -= el.offsetHeight / 2;
2022-07-17 12:06:33 +00:00
}
if (top + contentHeight - window.pageYOffset > window.innerHeight) {
top = window.innerHeight - contentHeight + window.pageYOffset - 1;
}
return [left, top];
};
const calc = (): {
left: number;
top: number;
transformOrigin: string;
} => {
switch (props.direction) {
2023-01-13 04:40:33 +00:00
case "top": {
2022-07-17 12:06:33 +00:00
const [left, top] = calcPosWhenTop();
// ツールチップを上に向かって表示するスペースがなければ下に向かって出す
if (top - window.pageYOffset < 0) {
const [left, top] = calcPosWhenBottom();
2023-01-13 04:40:33 +00:00
return { left, top, transformOrigin: "center top" };
2022-07-17 12:06:33 +00:00
}
2023-01-13 04:40:33 +00:00
return { left, top, transformOrigin: "center bottom" };
2022-07-17 12:06:33 +00:00
}
2023-01-13 04:40:33 +00:00
case "bottom": {
2022-07-17 12:06:33 +00:00
const [left, top] = calcPosWhenBottom();
// TODO: ツールチップを下に向かって表示するスペースがなければ上に向かって出す
2023-01-13 04:40:33 +00:00
return { left, top, transformOrigin: "center top" };
2022-07-17 12:06:33 +00:00
}
2023-01-13 04:40:33 +00:00
case "left": {
2022-07-17 12:06:33 +00:00
const [left, top] = calcPosWhenLeft();
// ツールチップを左に向かって表示するスペースがなければ右に向かって出す
if (left - window.pageXOffset < 0) {
const [left, top] = calcPosWhenRight();
2023-01-13 04:40:33 +00:00
return { left, top, transformOrigin: "left center" };
2022-07-17 12:06:33 +00:00
}
2023-01-13 04:40:33 +00:00
return { left, top, transformOrigin: "right center" };
2022-07-17 12:06:33 +00:00
}
2023-01-13 04:40:33 +00:00
case "right": {
2022-07-17 12:06:33 +00:00
const [left, top] = calcPosWhenRight();
// TODO: ツールチップを右に向かって表示するスペースがなければ左に向かって出す
2023-01-13 04:40:33 +00:00
return { left, top, transformOrigin: "left center" };
2022-07-17 12:06:33 +00:00
}
}
};
return calc();
}