UNPKG

@zag-js/toast

Version:

Core logic for the toast widget implemented as a state machine

1,086 lines (1,079 loc) • 32.8 kB
'use strict'; var domQuery = require('@zag-js/dom-query'); var anatomy$1 = require('@zag-js/anatomy'); var core = require('@zag-js/core'); var dismissable = require('@zag-js/dismissable'); var utils = require('@zag-js/utils'); // src/toast-group.connect.ts var anatomy = anatomy$1.createAnatomy("toast").parts( "group", "root", "title", "description", "actionTrigger", "closeTrigger" ); var parts = anatomy.build(); // src/toast.dom.ts var getRegionId = (placement) => `toast-group:${placement}`; var getRegionEl = (ctx, placement) => ctx.getById(`toast-group:${placement}`); var getRootId = (ctx) => `toast:${ctx.id}`; var getRootEl = (ctx) => ctx.getById(getRootId(ctx)); var getTitleId = (ctx) => `toast:${ctx.id}:title`; var getDescriptionId = (ctx) => `toast:${ctx.id}:description`; var getCloseTriggerId = (ctx) => `toast${ctx.id}:close`; var defaultTimeouts = { info: 5e3, error: 5e3, success: 2e3, loading: Infinity, DEFAULT: 5e3 }; function getToastDuration(duration, type) { return duration ?? defaultTimeouts[type] ?? defaultTimeouts.DEFAULT; } var getOffsets = (offsets) => typeof offsets === "string" ? { left: offsets, right: offsets, bottom: offsets, top: offsets } : offsets; function getGroupPlacementStyle(service, placement) { const { prop, computed, context } = service; const { offsets, gap } = prop("store").attrs; const heights = context.get("heights"); const computedOffset = getOffsets(offsets); const rtl = prop("dir") === "rtl"; const computedPlacement = placement.replace("-start", rtl ? "-right" : "-left").replace("-end", rtl ? "-left" : "-right"); const isRighty = computedPlacement.includes("right"); const isLefty = computedPlacement.includes("left"); const styles = { position: "fixed", pointerEvents: computed("count") > 0 ? void 0 : "none", display: "flex", flexDirection: "column", "--gap": `${gap}px`, "--first-height": `${heights[0]?.height || 0}px`, zIndex: domQuery.MAX_Z_INDEX }; let alignItems = "center"; if (isRighty) alignItems = "flex-end"; if (isLefty) alignItems = "flex-start"; styles.alignItems = alignItems; if (computedPlacement.includes("top")) { const offset = computedOffset.top; styles.top = `max(env(safe-area-inset-top, 0px), ${offset})`; } if (computedPlacement.includes("bottom")) { const offset = computedOffset.bottom; styles.bottom = `max(env(safe-area-inset-bottom, 0px), ${offset})`; } if (!computedPlacement.includes("left")) { const offset = computedOffset.right; styles.insetInlineEnd = `calc(env(safe-area-inset-right, 0px) + ${offset})`; } if (!computedPlacement.includes("right")) { const offset = computedOffset.left; styles.insetInlineStart = `calc(env(safe-area-inset-left, 0px) + ${offset})`; } return styles; } function getPlacementStyle(service, visible) { const { prop, context, computed } = service; const parent = prop("parent"); const placement = parent.computed("placement"); const { gap } = parent.prop("store").attrs; const [side] = placement.split("-"); const mounted = context.get("mounted"); const remainingTime = context.get("remainingTime"); const height = computed("height"); const frontmost = computed("frontmost"); const sibling = !frontmost; const overlap = !prop("stacked"); const stacked = prop("stacked"); const type = prop("type"); const duration = type === "loading" ? Number.MAX_SAFE_INTEGER : remainingTime; const offset = computed("heightIndex") * gap + computed("heightBefore"); const styles = { position: "absolute", pointerEvents: "auto", "--opacity": "0", "--remove-delay": `${prop("removeDelay")}ms`, "--duration": `${duration}ms`, "--initial-height": `${height}px`, "--offset": `${offset}px`, "--index": prop("index"), "--z-index": computed("zIndex"), "--lift-amount": "calc(var(--lift) * var(--gap))", "--y": "100%", "--x": "0" }; const assign = (overrides) => Object.assign(styles, overrides); if (side === "top") { assign({ top: "0", "--sign": "-1", "--y": "-100%", "--lift": "1" }); } else if (side === "bottom") { assign({ bottom: "0", "--sign": "1", "--y": "100%", "--lift": "-1" }); } if (mounted) { assign({ "--y": "0", "--opacity": "1" }); if (stacked) { assign({ "--y": "calc(var(--lift) * var(--offset))", "--height": "var(--initial-height)" }); } } if (!visible) { assign({ "--opacity": "0", pointerEvents: "none" }); } if (sibling && overlap) { assign({ "--base-scale": "var(--index) * 0.05 + 1", "--y": "calc(var(--lift-amount) * var(--index))", "--scale": "calc(-1 * var(--base-scale))", "--height": "var(--first-height)" }); if (!visible) { assign({ "--y": "calc(var(--sign) * 40%)" }); } } if (sibling && stacked && !visible) { assign({ "--y": "calc(var(--lift) * var(--offset) + var(--lift) * -100%)" }); } if (frontmost && !visible) { assign({ "--y": "calc(var(--lift) * -100%)" }); } return styles; } function getGhostBeforeStyle(service, visible) { const { computed } = service; const styles = { position: "absolute", inset: "0", scale: "1 2", pointerEvents: visible ? "none" : "auto" }; const assign = (overrides) => Object.assign(styles, overrides); if (computed("frontmost") && !visible) { assign({ height: "calc(var(--initial-height) + 80%)" }); } return styles; } function getGhostAfterStyle() { return { position: "absolute", left: "0", height: "calc(var(--gap) + 2px)", bottom: "100%", width: "100%" }; } // src/toast-group.connect.ts function groupConnect(service, normalize) { const { context, prop, send, refs, computed } = service; return { getCount() { return context.get("toasts").length; }, getToasts() { return context.get("toasts"); }, getGroupProps(options = {}) { const { label = "Notifications" } = options; const { hotkey } = prop("store").attrs; const hotkeyLabel = hotkey.join("+").replace(/Key/g, "").replace(/Digit/g, ""); const placement = computed("placement"); const [side, align = "center"] = placement.split("-"); return normalize.element({ ...parts.group.attrs, dir: prop("dir"), tabIndex: -1, "aria-label": `${placement} ${label} ${hotkeyLabel}`, id: getRegionId(placement), "data-placement": placement, "data-side": side, "data-align": align, "aria-live": "polite", role: "region", style: getGroupPlacementStyle(service, placement), onMouseEnter() { if (refs.get("ignoreMouseTimer").isActive()) return; send({ type: "REGION.POINTER_ENTER", placement }); }, onMouseMove() { if (refs.get("ignoreMouseTimer").isActive()) return; send({ type: "REGION.POINTER_ENTER", placement }); }, onMouseLeave() { if (refs.get("ignoreMouseTimer").isActive()) return; send({ type: "REGION.POINTER_LEAVE", placement }); }, onFocus(event) { send({ type: "REGION.FOCUS", target: event.relatedTarget }); }, onBlur(event) { if (refs.get("isFocusWithin") && !domQuery.contains(event.currentTarget, event.relatedTarget)) { queueMicrotask(() => send({ type: "REGION.BLUR" })); } } }); }, subscribe(fn) { const store = prop("store"); return store.subscribe(() => fn(context.get("toasts"))); } }; } var { guards, createMachine } = core.setup(); var { and } = guards; var groupMachine = createMachine({ props({ props }) { return { dir: "ltr", id: utils.uuid(), ...props, store: props.store }; }, initialState({ prop }) { return prop("store").attrs.overlap ? "overlap" : "stack"; }, refs() { return { lastFocusedEl: null, isFocusWithin: false, isPointerWithin: false, ignoreMouseTimer: domQuery.AnimationFrame.create(), dismissableCleanup: void 0 }; }, context({ bindable }) { return { toasts: bindable(() => ({ defaultValue: [], sync: true, hash: (toasts) => toasts.map((t) => t.id).join(",") })), heights: bindable(() => ({ defaultValue: [], sync: true })) }; }, computed: { count: ({ context }) => context.get("toasts").length, overlap: ({ prop }) => prop("store").attrs.overlap, placement: ({ prop }) => prop("store").attrs.placement }, effects: ["subscribeToStore", "trackDocumentVisibility", "trackHotKeyPress"], watch({ track, context, action }) { track([() => context.hash("toasts")], () => { queueMicrotask(() => { action(["collapsedIfEmpty", "setDismissableBranch"]); }); }); }, exit: ["clearDismissableBranch", "clearLastFocusedEl", "clearMouseEventTimer"], on: { "DOC.HOTKEY": { actions: ["focusRegionEl"] }, "REGION.BLUR": [ { guard: and("isOverlapping", "isPointerOut"), target: "overlap", actions: ["collapseToasts", "resumeToasts", "restoreFocusIfPointerOut"] }, { guard: "isPointerOut", target: "stack", actions: ["resumeToasts", "restoreFocusIfPointerOut"] }, { actions: ["clearFocusWithin"] } ], "TOAST.REMOVE": { actions: ["removeToast", "removeHeight", "ignoreMouseEventsTemporarily"] }, "TOAST.PAUSE": { actions: ["pauseToasts"] } }, states: { stack: { on: { "REGION.POINTER_LEAVE": [ { guard: "isOverlapping", target: "overlap", actions: ["clearPointerWithin", "resumeToasts", "collapseToasts"] }, { actions: ["clearPointerWithin", "resumeToasts"] } ], "REGION.OVERLAP": { target: "overlap", actions: ["collapseToasts"] }, "REGION.FOCUS": { actions: ["setLastFocusedEl", "pauseToasts"] }, "REGION.POINTER_ENTER": { actions: ["setPointerWithin", "pauseToasts"] } } }, overlap: { on: { "REGION.STACK": { target: "stack", actions: ["expandToasts"] }, "REGION.POINTER_ENTER": { target: "stack", actions: ["setPointerWithin", "pauseToasts", "expandToasts"] }, "REGION.FOCUS": { target: "stack", actions: ["setLastFocusedEl", "pauseToasts", "expandToasts"] } } } }, implementations: { guards: { isOverlapping: ({ computed }) => computed("overlap"), isPointerOut: ({ refs }) => !refs.get("isPointerWithin") }, effects: { subscribeToStore({ context, prop }) { return prop("store").subscribe((toast) => { if (toast.dismiss) { context.set("toasts", (prev) => prev.filter((t) => t.id !== toast.id)); return; } context.set("toasts", (prev) => { const index = prev.findIndex((t) => t.id === toast.id); if (index !== -1) { return [...prev.slice(0, index), { ...prev[index], ...toast }, ...prev.slice(index + 1)]; } return [toast, ...prev]; }); }); }, trackHotKeyPress({ prop, send }) { const handleKeyDown = (event) => { const { hotkey } = prop("store").attrs; const isHotkeyPressed = hotkey.every((key) => event[key] || event.code === key); if (!isHotkeyPressed) return; send({ type: "DOC.HOTKEY" }); }; return domQuery.addDomEvent(document, "keydown", handleKeyDown, { capture: true }); }, trackDocumentVisibility({ prop, send, scope }) { const { pauseOnPageIdle } = prop("store").attrs; if (!pauseOnPageIdle) return; const doc = scope.getDoc(); return domQuery.addDomEvent(doc, "visibilitychange", () => { const isHidden = doc.visibilityState === "hidden"; send({ type: isHidden ? "PAUSE_ALL" : "RESUME_ALL" }); }); } }, actions: { setDismissableBranch({ refs, context, computed, scope }) { const toasts = context.get("toasts"); const placement = computed("placement"); const hasToasts = toasts.length > 0; if (!hasToasts) { refs.get("dismissableCleanup")?.(); return; } if (hasToasts && refs.get("dismissableCleanup")) { return; } const groupEl = () => getRegionEl(scope, placement); const cleanup = dismissable.trackDismissableBranch(groupEl, { defer: true }); refs.set("dismissableCleanup", cleanup); }, clearDismissableBranch({ refs }) { refs.get("dismissableCleanup")?.(); }, focusRegionEl({ scope, computed }) { queueMicrotask(() => { getRegionEl(scope, computed("placement"))?.focus(); }); }, pauseToasts({ prop }) { prop("store").pause(); }, resumeToasts({ prop }) { prop("store").resume(); }, expandToasts({ prop }) { prop("store").expand(); }, collapseToasts({ prop }) { prop("store").collapse(); }, removeToast({ prop, event }) { prop("store").remove(event.id); }, removeHeight({ event, context }) { if (event?.id == null) return; queueMicrotask(() => { context.set("heights", (heights) => heights.filter((height) => height.id !== event.id)); }); }, collapsedIfEmpty({ send, computed }) { if (!computed("overlap") || computed("count") > 1) return; send({ type: "REGION.OVERLAP" }); }, setLastFocusedEl({ refs, event }) { if (refs.get("isFocusWithin") || !event.target) return; refs.set("isFocusWithin", true); refs.set("lastFocusedEl", event.target); }, restoreFocusIfPointerOut({ refs }) { if (!refs.get("lastFocusedEl") || refs.get("isPointerWithin")) return; refs.get("lastFocusedEl")?.focus({ preventScroll: true }); refs.set("lastFocusedEl", null); refs.set("isFocusWithin", false); }, setPointerWithin({ refs }) { refs.set("isPointerWithin", true); }, clearPointerWithin({ refs }) { refs.set("isPointerWithin", false); if (refs.get("lastFocusedEl") && !refs.get("isFocusWithin")) { refs.get("lastFocusedEl")?.focus({ preventScroll: true }); refs.set("lastFocusedEl", null); } }, clearFocusWithin({ refs }) { refs.set("isFocusWithin", false); }, clearLastFocusedEl({ refs }) { if (!refs.get("lastFocusedEl")) return; refs.get("lastFocusedEl")?.focus({ preventScroll: true }); refs.set("lastFocusedEl", null); refs.set("isFocusWithin", false); }, ignoreMouseEventsTemporarily({ refs }) { refs.get("ignoreMouseTimer").request(); }, clearMouseEventTimer({ refs }) { refs.get("ignoreMouseTimer").cancel(); } } } }); function connect(service, normalize) { const { state, send, prop, scope, context, computed } = service; const visible = state.hasTag("visible"); const paused = state.hasTag("paused"); const mounted = context.get("mounted"); const frontmost = computed("frontmost"); const placement = prop("parent").computed("placement"); const type = prop("type"); const stacked = prop("stacked"); const title = prop("title"); const description = prop("description"); const action = prop("action"); const [side, align = "center"] = placement.split("-"); return { type, title, description, placement, visible, paused, closable: !!prop("closable"), pause() { send({ type: "PAUSE" }); }, resume() { send({ type: "RESUME" }); }, dismiss() { send({ type: "DISMISS", src: "programmatic" }); }, getRootProps() { return normalize.element({ ...parts.root.attrs, dir: prop("dir"), id: getRootId(scope), "data-state": visible ? "open" : "closed", "data-type": type, "data-placement": placement, "data-align": align, "data-side": side, "data-mounted": domQuery.dataAttr(mounted), "data-paused": domQuery.dataAttr(paused), "data-first": domQuery.dataAttr(frontmost), "data-sibling": domQuery.dataAttr(!frontmost), "data-stack": domQuery.dataAttr(stacked), "data-overlap": domQuery.dataAttr(!stacked), role: "status", "aria-atomic": "true", "aria-describedby": description ? getDescriptionId(scope) : void 0, "aria-labelledby": title ? getTitleId(scope) : void 0, tabIndex: 0, style: getPlacementStyle(service, visible), onKeyDown(event) { if (event.defaultPrevented) return; if (event.key == "Escape") { send({ type: "DISMISS", src: "keyboard" }); event.preventDefault(); } } }); }, /* Leave a ghost div to avoid setting hover to false when transitioning out */ getGhostBeforeProps() { return normalize.element({ "data-ghost": "before", style: getGhostBeforeStyle(service, visible) }); }, /* Needed to avoid setting hover to false when in between toasts */ getGhostAfterProps() { return normalize.element({ "data-ghost": "after", style: getGhostAfterStyle() }); }, getTitleProps() { return normalize.element({ ...parts.title.attrs, id: getTitleId(scope) }); }, getDescriptionProps() { return normalize.element({ ...parts.description.attrs, id: getDescriptionId(scope) }); }, getActionTriggerProps() { return normalize.button({ ...parts.actionTrigger.attrs, type: "button", onClick(event) { if (event.defaultPrevented) return; action?.onClick?.(); send({ type: "DISMISS", src: "user" }); } }); }, getCloseTriggerProps() { return normalize.button({ id: getCloseTriggerId(scope), ...parts.closeTrigger.attrs, type: "button", "aria-label": "Dismiss notification", onClick(event) { if (event.defaultPrevented) return; send({ type: "DISMISS", src: "user" }); } }); } }; } var { not } = core.createGuards(); var machine = core.createMachine({ props({ props }) { utils.ensureProps(props, ["id", "type", "parent", "removeDelay"], "toast"); return { closable: true, ...props, duration: getToastDuration(props.duration, props.type) }; }, initialState({ prop }) { const persist = prop("type") === "loading" || prop("duration") === Infinity; return persist ? "visible:persist" : "visible"; }, context({ prop, bindable }) { return { remainingTime: bindable(() => ({ defaultValue: getToastDuration(prop("duration"), prop("type")) })), createdAt: bindable(() => ({ defaultValue: Date.now() })), mounted: bindable(() => ({ defaultValue: false })), initialHeight: bindable(() => ({ defaultValue: 0 })) }; }, refs() { return { closeTimerStartTime: Date.now(), lastCloseStartTimerStartTime: 0 }; }, computed: { zIndex: ({ prop }) => { const toasts = prop("parent").context.get("toasts"); const index = toasts.findIndex((toast) => toast.id === prop("id")); return toasts.length - index; }, height: ({ prop }) => { const heights = prop("parent").context.get("heights"); const height = heights.find((height2) => height2.id === prop("id")); return height?.height ?? 0; }, heightIndex: ({ prop }) => { const heights = prop("parent").context.get("heights"); return heights.findIndex((height) => height.id === prop("id")); }, frontmost: ({ prop }) => prop("index") === 0, heightBefore: ({ prop }) => { const heights = prop("parent").context.get("heights"); const heightIndex = heights.findIndex((height) => height.id === prop("id")); return heights.reduce((prev, curr, reducerIndex) => { if (reducerIndex >= heightIndex) return prev; return prev + curr.height; }, 0); }, shouldPersist: ({ prop }) => prop("type") === "loading" || prop("duration") === Infinity }, watch({ track, prop, send }) { track([() => prop("message")], () => { const message = prop("message"); if (message) send({ type: message, src: "programmatic" }); }); track([() => prop("type"), () => prop("duration")], () => { send({ type: "UPDATE" }); }); }, on: { UPDATE: [ { guard: "shouldPersist", target: "visible:persist", actions: ["resetCloseTimer"] }, { target: "visible:updating", actions: ["resetCloseTimer"] } ], MEASURE: { actions: ["measureHeight"] } }, entry: ["setMounted", "measureHeight", "invokeOnVisible"], effects: ["trackHeight"], states: { "visible:updating": { tags: ["visible", "updating"], effects: ["waitForNextTick"], on: { SHOW: { target: "visible" } } }, "visible:persist": { tags: ["visible", "paused"], on: { RESUME: { guard: not("isLoadingType"), target: "visible", actions: ["setCloseTimer"] }, DISMISS: { target: "dismissing" } } }, visible: { tags: ["visible"], effects: ["waitForDuration"], on: { DISMISS: { target: "dismissing" }, PAUSE: { target: "visible:persist", actions: ["syncRemainingTime"] } } }, dismissing: { entry: ["invokeOnDismiss"], effects: ["waitForRemoveDelay"], on: { REMOVE: { target: "unmounted", actions: ["notifyParentToRemove"] } } }, unmounted: { entry: ["invokeOnUnmount"] } }, implementations: { effects: { waitForRemoveDelay({ prop, send }) { return utils.setRafTimeout(() => { send({ type: "REMOVE", src: "timer" }); }, prop("removeDelay")); }, waitForDuration({ send, context, computed }) { if (computed("shouldPersist")) return; return utils.setRafTimeout(() => { send({ type: "DISMISS", src: "timer" }); }, context.get("remainingTime")); }, waitForNextTick({ send }) { return utils.setRafTimeout(() => { send({ type: "SHOW", src: "timer" }); }, 0); }, trackHeight({ scope, prop }) { let cleanup; domQuery.raf(() => { const rootEl = getRootEl(scope); if (!rootEl) return; const syncHeight = () => { const originalHeight = rootEl.style.height; rootEl.style.height = "auto"; const height = rootEl.getBoundingClientRect().height; rootEl.style.height = originalHeight; const item = { id: prop("id"), height }; setHeight(prop("parent"), item); }; const win = scope.getWin(); const observer = new win.MutationObserver(syncHeight); observer.observe(rootEl, { childList: true, subtree: true, characterData: true }); cleanup = () => observer.disconnect(); }); return () => cleanup?.(); } }, guards: { isLoadingType: ({ prop }) => prop("type") === "loading", shouldPersist: ({ computed }) => computed("shouldPersist") }, actions: { setMounted({ context }) { domQuery.raf(() => { context.set("mounted", true); }); }, measureHeight({ scope, prop, context }) { queueMicrotask(() => { const rootEl = getRootEl(scope); if (!rootEl) return; const originalHeight = rootEl.style.height; rootEl.style.height = "auto"; const height = rootEl.getBoundingClientRect().height; rootEl.style.height = originalHeight; context.set("initialHeight", height); const item = { id: prop("id"), height }; setHeight(prop("parent"), item); }); }, setCloseTimer({ refs }) { refs.set("closeTimerStartTime", Date.now()); }, resetCloseTimer({ context, refs, prop }) { refs.set("closeTimerStartTime", Date.now()); context.set("remainingTime", getToastDuration(prop("duration"), prop("type"))); }, syncRemainingTime({ context, refs }) { context.set("remainingTime", (prev) => { const closeTimerStartTime = refs.get("closeTimerStartTime"); const elapsedTime = Date.now() - closeTimerStartTime; refs.set("lastCloseStartTimerStartTime", Date.now()); return prev - elapsedTime; }); }, notifyParentToRemove({ prop }) { const parent = prop("parent"); parent.send({ type: "TOAST.REMOVE", id: prop("id") }); }, invokeOnDismiss({ prop, event }) { prop("onStatusChange")?.({ status: "dismissing", src: event.src }); }, invokeOnUnmount({ prop }) { prop("onStatusChange")?.({ status: "unmounted" }); }, invokeOnVisible({ prop }) { prop("onStatusChange")?.({ status: "visible" }); } } } }); function setHeight(parent, item) { const { id, height } = item; parent.context.set("heights", (prev) => { const alreadyExists = prev.find((i) => i.id === id); if (!alreadyExists) { return [{ id, height }, ...prev]; } else { return prev.map((i) => i.id === id ? { ...i, height } : i); } }); } var withDefaults = (options, defaults) => { return { ...defaults, ...utils.compact(options) }; }; function createToastStore(props = {}) { const attrs = withDefaults(props, { placement: "bottom", overlap: false, max: 24, gap: 16, offsets: "1rem", hotkey: ["altKey", "KeyT"], removeDelay: 200, pauseOnPageIdle: true }); let subscribers = []; let toasts = []; let dismissedToasts = /* @__PURE__ */ new Set(); let toastQueue = []; const subscribe = (subscriber) => { subscribers.push(subscriber); return () => { const index = subscribers.indexOf(subscriber); subscribers.splice(index, 1); }; }; const publish = (data) => { subscribers.forEach((subscriber) => subscriber(data)); return data; }; const addToast = (data) => { if (toasts.length >= attrs.max) { toastQueue.push(data); return; } publish(data); toasts.unshift(data); }; const processQueue = () => { while (toastQueue.length > 0 && toasts.length < attrs.max) { const nextToast = toastQueue.shift(); if (nextToast) { publish(nextToast); toasts.unshift(nextToast); } } }; const create = (data) => { const id = data.id ?? `toast:${utils.uuid()}`; const exists = toasts.find((toast) => toast.id === id); if (dismissedToasts.has(id)) dismissedToasts.delete(id); if (exists) { toasts = toasts.map((toast) => { if (toast.id === id) { return publish({ ...toast, ...data, id }); } return toast; }); } else { addToast({ id, duration: attrs.duration, removeDelay: attrs.removeDelay, type: "info", ...data, stacked: !attrs.overlap, gap: attrs.gap }); } return id; }; const remove = (id) => { dismissedToasts.add(id); if (!id) { toasts.forEach((toast) => { subscribers.forEach((subscriber) => subscriber({ id: toast.id, dismiss: true })); }); toasts = []; toastQueue = []; } else { subscribers.forEach((subscriber) => subscriber({ id, dismiss: true })); toasts = toasts.filter((toast) => toast.id !== id); processQueue(); } return id; }; const error = (data) => { return create({ ...data, type: "error" }); }; const success = (data) => { return create({ ...data, type: "success" }); }; const info = (data) => { return create({ ...data, type: "info" }); }; const warning = (data) => { return create({ ...data, type: "warning" }); }; const loading = (data) => { return create({ ...data, type: "loading" }); }; const getVisibleToasts = () => { return toasts.filter((toast) => !dismissedToasts.has(toast.id)); }; const getCount = () => { return toasts.length; }; const promise = (promise2, options, shared = {}) => { if (!options || !options.loading) { utils.warn("[zag-js > toast] toaster.promise() requires at least a 'loading' option to be specified"); return; } const id = create({ ...shared, ...options.loading, promise: promise2, type: "loading" }); let removable = true; let result; const prom = utils.runIfFn(promise2).then(async (response) => { result = ["resolve", response]; if (isHttpResponse(response) && !response.ok) { removable = false; const errorOptions = utils.runIfFn(options.error, `HTTP Error! status: ${response.status}`); create({ ...shared, ...errorOptions, id, type: "error" }); } else if (options.success !== void 0) { removable = false; const successOptions = utils.runIfFn(options.success, response); create({ ...shared, ...successOptions, id, type: "success" }); } }).catch(async (error2) => { result = ["reject", error2]; if (options.error !== void 0) { removable = false; const errorOptions = utils.runIfFn(options.error, error2); create({ ...shared, ...errorOptions, id, type: "error" }); } }).finally(() => { if (removable) { remove(id); } options.finally?.(); }); const unwrap = () => new Promise( (resolve, reject) => prom.then(() => result[0] === "reject" ? reject(result[1]) : resolve(result[1])).catch(reject) ); return { id, unwrap }; }; const update = (id, data) => { return create({ id, ...data }); }; const pause = (id) => { if (id != null) { toasts = toasts.map((toast) => { if (toast.id === id) return publish({ ...toast, message: "PAUSE" }); return toast; }); } else { toasts = toasts.map((toast) => publish({ ...toast, message: "PAUSE" })); } }; const resume = (id) => { if (id != null) { toasts = toasts.map((toast) => { if (toast.id === id) return publish({ ...toast, message: "RESUME" }); return toast; }); } else { toasts = toasts.map((toast) => publish({ ...toast, message: "RESUME" })); } }; const dismiss = (id) => { if (id != null) { toasts = toasts.map((toast) => { if (toast.id === id) return publish({ ...toast, message: "DISMISS" }); return toast; }); } else { toasts = toasts.map((toast) => publish({ ...toast, message: "DISMISS" })); } }; const isVisible = (id) => { return !dismissedToasts.has(id) && !!toasts.find((toast) => toast.id === id); }; const isDismissed = (id) => { return dismissedToasts.has(id); }; const expand = () => { toasts = toasts.map((toast) => publish({ ...toast, stacked: true })); }; const collapse = () => { toasts = toasts.map((toast) => publish({ ...toast, stacked: false })); }; return { attrs, subscribe, create, update, remove, dismiss, error, success, info, warning, loading, getVisibleToasts, getCount, promise, pause, resume, isVisible, isDismissed, expand, collapse }; } var isHttpResponse = (data) => { return data && typeof data === "object" && "ok" in data && typeof data.ok === "boolean" && "status" in data && typeof data.status === "number"; }; // src/index.ts var group = { connect: groupConnect, machine: groupMachine }; exports.anatomy = anatomy; exports.connect = connect; exports.createStore = createToastStore; exports.group = group; exports.machine = machine;