diff --git a/packages/tiny-toast/src/common.ts b/packages/tiny-toast/src/common.ts index 407486f4..9ecba790 100644 --- a/packages/tiny-toast/src/common.ts +++ b/packages/tiny-toast/src/common.ts @@ -1,4 +1,5 @@ -import { cls, forceStyle, styleAssign } from "./utils"; +import { tinyassert } from "@hiogawa/utils"; +import { cls, styleAssign } from "./utils"; // TODO: support all 6 positions export const TOAST_POSITIONS = ["bottom-left", "top-center"] as const; @@ -37,33 +38,70 @@ export function slideScaleCollapseTransition({ position: ToastPosition; }) { // steps - // - slide out + scale down + collapse // - slide in + scale up + uncollapse + // - slide out + scale down + collapse return { - out: (el: HTMLElement) => { + enterFrom: (el: HTMLElement) => { + tinyassert(el.firstElementChild instanceof HTMLElement); styleAssign(el.style, { - opacity: "0", + transition: "all 0.2s ease", + height: "0", + }); + styleAssign(el.firstElementChild.style, { + transition: "all 0.2s ease-in-out", // "translateY" comes slightly after "height" + opacity: "0.5", transform: cls( - "scale(0)", - position === "bottom-left" && "translateY(120%)", - position === "top-center" && "translateY(-120%)" + "scale(0.5)", + position === "bottom-left" && "translateY(200%)", + position === "top-center" && "translateY(-200%)" ), }); - forceStyle(el); // somehow order matters and without this preact animation glitches - el.style.height = "0px"; }, - in: (el: HTMLElement) => { - if (el.firstElementChild) { - el.style.height = el.firstElementChild.clientHeight + "px"; - forceStyle(el); - } + enterTo: (el: HTMLElement) => { + tinyassert(el.firstElementChild instanceof HTMLElement); styleAssign(el.style, { + height: el.firstElementChild.clientHeight + "px", + }); + styleAssign(el.firstElementChild.style, { opacity: "1", transform: "scale(1) translateY(0)", }); }, - reset: (el: HTMLElement) => { - el.style.height = ""; + entered: (el: HTMLElement) => { + tinyassert(el.firstElementChild instanceof HTMLElement); + styleAssign(el.style, { + transition: "", + height: "", + }); + styleAssign(el.firstElementChild.style, { + transition: "", + }); + }, + leaveFrom: (el: HTMLElement) => { + tinyassert(el.firstElementChild instanceof HTMLElement); + styleAssign(el.style, { + transition: "all 0.2s ease-in-out", + height: el.firstElementChild.clientHeight + "px", + }); + styleAssign(el.firstElementChild.style, { + transition: "all 0.2s ease", + opacity: "1", + transform: "scale(1) translateY(0)", + }); + }, + leaveTo: (el: HTMLElement) => { + tinyassert(el.firstElementChild instanceof HTMLElement); + styleAssign(el.style, { + height: "0", + }); + styleAssign(el.firstElementChild.style, { + opacity: "0", + transform: cls( + "scale(0)", + position === "bottom-left" && "translateY(150%)", + position === "top-center" && "translateY(-150%)" + ), + }); }, }; } diff --git a/packages/tiny-toast/src/preact/ui.tsx b/packages/tiny-toast/src/preact/ui.tsx index c5b663b0..ef54bf67 100644 --- a/packages/tiny-toast/src/preact/ui.tsx +++ b/packages/tiny-toast/src/preact/ui.tsx @@ -53,7 +53,7 @@ export function ToastContainer({ toast }: { toast: PreactToastManager }) { { style: istyle({ position: "absolute", - top: "3px", + top: "0.5rem", width: "100%", display: "flex", flexDirection: "column-reverse", @@ -81,11 +81,11 @@ function ToastAnimation({ }); const manager = new TransitionManager({ defaultEntered: false, - onEnterFrom: transition.out, - onEnterTo: transition.in, - onEntered: transition.reset, - onLeaveFrom: transition.in, - onLeaveTo: transition.out, + onEnterFrom: transition.enterFrom, + onEnterTo: transition.enterTo, + onEntered: transition.entered, + onLeaveFrom: transition.leaveFrom, + onLeaveTo: transition.leaveTo, onLeft: () => toast.remove(item.id), }); return manager; @@ -107,7 +107,6 @@ function ToastAnimation({ ref: manager.setElement, style: istyle({ pointerEvents: "auto", - transitionDuration: "200ms", }), }, h( diff --git a/packages/tiny-toast/src/react/ui.tsx b/packages/tiny-toast/src/react/ui.tsx index 6be626fe..0c724fd5 100644 --- a/packages/tiny-toast/src/react/ui.tsx +++ b/packages/tiny-toast/src/react/ui.tsx @@ -106,11 +106,11 @@ function AnimationWrapper({ pointerEvents: "auto", transitionDuration: "200ms", }} - onEnterFrom={transition.out} - onEnterTo={transition.in} - onEntered={transition.reset} - onLeaveFrom={transition.in} - onLeaveTo={transition.out} + onEnterFrom={transition.enterFrom} + onEnterTo={transition.enterTo} + onEntered={transition.entered} + onLeaveFrom={transition.leaveFrom} + onLeaveTo={transition.leaveTo} onLeft={() => toast.remove(item.id)} >
diff --git a/packages/tiny-toast/src/utils.ts b/packages/tiny-toast/src/utils.ts index 61e5acac..f23140bb 100644 --- a/packages/tiny-toast/src/utils.ts +++ b/packages/tiny-toast/src/utils.ts @@ -67,10 +67,6 @@ export function cls(...args: unknown[]) { return args.filter(Boolean).join(" "); } -export function forceStyle(el: HTMLElement) { - typeof getComputedStyle(el).transition || console.log("unreachable"); -} - // type-safe styles assignment export const styleAssign = Object.assign< Partial, diff --git a/packages/tiny-transition/src/core.ts b/packages/tiny-transition/src/core.ts index e46cbdcc..74f18306 100644 --- a/packages/tiny-transition/src/core.ts +++ b/packages/tiny-transition/src/core.ts @@ -81,6 +81,7 @@ export class TransitionManager { this.disposables.add( onNextFrame(() => { // "enterTo" on next frame + forceStyle(el); // TODO: don't need next frame if forceStyle? this.options.onEnterTo?.(el); // notify "entered" @@ -105,6 +106,7 @@ export class TransitionManager { this.disposables.add( onNextFrame(() => { // "leaveTo" on next frame + forceStyle(el); this.options.onLeaveTo?.(el); // notify "left" @@ -190,3 +192,7 @@ function parseDurationSingle(s: string): number { tinyassert(Number.isFinite(ms), `failed to parse css duration '${s}'`); return ms; } + +export function forceStyle(el: HTMLElement) { + typeof getComputedStyle(el).transition || console.log("unreachable"); +}