UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

104 lines (91 loc) 3.73 kB
import type { ReactNode } from "react"; import { isAsync } from "../../util/async.js"; import { getMessage } from "../../util/error.js"; import type { Arguments } from "../../util/function.js"; import type { Status } from "../style/Status.js"; // Constants. const NOTICE_EVENT = "notice"; /** MessageEvent */ class NoticeEvent extends CustomEvent<{ message: ReactNode; status: Status | undefined }> {} /** * Notify the user with a message by dispatching a notice event. * - This is how e.g. `<Button>` and `<FormNotify>` components send notices to the `<Notices>` list of global notices. */ export function notify(message: ReactNode, status?: Status | undefined, el: EventTarget = window): void { el.dispatchEvent( new NoticeEvent(NOTICE_EVENT, { detail: { message, status }, bubbles: true, }), ); } /** Notify the user with a success message by dispatching a notice event. */ export function notifySuccess(message: ReactNode, el?: EventTarget) { notify(message, "success", el); } /** Notify the user with a success message by dispatching a notice event. */ export function notifyError(message: ReactNode, el?: EventTarget) { notify(message, "error", el); } /** Look at a thrown value, extract a viable message from it, and dispatch a notice event.. */ export function notifyThrown(thrown: unknown, el?: EventTarget) { const message = getMessage(thrown); if (message) { notifyError(message, el); } else { notifyError("Unknown error", el); console.error(thrown); } } /** Subscribe to notice events on the window and call a callback when they happen. */ export function subscribeNotices( callback: (message: ReactNode, status?: Status | undefined) => void, el: EventTarget = window, ): () => void { const listener = (e: Event) => { e.stopPropagation(); // Prevent bubbling to a higher notice subscriber. if (e instanceof NoticeEvent) callback(e.detail.message, e.detail.status); }; el.addEventListener(NOTICE_EVENT, listener); return () => el.removeEventListener(NOTICE_EVENT, listener); } /** Callback that can return or throw a string, which will trigger a success or error notice accordingly. */ export type NoticeCallback<A extends Arguments> = (...args: A) => PromiseLike<ReactNode | undefined | void> | ReactNode | undefined | void; /** Callback that publishes notices to the window if it returns or throws "string" */ export function callNotified<A extends Arguments>(callback: NoticeCallback<A>, ...args: A): boolean | Promise<boolean> { return callNotifiedElement(window, callback, ...args); } /** Await a value that publishes "success" or "error" notices to the window if it returns */ export function awaitNotified(pending: PromiseLike<ReactNode | undefined | void>): Promise<boolean> { return awaitNotifiedElement(window, pending); } /** Callback that publishes notices to an element (defaults to the window) if it returns or throws "string" */ export function callNotifiedElement<A extends Arguments>( el: EventTarget | undefined, callback: NoticeCallback<A>, ...args: A ): boolean | Promise<boolean> { try { const result = callback(...args); if (isAsync(result)) return awaitNotifiedElement(el, result); if (result) notifySuccess(result, el); return true; } catch (thrown) { notifyThrown(thrown, el); return false; } } /** Await a value that publishes "success" or "error" notices to an element (defaults to the window) if it returns */ export async function awaitNotifiedElement( el: EventTarget | undefined, pending: PromiseLike<ReactNode | undefined | void>, ): Promise<boolean> { try { const result = await pending; if (result) notifySuccess(result, el); return true; } catch (thrown) { notifyThrown(thrown, el); return false; } }