Menu: support right-top origin
All checks were successful
/ depoly (push) Successful in 1m18s

This commit is contained in:
thislight 2024-10-25 22:40:07 +08:00
parent 556e0b2336
commit 4958fb1346

View file

@ -14,56 +14,84 @@ type Props = {
anchor: () => DOMRect; anchor: () => DOMRect;
}; };
function adjustMenuPosition( function px(n?: number) {
rect: DOMRect, if (n) {
[left, top]: [number, number], return `${n}px`;
{ width, height }: { width: number; height: number }, } else {
) { return undefined;
const ntop = rect.bottom > height ? top - (rect.bottom - height) : top; }
const nleft = rect.right > width ? left - (rect.right - width) : left;
return [nleft, ntop] as [number, number];
} }
const Menu: ParentComponent<Props> = (props) => { const Menu: ParentComponent<Props> = (props) => {
let root: HTMLDialogElement; let root: HTMLDialogElement;
const [pos, setPos] = createSignal<[number, number]>([0, 0]);
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const [anchorPos, setAnchorPos] = createSignal<{
left?: number;
top?: number;
}>({});
let openAnimationOrigin: "lt" | "rt" = "lt";
createEffect(() => { createEffect(() => {
if (props.open) { if (props.open) {
const a = props.anchor(); const a = props.anchor();
if (!root.open) { if (!root.open) {
root.showModal(); root.showModal();
const rend = root.getBoundingClientRect(); const rend = root.getBoundingClientRect();
setPos(adjustMenuPosition(rend, [a.left, a.top], windowSize)); const { width } = windowSize;
const { left, top, right } = a;
if (left > width / 2) {
openAnimationOrigin = "rt";
setAnchorPos({
left: right - rend.width,
top,
});
const overflow = root.style.overflow; const overflow = root.style.overflow;
root.style.overflow = "hidden"; root.style.overflow = "hidden";
const duration = (rend.height / 1600) * 1000; const duration = (rend.height / 1600) * 1000;
const easing = ANIM_CURVE_STD; const easing = ANIM_CURVE_STD;
const animation = root.animate( const animation = root.animate(
{ {
height: [`${rend.height / 2}px`, `${rend.height}px`], height: [`${rend.height / 2}px`, `${rend.height}px`],
width: [`${rend.width / 4 * 3}px`, `${rend.width}px`], },
}, {
{ duration,
duration, easing,
easing, },
}, );
); animation.addEventListener(
animation.addEventListener( "finish",
"finish", () => (root.style.overflow = overflow),
() => (root.style.overflow = overflow), );
); } else {
openAnimationOrigin = "lt";
setAnchorPos({ left, top });
const overflow = root.style.overflow;
root.style.overflow = "hidden";
const duration = (rend.height / 1600) * 1000;
const easing = ANIM_CURVE_STD;
const animation = root.animate(
{
height: [`${rend.height / 2}px`, `${rend.height}px`],
width: [`${(rend.width / 4) * 3}px`, `${rend.width}px`],
},
{
duration,
easing,
},
);
animation.addEventListener(
"finish",
() => (root.style.overflow = overflow),
);
}
} else { } else {
setPos( // TODO: update the pos
adjustMenuPosition(
root.getBoundingClientRect(),
[a.left, a.top],
windowSize,
),
);
} }
} else { } else {
animateClose(); animateClose();
@ -72,22 +100,40 @@ const Menu: ParentComponent<Props> = (props) => {
const animateClose = () => { const animateClose = () => {
const rend = root.getBoundingClientRect(); const rend = root.getBoundingClientRect();
const overflow = root.style.overflow; if (openAnimationOrigin === "lt") {
root.style.overflow = "hidden"; const overflow = root.style.overflow;
const animation = root.animate( root.style.overflow = "hidden";
{ const animation = root.animate(
height: [`${rend.height}px`, `${rend.height / 2}px`], {
width: [`${rend.width}px`, `${rend.width / 4 * 3}px`], height: [`${rend.height}px`, `${rend.height / 2}px`],
}, width: [`${rend.width}px`, `${(rend.width / 4) * 3}px`],
{ },
duration: (rend.height / 2 / 1600) * 1000, {
easing: ANIM_CURVE_STD, duration: (rend.height / 2 / 1600) * 1000,
}, easing: ANIM_CURVE_STD,
); },
animation.addEventListener("finish", () => { );
root.style.overflow = overflow; animation.addEventListener("finish", () => {
root.close(); root.style.overflow = overflow;
}); root.close();
});
} else {
const overflow = root.style.overflow;
root.style.overflow = "hidden";
const animation = root.animate(
{
height: [`${rend.height}px`, `${rend.height / 2}px`],
},
{
duration: (rend.height / 2 / 1600) * 1000,
easing: ANIM_CURVE_STD,
},
);
animation.addEventListener("finish", () => {
root.style.overflow = overflow;
root.close();
});
}
}; };
return ( return (
@ -111,14 +157,15 @@ const Menu: ParentComponent<Props> = (props) => {
} }
}} }}
style={{ style={{
position: "absolute", position: "fixed",
left: `${pos()[0]}px`, left: px(anchorPos().left),
top: `${pos()[1]}px`, top: px(anchorPos().top),
border: "none", border: "none",
"border-radius": "2px",
padding: 0, padding: 0,
"max-width": "560px", "max-width": "560px",
width: "max-content", width: "max-content",
/*"min-width": "20vw", */ /* FIXME: the content may be overflow */
"box-shadow": "var(--tutu-shadow-e8)", "box-shadow": "var(--tutu-shadow-e8)",
}} }}
> >