@zag-js/toast
Version:
Core logic for the toast widget implemented as a state machine
272 lines (270 loc) • 8.28 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/toast.store.ts
var toast_store_exports = {};
__export(toast_store_exports, {
createToastStore: () => createToastStore
});
module.exports = __toCommonJS(toast_store_exports);
var import_utils = require("@zag-js/utils");
var withDefaults = (options, defaults) => {
return { ...defaults, ...(0, import_utils.compact)(options) };
};
var priorities = {
error: [1, 2],
warning: [3, 6],
loading: [4, 5],
success: [5, 7],
info: [6, 8]
};
var DEFAULT_TYPE = "info";
var getPriorityForType = (type, hasAction) => {
const [actionable, nonActionable] = priorities[type ?? DEFAULT_TYPE];
return hasAction ? actionable : nonActionable;
};
var sortToastsByPriority = (toastArray) => {
return toastArray.sort((a, b) => {
const priorityA = a.priority ?? getPriorityForType(a.type, !!a.action);
const priorityB = b.priority ?? getPriorityForType(b.type, !!b.action);
return priorityA - priorityB;
});
};
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 = () => {
toastQueue = sortToastsByPriority(toastQueue);
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:${(0, import_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 {
const newToast = {
id,
duration: attrs.duration,
removeDelay: attrs.removeDelay,
type: DEFAULT_TYPE,
...data,
stacked: !attrs.overlap,
gap: attrs.gap
};
const priority = newToast.priority ?? getPriorityForType(newToast.type, !!newToast.action);
addToast({ ...newToast, priority });
}
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) {
(0, import_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 = (0, import_utils.runIfFn)(promise2).then(async (response) => {
result = ["resolve", response];
if (isHttpResponse(response) && !response.ok) {
removable = false;
const errorOptions = (0, import_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 = (0, import_utils.runIfFn)(options.success, response);
create({ ...shared, ...successOptions, id, type: successOptions.type ?? "success" });
}
}).catch(async (error2) => {
result = ["reject", error2];
if (options.error !== void 0) {
removable = false;
const errorOptions = (0, import_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";
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createToastStore
});