@nexim/snackbar
Version:
Snackbar component with signal capability.
8 lines (7 loc) • 13.5 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/main.ts", "../src/lib/handler.ts", "../src/lib/signal.ts", "../src/lib/element.ts", "../src/lib/utils.ts"],
"sourcesContent": ["import { packageTracer } from '@alwatr/package-tracer';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\nimport './lib/handler.js';\n\nexport * from './lib/element.js';\nexport { snackbarSignal } from './lib/signal.js';\n", "import { delay } from '@alwatr/delay';\nimport { createLogger } from '@alwatr/logger';\nimport debounce from 'debounce';\n\nimport { snackbarActionButtonClickedSignal, snackbarCloseButtonClickedSignal, snackbarSignal } from './signal.js';\n\nimport type { SnackbarElement } from './element.js';\nimport type { SnackbarActionHandler, SnackbarOptions } from './type.js';\n\nconst logger = createLogger(__package_name__);\n\n/**\n * Store the function to close the last snackbar.\n */\nlet closeLastSnackbar: (() => MaybePromise<void>) | null = null;\n\n/**\n * Store the function to unsubscribe the action button handler after close or action button clicked.\n */\nlet unsubscribeActionButtonHandler: (() => void) | null = null;\n\n/**\n * Store the function to unsubscribe the close button handler after close or action button clicked.\n */\nlet unsubscribeCloseButtonHandler: (() => void) | null = null;\n\n/**\n * Create snackbar element with given options.\n *\n * @param options - Options for configuring the snackbar.\n * @returns The created snackbar element.\n */\nfunction createSnackbarElement(options: SnackbarOptions): SnackbarElement {\n const element = document.createElement('snack-bar');\n element.setAttribute('content', options.content);\n\n if (options.addCloseButton === true) {\n element.setAttribute('add-close-button', '');\n }\n\n if (options.action != null) {\n element.setAttribute('action-button-label', options.action.label);\n }\n\n return element;\n}\n\n/**\n * Handle action button click.\n *\n * @param closeSnackbar - Function to close the snackbar.\n * @param handler - Handler to be called when the action button is clicked.\n */\nfunction handleActionButtonClick(closeSnackbar: () => Promise<void>, handler?: SnackbarActionHandler): Promise<void> {\n logger.logMethod?.('handleActionButtonClick');\n\n // non-blocking to handler done\n (async () => {\n try {\n await handler!();\n }\n catch (error) {\n logger.error('handleActionButtonClick', 'call_handler_failed', error);\n }\n })();\n\n return closeSnackbar();\n}\n\n/**\n * Displays the snackbar with the given options.\n *\n * @param options - Options for configuring the snackbar.\n */\nasync function showSnackbar(options: SnackbarOptions): Promise<void> {\n logger.logMethodArgs?.('showSnackbar', { options });\n\n // Set default duration if not provided\n options.duration ??= '5s';\n\n const element = createSnackbarElement(options);\n\n let closed = false;\n const closeSnackbar = async () => {\n if (closed) return;\n logger.logMethodArgs?.('closeSnackbar', { options });\n\n await element.close();\n unsubscribeActionButtonHandler?.();\n unsubscribeCloseButtonHandler?.();\n\n closed = true;\n };\n\n await closeLastSnackbar?.();\n closeLastSnackbar = closeSnackbar;\n\n if (options.action != null) {\n /**\n * Store the function to unsubscribe the action button handler after close or action button clicked.\n *\n * TODO: check why once not work\n */\n unsubscribeActionButtonHandler = snackbarActionButtonClickedSignal.subscribe(\n handleActionButtonClick.bind(null, closeSnackbar, options.action.handler),\n ).unsubscribe;\n }\n\n if (options.addCloseButton === true) {\n // TODO: check why once not work\n unsubscribeCloseButtonHandler = snackbarCloseButtonClickedSignal.subscribe(closeSnackbar.bind(null)).unsubscribe;\n }\n\n // Close the last snackbar if it exists\n document.body.appendChild(element);\n\n // Set a timeout to close the snackbar if duration is not infinite\n if (options.duration !== 'infinite') {\n delay.by(options.duration).then(closeSnackbar);\n }\n}\n\n// Debounce utility to only process the last signal value within a short interval.\nconst debouncedShowSnackbar = debounce((options: SnackbarOptions) => {\n showSnackbar(options);\n}, 50);\n\nsnackbarSignal.subscribe((options) => {\n debouncedShowSnackbar(options);\n});\n", "import { AlwatrSignal, AlwatrTrigger } from '@alwatr/flux';\n\nimport type { SnackbarOptions } from './type.js';\n\n/**\n * Signal triggered when the snackbar action button is clicked to close snackbar.\n */\nexport const snackbarActionButtonClickedSignal = /* @__PURE__ */ new AlwatrTrigger({\n name: 'snackbar-action-button-click',\n});\n\n/**\n * Signal triggered when the snackbar close button is clicked to close snackbar.\n */\nexport const snackbarCloseButtonClickedSignal = /* @__PURE__ */ new AlwatrTrigger({\n name: 'snackbar-close-button-click',\n});\n\n/**\n * Signal for displaying the snackbar.\n *\n * @example\n * import \\{snackbarSignal\\} from '\\@nexim/snackbar';\n *\n * snackbarSignal.notify(\\{\n * content: 'This is a snackbar message',\n * action: \\{\n * label: 'Undo',\n * handler: () =\\> \\{\n * console.log('Action button clicked');\n * \\},\n * \\},\n * duration: '5s',\n * addCloseButton: true,\n * \\});\n */\nexport const snackbarSignal = /* @__PURE__ */ new AlwatrSignal<SnackbarOptions>({ name: 'snackbar' });\n", "import { delay } from '@alwatr/delay';\nimport { lightDomMixin, loggerMixin } from '@nexim/element';\nimport { html, LitElement, nothing, type PropertyValues, type TemplateResult } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\n\nimport { snackbarActionButtonClickedSignal, snackbarCloseButtonClickedSignal } from './signal.js';\nimport { waitForNextFrame } from './utils.js';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'snack-bar': SnackbarElement;\n }\n}\n\n@customElement('snack-bar')\nexport class SnackbarElement extends lightDomMixin(loggerMixin(LitElement)) {\n /**\n * The content to be displayed inside the snackbar.\n */\n @property({ type: String }) content = '';\n\n /**\n * The label for the action button. If null, the action button will not be rendered.\n */\n @property({ type: String, attribute: 'action-button-label' }) actionButtonLabel: string | null = null;\n\n /**\n * Whether to add a close button to the snackbar.\n */\n @property({ type: Boolean, attribute: 'add-close-button' }) addCloseButton = false;\n\n /**\n * Duration for the open and close animation in milliseconds.\n */\n private static openAndCloseAnimationDuration__ = 200; // ms\n\n protected override firstUpdated(changedProperties: PropertyValues): void {\n super.firstUpdated(changedProperties);\n\n // wait for render complete, then open the snackbar to start the opening animation\n waitForNextFrame().then(() => {\n this.setAttribute('open', '');\n });\n }\n\n /**\n * Close the snackbar and remove it from the DOM.\n * Waits for the closing animation to end before removing the element.\n *\n * @internal\n * This method should be used by the package API, not directly, to ensure proper signal unsubscription.\n */\n async close(): Promise<void> {\n this.logger_.logMethod?.('close');\n\n this.removeAttribute('open');\n\n await delay.by(SnackbarElement.openAndCloseAnimationDuration__);\n this.remove();\n }\n\n /**\n * Handle the close button click event.\n * Sends a signal when the action button is clicked.\n */\n private closeButtonClickHandler__(): void {\n this.logger_.logMethod?.('closeButtonClickHandler__');\n\n snackbarCloseButtonClickedSignal.notify();\n }\n\n /**\n * Handle the action button click event.\n * Sends a signal when the action button is clicked.\n */\n private actionButtonClickHandler__(): void {\n this.logger_.logMethod?.('actionButtonClickHandler__');\n\n snackbarActionButtonClickedSignal.notify();\n }\n\n /**\n * Render the snackbar component.\n */\n protected override render(): unknown {\n super.render();\n\n const actionButtonHtml = this.renderActionButton__();\n const closeButtonHtml = this.renderCloseButton__();\n\n let actionButtonHandler: TemplateResult | typeof nothing = nothing;\n if (actionButtonHtml != nothing || closeButtonHtml != nothing) {\n actionButtonHandler = html`<div>${actionButtonHtml} ${closeButtonHtml}</div>`;\n }\n\n return [ html`<span>${this.content}</span>`, actionButtonHandler ];\n }\n\n /**\n * Render the action button.\n */\n private renderActionButton__(): TemplateResult | typeof nothing {\n if (this.actionButtonLabel == null) return nothing;\n this.logger_.logMethodArgs?.('renderActionButton__', { actionLabel: this.actionButtonLabel });\n\n return html` <button class=\"action-button\" @click=${this.actionButtonClickHandler__.bind(this)}>${this.actionButtonLabel}</button> `;\n }\n\n /**\n * Render the close button.\n */\n private renderCloseButton__(): TemplateResult | typeof nothing {\n if (!this.addCloseButton) return nothing;\n this.logger_.logMethod?.('renderCloseButton__');\n\n return html`\n <button class=\"close-button\" @click=${this.closeButtonClickHandler__.bind(this)}>\n <span class=\"icon\">close</span>\n </button>\n `;\n }\n}\n", "import { delay } from '@alwatr/delay';\n\n/**\n * Waits for the next frame to ensure the DOM has been fully calculated.\n * This minimizes the chance that querying the DOM will cause a costly reflow.\n *\n * This function uses `requestAnimationFrame` to schedule code to run immediately before the repaint,\n * followed by `setTimeout` with a delay of 0 to execute code as soon as possible after the repaint.\n *\n * @see https://stackoverflow.com/a/47184426\n */\nexport function waitForNextFrame(): Promise<void> {\n return new Promise((resolve) => {\n delay.untilNextAnimationFrame().then(() => {\n delay.immediate().then(resolve);\n });\n });\n}\n"],
"mappings": "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",
"names": ["delay", "delay", "delay"]
}