Compare commits
	
		
			No commits in common. "7205fa5775f40bc0286c5077fc562597519dce0f" and "e174b7aafdb20f8cbdbf65a606acb54bd6a7b50e" have entirely different histories.
		
	
	
		
			7205fa5775
			...
			e174b7aafd
		
	
		
					 7 changed files with 76 additions and 150 deletions
				
			
		| 
						 | 
					@ -8,8 +8,7 @@
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "dev": "vite --host 0.0.0.0",
 | 
					    "dev": "vite --host 0.0.0.0",
 | 
				
			||||||
    "preview": "vite preview",
 | 
					    "preview": "vite preview",
 | 
				
			||||||
    "dist": "vite build",
 | 
					    "dist": "vite build"
 | 
				
			||||||
    "count-source-lines": "exec scripts/src-lc.sh"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "keywords": [],
 | 
					  "keywords": [],
 | 
				
			||||||
  "author": "Rubicon",
 | 
					  "author": "Rubicon",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +0,0 @@
 | 
				
			||||||
#!/bin/sh
 | 
					 | 
				
			||||||
# Count the source lines.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
find . '(' ! -path "./node_modules/**" ')' \
 | 
					 | 
				
			||||||
  -and '(' ! -path "./.git/**" ')' \
 | 
					 | 
				
			||||||
  -and '(' ! -path "./*dist/**" ')' \
 | 
					 | 
				
			||||||
  -and '(' ! -path "./bun.lockb" ')' \
 | 
					 | 
				
			||||||
  -and '(' ! -path "./docs/**" ')' \
 | 
					 | 
				
			||||||
  -type f -print0 \
 | 
					 | 
				
			||||||
  | wc -l --files0-from=-
 | 
					 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,6 @@
 | 
				
			||||||
  width: max-content;
 | 
					  width: max-content;
 | 
				
			||||||
  box-shadow: var(--tutu-shadow-e8);
 | 
					  box-shadow: var(--tutu-shadow-e8);
 | 
				
			||||||
  contain: content;
 | 
					  contain: content;
 | 
				
			||||||
  overscroll-behavior: contain;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &.e1 {
 | 
					  &.e1 {
 | 
				
			||||||
    box-shadow: var(--tutu-shadow-e9);
 | 
					    box-shadow: var(--tutu-shadow-e9);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
import { useWindowSize } from "@solid-primitives/resize-observer";
 | 
					import { useWindowSize } from "@solid-primitives/resize-observer";
 | 
				
			||||||
import { MenuList } from "@suid/material";
 | 
					import { MenuList } from "@suid/material";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  batch,
 | 
					 | 
				
			||||||
  createEffect,
 | 
					  createEffect,
 | 
				
			||||||
  createSignal,
 | 
					  createSignal,
 | 
				
			||||||
  splitProps,
 | 
					  splitProps,
 | 
				
			||||||
| 
						 | 
					@ -66,39 +65,11 @@ export function createManagedMenuState() {
 | 
				
			||||||
        return !!anchor();
 | 
					        return !!anchor();
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      anchor: anchor as () => Anchor,
 | 
					      anchor: anchor as () => Anchor,
 | 
				
			||||||
      onClose: (event: Event) => {
 | 
					      onClose: () => setAnchor(),
 | 
				
			||||||
        event.preventDefault();
 | 
					 | 
				
			||||||
        return setAnchor();
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  ] as const;
 | 
					  ] as const;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function animateGrowFromTopLeft(
 | 
					 | 
				
			||||||
  element: HTMLElement,
 | 
					 | 
				
			||||||
  opts?: Omit<KeyframeAnimationOptions, "duration">,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  const rend = element.getBoundingClientRect();
 | 
					 | 
				
			||||||
  const overflow = element.style.overflow;
 | 
					 | 
				
			||||||
  element.style.overflow = "hidden";
 | 
					 | 
				
			||||||
  const duration = (rend.height / 1600) * 1000;
 | 
					 | 
				
			||||||
  const animation = element.animate(
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      height: [`${rend.height / 2}px`, `${rend.height}px`],
 | 
					 | 
				
			||||||
      width: [`${(rend.width / 4) * 3}px`, `${rend.width}px`],
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      duration,
 | 
					 | 
				
			||||||
      ...opts,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  animation.addEventListener(
 | 
					 | 
				
			||||||
    "finish",
 | 
					 | 
				
			||||||
    () => (element.style.overflow = overflow),
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  return animation;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Material Menu Component. This component is
 | 
					 * Material Menu Component. This component is
 | 
				
			||||||
 * implemented with dialog and {@link MenuList} from SUID.
 | 
					 * implemented with dialog and {@link MenuList} from SUID.
 | 
				
			||||||
| 
						 | 
					@ -107,16 +78,10 @@ function animateGrowFromTopLeft(
 | 
				
			||||||
 * - Use {@link createManagedMenuState} and you don't need to manage the open and close.
 | 
					 * - Use {@link createManagedMenuState} and you don't need to manage the open and close.
 | 
				
			||||||
 * - Use {@link MenuItem} from SUID as children.
 | 
					 * - Use {@link MenuItem} from SUID as children.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const Menu: Component<MenuProps> = (oprops) => {
 | 
					const Menu: Component<MenuProps> = (props) => {
 | 
				
			||||||
  let root: HTMLDialogElement;
 | 
					  let root: HTMLDialogElement;
 | 
				
			||||||
  const windowSize = useWindowSize();
 | 
					  const windowSize = useWindowSize();
 | 
				
			||||||
  const [props, rest] = splitProps(oprops, [
 | 
					  const [, rest] = splitProps(props, ["open", "onClose", "anchor"]);
 | 
				
			||||||
    "open",
 | 
					 | 
				
			||||||
    "onClose",
 | 
					 | 
				
			||||||
    "anchor",
 | 
					 | 
				
			||||||
    "MenuListProps",
 | 
					 | 
				
			||||||
    "children",
 | 
					 | 
				
			||||||
  ]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [anchorPos, setAnchorPos] = createSignal<{
 | 
					  const [anchorPos, setAnchorPos] = createSignal<{
 | 
				
			||||||
    left?: number;
 | 
					    left?: number;
 | 
				
			||||||
| 
						 | 
					@ -141,57 +106,51 @@ const Menu: Component<MenuProps> = (oprops) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let openAnimationOrigin: "lt" | "rt" = "lt";
 | 
					  let openAnimationOrigin: "lt" | "rt" = "lt";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const animateOpen = () => {
 | 
					 | 
				
			||||||
    const a = props.anchor();
 | 
					 | 
				
			||||||
    const { width } = windowSize;
 | 
					 | 
				
			||||||
    const { left, top, right, e } = a;
 | 
					 | 
				
			||||||
    const isOpened = root.open;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // There are incomplete animations.
 | 
					 | 
				
			||||||
    // For `getBoundingClientRect()`, WebKit reports the initial state
 | 
					 | 
				
			||||||
    // of the element, whilst Firefox reports the final state.
 | 
					 | 
				
			||||||
    //
 | 
					 | 
				
			||||||
    // We skip if animations are still on the element
 | 
					 | 
				
			||||||
    // to avoid the problem on WebKit.
 | 
					 | 
				
			||||||
    // Here use the final state.
 | 
					 | 
				
			||||||
    //
 | 
					 | 
				
			||||||
    // This is a dirty workaround. It's here because the feature is still
 | 
					 | 
				
			||||||
    // works with it.
 | 
					 | 
				
			||||||
    // I am curious that why the ones on the other parts are works. (Rubicon)
 | 
					 | 
				
			||||||
    if (root.getAnimations().length > 0) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    root.showModal();
 | 
					 | 
				
			||||||
    const rend = root.getBoundingClientRect();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (left > width / 2) {
 | 
					 | 
				
			||||||
      openAnimationOrigin = "rt";
 | 
					 | 
				
			||||||
      setAnchorPos({
 | 
					 | 
				
			||||||
        left: right - rend.width,
 | 
					 | 
				
			||||||
        top,
 | 
					 | 
				
			||||||
        e,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      openAnimationOrigin = "lt";
 | 
					 | 
				
			||||||
      setAnchorPos({ left, top, e });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!isOpened) {
 | 
					 | 
				
			||||||
      switch (openAnimationOrigin) {
 | 
					 | 
				
			||||||
        case "lt":
 | 
					 | 
				
			||||||
          animateGrowFromTopLeft(root, { easing: ANIM_CURVE_STD });
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        case "rt":
 | 
					 | 
				
			||||||
          animateGrowFromTopRight(root, { easing: ANIM_CURVE_STD });
 | 
					 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  createEffect(() => {
 | 
					  createEffect(() => {
 | 
				
			||||||
    if (props.open) {
 | 
					    if (props.open) {
 | 
				
			||||||
      animateOpen();
 | 
					      const a = props.anchor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!root.open) {
 | 
				
			||||||
 | 
					        root.showModal();
 | 
				
			||||||
 | 
					        const rend = root.getBoundingClientRect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const { width } = windowSize;
 | 
				
			||||||
 | 
					        const { left, top, right, e } = a;
 | 
				
			||||||
 | 
					        if (left > width / 2) {
 | 
				
			||||||
 | 
					          openAnimationOrigin = "rt";
 | 
				
			||||||
 | 
					          setAnchorPos({
 | 
				
			||||||
 | 
					            left: right - rend.width,
 | 
				
			||||||
 | 
					            top,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          animateGrowFromTopRight(root, { easing: ANIM_CURVE_STD });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          openAnimationOrigin = "lt";
 | 
				
			||||||
 | 
					          setAnchorPos({ left, top, e });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          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 {
 | 
				
			||||||
 | 
					        // TODO: update the pos
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      animateClose();
 | 
					      animateClose();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -226,42 +185,27 @@ const Menu: Component<MenuProps> = (oprops) => {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onDialogClick = (
 | 
					 | 
				
			||||||
    event: MouseEvent & { currentTarget: HTMLDialogElement },
 | 
					 | 
				
			||||||
  ) => {
 | 
					 | 
				
			||||||
    event.stopPropagation();
 | 
					 | 
				
			||||||
    if (event.currentTarget !== event.target) return;
 | 
					 | 
				
			||||||
    if (!event.currentTarget.open) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const rect = event.currentTarget.getBoundingClientRect();
 | 
					 | 
				
			||||||
    const isNotInDialog =
 | 
					 | 
				
			||||||
      event.clientY < rect.top ||
 | 
					 | 
				
			||||||
      event.clientY > rect.bottom ||
 | 
					 | 
				
			||||||
      event.clientX < rect.left ||
 | 
					 | 
				
			||||||
      event.clientX > rect.right;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (isNotInDialog) {
 | 
					 | 
				
			||||||
      if (props.onClose) {
 | 
					 | 
				
			||||||
        if (Array.isArray(props.onClose)) {
 | 
					 | 
				
			||||||
          props.onClose[0](props.onClose[1], event);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          (
 | 
					 | 
				
			||||||
            props.onClose as (
 | 
					 | 
				
			||||||
              event: Event & { currentTarget: HTMLDialogElement },
 | 
					 | 
				
			||||||
            ) => void
 | 
					 | 
				
			||||||
          )(event);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <dialog
 | 
					    <dialog
 | 
				
			||||||
      ref={root!}
 | 
					      ref={root!}
 | 
				
			||||||
      onClose={props.onClose}
 | 
					      onClose={props.onClose}
 | 
				
			||||||
      onCancel={props.onClose}
 | 
					      onClick={(e) => {
 | 
				
			||||||
      onClick={onDialogClick}
 | 
					        if (e.target === root) {
 | 
				
			||||||
      class={`Menu e${anchorPos().e || "0"}`}
 | 
					          if (props.onClose) {
 | 
				
			||||||
 | 
					            if (Array.isArray(props.onClose)) {
 | 
				
			||||||
 | 
					              props.onClose[0](props.onClose[1], e);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              (
 | 
				
			||||||
 | 
					                props.onClose as (
 | 
				
			||||||
 | 
					                  event: Event & { currentTarget: HTMLDialogElement },
 | 
				
			||||||
 | 
					                ) => void
 | 
				
			||||||
 | 
					              )(e);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          e.stopPropagation();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      class={`Menu e${anchorPos().e || 0}`}
 | 
				
			||||||
      style={{
 | 
					      style={{
 | 
				
			||||||
        left: px(anchorPos().left),
 | 
					        left: px(anchorPos().left),
 | 
				
			||||||
        top: px(anchorPos().top),
 | 
					        top: px(anchorPos().top),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,9 +4,6 @@
 | 
				
			||||||
  z-index: var(--tutu-zidx-nav, auto);
 | 
					  z-index: var(--tutu-zidx-nav, auto);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .MuiToolbar-root {
 | 
					  .MuiToolbar-root {
 | 
				
			||||||
    margin-left: var(--safe-area-inset-left);
 | 
					 | 
				
			||||||
    margin-right: var(--safe-area-inset-right);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    >.MuiButtonBase-root {
 | 
					    >.MuiButtonBase-root {
 | 
				
			||||||
      &:first-child {
 | 
					      &:first-child {
 | 
				
			||||||
        margin-left: -0.5em;
 | 
					        margin-left: -0.5em;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,16 +15,6 @@ dialog.StackedPage {
 | 
				
			||||||
  width: 560px;
 | 
					  width: 560px;
 | 
				
			||||||
  max-height: 100vh;
 | 
					  max-height: 100vh;
 | 
				
			||||||
  max-height: 100dvh;
 | 
					  max-height: 100dvh;
 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
  * WebKit does not see contain-instric-size as the real element size.
 | 
					 | 
				
			||||||
  * If the container does not have height, the child element using 100%
 | 
					 | 
				
			||||||
  * height (usually Scafflod in our case) was have 0px computed height.
 | 
					 | 
				
			||||||
  *
 | 
					 | 
				
			||||||
  * This behaviour is different from Firefox. So we need to actually
 | 
					 | 
				
			||||||
  * define the box height here. (Rubicon)
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  height: 100vh;
 | 
					 | 
				
			||||||
  height: 100dvh;
 | 
					 | 
				
			||||||
  background: none;
 | 
					  background: none;
 | 
				
			||||||
  display: none;
 | 
					  display: none;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,9 +31,10 @@ dialog.StackedPage {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @media (max-width: 560px) {
 | 
					  @media (max-width: 560px) {
 | 
				
			||||||
    & {
 | 
					    & {
 | 
				
			||||||
      margin: 0;
 | 
					 | 
				
			||||||
      width: 100vw;
 | 
					      width: 100vw;
 | 
				
			||||||
      width: 100dvw;
 | 
					      width: 100dvw;
 | 
				
			||||||
 | 
					      height: 100vh;
 | 
				
			||||||
 | 
					      height: 100dvh;
 | 
				
			||||||
      contain-intrinsic-size: 100vw 100vh;
 | 
					      contain-intrinsic-size: 100vw 100vh;
 | 
				
			||||||
      contain-intrinsic-size: 100dvw 100dvh;
 | 
					      contain-intrinsic-size: 100dvw 100dvh;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,11 @@ import {
 | 
				
			||||||
  ListItemText,
 | 
					  ListItemText,
 | 
				
			||||||
  MenuItem,
 | 
					  MenuItem,
 | 
				
			||||||
} from "@suid/material";
 | 
					} from "@suid/material";
 | 
				
			||||||
import { Show, createUniqueId, type ParentComponent } from "solid-js";
 | 
					import {
 | 
				
			||||||
 | 
					  Show,
 | 
				
			||||||
 | 
					  createUniqueId,
 | 
				
			||||||
 | 
					  type ParentComponent,
 | 
				
			||||||
 | 
					} from "solid-js";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Settings as SettingsIcon,
 | 
					  Settings as SettingsIcon,
 | 
				
			||||||
  Bookmark as BookmarkIcon,
 | 
					  Bookmark as BookmarkIcon,
 | 
				
			||||||
| 
						 | 
					@ -35,7 +39,9 @@ const ProfileMenuButton: ParentComponent<{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [open, state] = createManagedMenuState();
 | 
					  const [open, state] = createManagedMenuState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onClick = (event: { currentTarget: HTMLElement }) => {
 | 
					  const onClick = (
 | 
				
			||||||
 | 
					    event: MouseEvent & { currentTarget: HTMLButtonElement },
 | 
				
			||||||
 | 
					  ) => {
 | 
				
			||||||
    open(event.currentTarget.getBoundingClientRect());
 | 
					    open(event.currentTarget.getBoundingClientRect());
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,8 +54,8 @@ const ProfileMenuButton: ParentComponent<{
 | 
				
			||||||
        sx={{ borderRadius: "50%" }}
 | 
					        sx={{ borderRadius: "50%" }}
 | 
				
			||||||
        id={buttonId}
 | 
					        id={buttonId}
 | 
				
			||||||
        onClick={onClick}
 | 
					        onClick={onClick}
 | 
				
			||||||
        aria-controls={state.open ? menuId : undefined}
 | 
					        aria-controls={open() ? menuId : undefined}
 | 
				
			||||||
        aria-expanded={state.open ? "true" : "false"}
 | 
					        aria-expanded={open() ? "true" : undefined}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <Avatar
 | 
					        <Avatar
 | 
				
			||||||
          alt={`${inf()?.displayName}'s avatar`}
 | 
					          alt={`${inf()?.displayName}'s avatar`}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue