UNPKG

react-atom-toast

Version:
486 lines (476 loc) 14.5 kB
'use strict'; var react = require('react'); var reactTransitionPreset = require('react-transition-preset'); var jsxRuntime = require('react/jsx-runtime'); var client = require('react-dom/client'); var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); function useMemoizedFn(fn) { const fnRef = react.useRef(fn); fnRef.current = react.useMemo(() => fn, [fn]); const memoizedFn = react.useRef(); if (!memoizedFn.current) { memoizedFn.current = function(...args) { return fnRef.current.apply(this, args); }; } return memoizedFn.current; } // src/utils.ts function isBrowser() { return typeof window !== "undefined" && typeof document !== "undefined"; } function isFunction(value) { return typeof value === "function"; } function omit(obj, keys) { const copy = __spreadValues({}, obj); keys.forEach((key) => delete copy[key]); return copy; } function classnames(...args) { return args.filter(Boolean).join(" "); } function defaults(options, defaultOptions) { const result = __spreadValues({}, defaultOptions); for (const key in options) { if (options[key] !== void 0) { result[key] = options[key]; } } return result; } function secToMs(ms) { return (ms || 0) * 1e3; } var defaultShouldUpdate = (a, b) => !Object.is(a, b); function usePrevious(state, shouldUpdate = defaultShouldUpdate) { const prevRef = react.useRef(); const curRef = react.useRef(); if (shouldUpdate(curRef.current, state)) { prevRef.current = curRef.current; curRef.current = state; } return prevRef.current; } var useUpdate = () => { const [, setState] = react.useState({}); return react.useCallback(() => setState({}), []); }; // src/hooks/use-controlled-state.ts function useControlledState(option) { const { defaultValue, value, onChange, beforeValue, postValue, onInit } = option; const isControlled = Object.prototype.hasOwnProperty.call(option, "value") && typeof value !== "undefined"; const initialValue = react.useMemo(() => { let init = value; if (isControlled) { init = value; } else if (defaultValue !== void 0) { init = isFunction(defaultValue) ? defaultValue() : defaultValue; } return init; }, []); react.useEffect(() => { onInit == null ? void 0 : onInit(initialValue); }, [initialValue]); const stateRef = react.useRef(initialValue); if (isControlled) { stateRef.current = value; } const previousState = usePrevious(stateRef.current); if (postValue) { const post = postValue(stateRef.current, previousState); if (post) { stateRef.current = post; } } const update = useUpdate(); function triggerChange(newValue) { let r = isFunction(newValue) ? newValue(stateRef.current) : newValue; if (beforeValue) { const before = beforeValue(r, stateRef.current); if (before) { r = before; } } if (onChange) { onChange(r, stateRef.current); } if (!isControlled) { stateRef.current = r; update(); } } return [stateRef.current, useMemoizedFn(triggerChange), previousState]; } var useIsoLayoutEffect = isBrowser() ? react.useLayoutEffect : react.useEffect; var createUpdateEffect = (hook) => (effect, deps) => { const isMounted = react.useRef(false); hook(() => { return () => { isMounted.current = false; }; }, []); hook(() => { if (!isMounted.current) { isMounted.current = true; } else { return effect(); } }, deps); }; var create_update_effect_default = createUpdateEffect; // src/hooks/use-update-iso-layout-effect.ts var useUpdateIsoLayoutEffect = create_update_effect_default(useIsoLayoutEffect); function Toast(props) { const { content, className, style, duration, onOpenChange, onClosed, pauseOnHover, hover, updateFlag, transition: _transition, open: _open, onEnter: _onEnter, onUpdate: _onUpdate, onExited: _onExited, offsetHeight } = props; const didMount = react.useRef(false); const transition = useMemoizedFn(() => { if (typeof _transition === "string") { return { transition: _transition }; } return { transition: _transition == null ? void 0 : _transition.name, duration: _transition == null ? void 0 : _transition.duration, exitDuration: _transition == null ? void 0 : _transition.exitDuration }; }); const timer = react.useRef(); const ref = react.useRef(null); const [open, setOpen] = useControlledState({ defaultValue: !!_open, value: _open, onChange: (value) => { onOpenChange(value); } }); const delayClear = useMemoizedFn(() => { if (duration) { timer.current && clearTimeout(timer.current); timer.current = window.setTimeout(() => { setOpen(false); timer.current && clearTimeout(timer.current); }, secToMs(duration)); } }); useUpdateIsoLayoutEffect(() => { if (!open) return; if (pauseOnHover) { if (hover) { timer.current && clearTimeout(timer.current); delayClear(); } } }, [hover]); const onEnter = useMemoizedFn(() => { if (!ref.current) return; const height = ref.current.clientHeight; _onEnter(height); }); const onUpdate = useMemoizedFn(() => { var _a; const height = (_a = ref.current) == null ? void 0 : _a.clientHeight; _onUpdate(height || 0); }); const onExited = useMemoizedFn(() => { var _a; const height = (_a = ref.current) == null ? void 0 : _a.clientHeight; _onExited(height || 0); onClosed == null ? void 0 : onClosed(); }); const resolveTransform = useMemoizedFn((transform, offsetHeight2) => { const internalTransform = `translate(-50%, calc(-50% - ${offsetHeight2}px))`; return [internalTransform, transform].filter(Boolean).join(" "); }); const resolveTransformProperty = useMemoizedFn((transform) => { return [.../* @__PURE__ */ new Set(["transform", transform])].filter(Boolean).join(", "); }); useIsoLayoutEffect(() => { if (!open) return; if (!didMount.current) { didMount.current = true; } else { onUpdate(); } delayClear(); return () => { timer.current && clearTimeout(timer.current); }; }, [updateFlag]); useIsoLayoutEffect(() => { if (!content) { onExited(); } }, [content]); return /* @__PURE__ */ jsxRuntime.jsx(reactTransitionPreset.Transition, __spreadProps(__spreadValues({ mounted: open }, transition()), { onEnter, onExited, initial: true, children: (styles) => /* @__PURE__ */ jsxRuntime.jsx( "div", { style: __spreadProps(__spreadValues(__spreadValues({ position: "fixed", zIndex: 9999, top: "50%", left: "50%" }, style), styles), { transitionProperty: resolveTransformProperty(styles.transitionProperty), transform: resolveTransform(styles.transform, offsetHeight) }), ref, className: classnames("toast__content", className), children: content } ) })); } var toast_default = Toast; function ToastContainer(props) { const { toasts, onClosed, onOpenChange } = props; const [hoverState, setHoverState] = react.useState(false); const [heightMap, setHeightMap] = react.useState(/* @__PURE__ */ new Map()); const onEnter = useMemoizedFn((key, height) => { setHeightMap((prev) => { prev.set(key, height); return new Map(prev); }); }); const onUpdate = useMemoizedFn(onEnter); const onExited = useMemoizedFn((key) => { setHeightMap((prev) => { prev.delete(key); return new Map(prev); }); }); const offsetHeight = useMemoizedFn((toast2) => { const index = toasts.findIndex((t) => t.key === toast2.key); let offset = 0; const start = toasts.length - 1; for (let i = start; i > index; i--) { const currentToastHeight = heightMap.get(toasts[i].key) || 0; const nextToastHeight = heightMap.get(toasts[i - 1].key) || 0; offset += nextToastHeight / 2 + currentToastHeight / 2 + toasts[index].gap; } return offset; }); return /* @__PURE__ */ jsxRuntime.jsx( "div", { onMouseEnter: () => { setHoverState(true); }, onMouseLeave: () => { setHoverState(false); }, className: "toast__container", children: toasts.map((toast2) => /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: toast2.render( /* @__PURE__ */ jsxRuntime.jsx( toast_default, __spreadProps(__spreadValues({}, omit(toast2, ["key"])), { onOpenChange: (open) => onOpenChange(toast2.key, open), onEnter: (height) => { onEnter(toast2.key, height); }, onUpdate: (height) => { onUpdate(toast2.key, height); }, onExited: () => { onExited(toast2.key); }, onClosed: () => { var _a; (_a = toast2.onClosed) == null ? void 0 : _a.call(toast2); onClosed(toast2.key); }, hover: hoverState, offsetHeight: offsetHeight(toast2) }) ) ) }, toast2.key)) } ); } var toast_container_default = ToastContainer; var MARK = "__react_root__"; function mount(node, container) { const root = container[MARK] || client.createRoot(container); root.render(node); container[MARK] = root; } var Renderer = class { constructor(queue) { __publicField(this, "containerID", "react-atom-toast"); __publicField(this, "container", null); __publicField(this, "queue"); this.queue = queue; } createContainer() { if (isBrowser()) { let container = document.getElementById(this.containerID); if (!container) { container = document.createElement("div"); container.id = this.containerID; document.body.appendChild(container); } this.container = container; } } render(toasts) { if (!toasts.length && this.container) { this.container.remove(); this.container = null; return; } this.createContainer(); if (!this.container) return; this.reactMount(toasts, this.container); } reactMount(toasts, container) { mount( /* @__PURE__ */ jsxRuntime.jsx( toast_container_default, { toasts, onClosed: (key) => this.queue.remove(key), onOpenChange: (key, open) => { this.queue.update(key, { open }); } } ), container ); } }; // src/toast-queue.ts var ToastQueue = class { constructor() { __publicField(this, "renderer"); __publicField(this, "toasts", []); this.renderer = new Renderer(this); } render() { this.renderer.render(this.toasts); } add(options) { var _a; const { maxCount, key } = options; if (key && this.toasts.some((t) => t.key === key)) { this.update(key, options); return; } const visibleToasts = this.toasts.filter((t) => t.open === true); if (maxCount && visibleToasts.length >= maxCount) { if (maxCount === 1) { for (let i = 0; i < visibleToasts.length - 1; i++) { this.remove(visibleToasts[i].key); } this.update(visibleToasts[visibleToasts.length - 1].key, __spreadProps(__spreadValues({}, options), { open: true })); return; } else { this.close((_a = visibleToasts[0]) == null ? void 0 : _a.key); } } const toastOptions = __spreadProps(__spreadValues({}, options), { key: options.key || Math.random().toString(36), open: true }); this.toasts.push(toastOptions); this.render(); } close(key) { if (!key) return; this.update(key, { open: false }); } closeAll() { this.toasts = this.toasts.map((toastOptions) => __spreadProps(__spreadValues({}, toastOptions), { open: false })); this.render(); } remove(key) { this.toasts = this.toasts.filter((toastOptions) => toastOptions.key !== key); this.render(); } removeAll() { this.toasts = []; this.render(); } update(key, options) { const index = this.toasts.findIndex((toastOptions) => toastOptions.key === key); if (index !== -1) { this.toasts[index] = __spreadProps(__spreadValues(__spreadValues({}, this.toasts[index]), options), { updateFlag: !this.toasts[index].updateFlag }); this.render(); } } }; // src/Toast.ts var Toast2 = class { constructor() { __publicField(this, "defaultOptions", { duration: 2, pauseOnHover: true, transition: "fade", maxCount: 3, gap: 16, render: (children) => children }); __publicField(this, "toastQueue"); __publicField(this, "open", (options) => { if (!(options && typeof options === "object" && "content" in options)) { options = { content: options }; } this.toastQueue.add(defaults(options, this.defaultOptions)); }); __publicField(this, "close", (key) => { this.toastQueue.close(key); }); __publicField(this, "closeAll", () => { this.toastQueue.closeAll(); }); __publicField(this, "update", (key, options) => { this.toastQueue.update(key, __spreadValues({}, options)); }); __publicField(this, "setDefaultOptions", (options) => { this.defaultOptions = defaults(options, this.defaultOptions); }); this.toastQueue = new ToastQueue(); } }; // src/index.ts var toast = new Toast2(); exports.Toast = Toast2; exports.toast = toast;