From 4958fb134678dfe158e537915306ecb3cd312638 Mon Sep 17 00:00:00 2001 From: thislight Date: Fri, 25 Oct 2024 22:40:07 +0800 Subject: [PATCH] Menu: support right-top origin --- src/material/Menu.tsx | 157 +++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/src/material/Menu.tsx b/src/material/Menu.tsx index 365d090..6c08dad 100644 --- a/src/material/Menu.tsx +++ b/src/material/Menu.tsx @@ -14,56 +14,84 @@ type Props = { anchor: () => DOMRect; }; -function adjustMenuPosition( - rect: DOMRect, - [left, top]: [number, number], - { width, height }: { width: number; height: number }, -) { - 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]; +function px(n?: number) { + if (n) { + return `${n}px`; + } else { + return undefined; + } } const Menu: ParentComponent = (props) => { let root: HTMLDialogElement; - const [pos, setPos] = createSignal<[number, number]>([0, 0]); const windowSize = useWindowSize(); + const [anchorPos, setAnchorPos] = createSignal<{ + left?: number; + top?: number; + }>({}); + + let openAnimationOrigin: "lt" | "rt" = "lt"; + createEffect(() => { if (props.open) { const a = props.anchor(); + if (!root.open) { root.showModal(); 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; - 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), - ); + 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`], + }, + { + duration, + easing, + }, + ); + animation.addEventListener( + "finish", + () => (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 { - setPos( - adjustMenuPosition( - root.getBoundingClientRect(), - [a.left, a.top], - windowSize, - ), - ); + // TODO: update the pos } } else { animateClose(); @@ -72,22 +100,40 @@ const Menu: ParentComponent = (props) => { const animateClose = () => { const rend = root.getBoundingClientRect(); - const overflow = root.style.overflow; - 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`], - }, - { - duration: (rend.height / 2 / 1600) * 1000, - easing: ANIM_CURVE_STD, - }, - ); - animation.addEventListener("finish", () => { - root.style.overflow = overflow; - root.close(); - }); + if (openAnimationOrigin === "lt") { + const overflow = root.style.overflow; + 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`], + }, + { + duration: (rend.height / 2 / 1600) * 1000, + easing: ANIM_CURVE_STD, + }, + ); + animation.addEventListener("finish", () => { + 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 ( @@ -111,14 +157,15 @@ const Menu: ParentComponent = (props) => { } }} style={{ - position: "absolute", - left: `${pos()[0]}px`, - top: `${pos()[1]}px`, + position: "fixed", + left: px(anchorPos().left), + top: px(anchorPos().top), border: "none", + "border-radius": "2px", padding: 0, "max-width": "560px", width: "max-content", - /*"min-width": "20vw", */ + /* FIXME: the content may be overflow */ "box-shadow": "var(--tutu-shadow-e8)", }} >