UNPKG

@mantine/notifications

Version:

Mantine notifications system

207 lines (206 loc) 7.59 kB
"use client"; import { getAutoClose } from "./get-auto-close/get-auto-close.mjs"; import { useDrag, useMergedRef } from "@mantine/hooks"; import { useEffect, useEffectEvent, useRef, useState } from "react"; import { Notification, getStyleObject, useMantineTheme } from "@mantine/core"; import { jsx } from "react/jsx-runtime"; //#region packages/@mantine/notifications/src/NotificationContainer.tsx const SCROLL_DISMISS_RESET_TIMEOUT = 120; function NotificationContainer({ data, onHide, autoClose, transitionDuration, allowDragDismiss, allowScrollDismiss, paused, onHoverStart, onHoverEnd, ref, style, ...others }) { const [offset, setOffset] = useState(0); const [dismissed, setDismissed] = useState(false); const [dismissDirection, setDismissDirection] = useState(1); const [scrollDismissActive, setScrollDismissActive] = useState(false); const theme = useMantineTheme(); const { autoClose: _autoClose, message, allowClose, position: _position, style: dataStyle, withCloseButton, onOpen: _onOpen, ...notificationProps } = data; const autoCloseDuration = getAutoClose(autoClose, data.autoClose); const autoCloseTimeout = useRef(-1); const hideTimeout = useRef(-1); const scrollDismissTimeout = useRef(-1); const notificationRef = useRef(null); const hoveredRef = useRef(false); const offsetRef = useRef(0); const isCloseDisabled = allowClose === false; const cancelAutoClose = () => window.clearTimeout(autoCloseTimeout.current); const cancelHide = () => window.clearTimeout(hideTimeout.current); const cancelScrollDismissReset = () => window.clearTimeout(scrollDismissTimeout.current); const setSwipeOffset = (value) => { offsetRef.current = value; setOffset(value); }; const handleHide = () => { onHide(data.id); cancelAutoClose(); cancelHide(); cancelScrollDismissReset(); }; const handleAutoClose = () => { if (dismissed || active || paused || hoveredRef.current || typeof autoCloseDuration !== "number") return; autoCloseTimeout.current = window.setTimeout(handleHide, autoCloseDuration); }; const getExitOffset = (direction) => { return direction * ((notificationRef.current?.offsetWidth ?? 440) + 40); }; const shouldDismiss = (movement, velocity) => { const width = notificationRef.current?.offsetWidth ?? 440; return Math.abs(movement) > width * .35 || velocity > .5; }; const resetSwipe = () => { cancelScrollDismissReset(); setScrollDismissActive(false); setSwipeOffset(0); }; const dismissNotification = (direction) => { setDismissDirection(direction); setDismissed(true); setScrollDismissActive(false); setSwipeOffset(getExitOffset(direction)); cancelAutoClose(); cancelHide(); cancelScrollDismissReset(); hideTimeout.current = window.setTimeout(handleHide, transitionDuration); }; const scheduleScrollDismissReset = () => { cancelScrollDismissReset(); scrollDismissTimeout.current = window.setTimeout(() => { setScrollDismissActive(false); setSwipeOffset(0); handleAutoClose(); }, SCROLL_DISMISS_RESET_TIMEOUT); }; const { ref: dragRef, active } = useDrag((state) => { if (dismissed) return; if (state.first) cancelAutoClose(); if (state.last) { if (state.tap || state.canceled) { setSwipeOffset(0); handleAutoClose(); return; } const movement = state.movement[0]; const direction = movement === 0 ? state.direction[0] === -1 ? -1 : 1 : movement > 0 ? 1 : -1; if (shouldDismiss(movement, state.velocity[0])) dismissNotification(direction); else { setSwipeOffset(0); handleAutoClose(); } } else setSwipeOffset(state.movement[0]); }, { axis: "x", threshold: 5, filterTaps: true, enabled: allowDragDismiss && !isCloseDisabled && !dismissed }); const mergedRef = useMergedRef(ref, notificationRef, dragRef); const resolvedStyle = getStyleObject(style, theme); const resolvedDataStyle = getStyleObject(dataStyle, theme); const baseStyle = { ...resolvedStyle, ...resolvedDataStyle }; const baseOpacity = typeof baseStyle.opacity === "number" ? baseStyle.opacity : 1; const swipeOpacity = dismissed ? 0 : 1 - Math.min(Math.abs(offset) / 200, 1) * .6; const resolvedTransitionDuration = baseStyle.transitionDuration ?? `${transitionDuration}ms, ${transitionDuration}ms, ${transitionDuration}ms`; const notificationStyle = { ...baseStyle, ["--notifications-state-transform"]: typeof baseStyle.transform === "string" ? baseStyle.transform : "translateX(0)", ["--notifications-state-opacity"]: String(baseOpacity), ["--notifications-swipe-offset"]: `${offset}px`, ["--notifications-swipe-opacity"]: String(swipeOpacity), transform: "var(--notifications-state-transform) translate3d(var(--notifications-swipe-offset), 0, 0)", opacity: "calc(var(--notifications-state-opacity) * var(--notifications-swipe-opacity))", transitionDuration: active || scrollDismissActive ? "0ms, 0ms, 0ms" : resolvedTransitionDuration, cursor: "default", touchAction: "pan-y" }; const handleMouseEnter = () => { hoveredRef.current = true; cancelAutoClose(); onHoverStart?.(); }; const handleMouseLeave = () => { hoveredRef.current = false; if (!scrollDismissActive) { resetSwipe(); handleAutoClose(); } onHoverEnd?.(); }; const handleWheel = useEffectEvent((event) => { if (dismissed || active) return; const isDocumentEvent = event.currentTarget === document; if (!isDocumentEvent && !hoveredRef.current) return; const { deltaX, deltaY } = event; if (Math.abs(deltaX) <= Math.abs(deltaY) || deltaX === 0) return; if (!allowScrollDismiss || isCloseDisabled) return; if (!isDocumentEvent) { event.preventDefault(); event.stopPropagation(); } cancelAutoClose(); setScrollDismissActive(true); const nextOffset = offsetRef.current - deltaX; const direction = nextOffset > 0 ? 1 : -1; if (shouldDismiss(nextOffset, 0)) { dismissNotification(direction); return; } setSwipeOffset(nextOffset); scheduleScrollDismissReset(); }); useEffect(() => { if (!scrollDismissActive) return; document.addEventListener("wheel", handleWheel, { passive: false }); return () => document.removeEventListener("wheel", handleWheel, { passive: false }); }, [scrollDismissActive]); useEffect(() => { const handleResize = () => { if (dismissed) setSwipeOffset(getExitOffset(dismissDirection)); }; window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, [dismissDirection, dismissed]); useEffect(() => { const node = notificationRef.current; if (!node) return; node.addEventListener("wheel", handleWheel, { passive: false }); return () => node.removeEventListener("wheel", handleWheel, { passive: false }); }, []); useEffect(() => { return () => { cancelHide(); cancelScrollDismissReset(); }; }, []); useEffect(() => { data.onOpen?.(data); }, []); useEffect(() => { handleAutoClose(); return cancelAutoClose; }, [ autoCloseDuration, active, dismissed ]); useEffect(() => { if (paused) cancelAutoClose(); else handleAutoClose(); return cancelAutoClose; }, [paused]); return /* @__PURE__ */ jsx(Notification, { ref: mergedRef, ...others, style: notificationStyle, ...notificationProps, withCloseButton: isCloseDisabled ? false : withCloseButton, onClose: handleHide, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: message }); } NotificationContainer.displayName = "@mantine/notifications/NotificationContainer"; //#endregion export { NotificationContainer }; //# sourceMappingURL=NotificationContainer.mjs.map