@nexim/snackbar
Version:
Snackbar component with signal capability.
8 lines (7 loc) • 13.1 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": ["debounce", "import_delay", "import_delay"]
}