UNPKG

@carbon/ibm-products-web-components

Version:
391 lines (389 loc) 14.5 kB
/** * Copyright IBM Corp. 2020, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import "../../globals/settings.js"; import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.js"; import { dateTimeLocaleContext } from "./date-time-context.js"; import notification_panel_default$1 from "./notification-panel.scss.js"; import { classMap } from "lit/directives/class-map.js"; import { LitElement, html } from "lit"; import { property, query, queryAssignedElements, state } from "lit/decorators.js"; import '@carbon/web-components/es-custom/components/button/index.js'; import HostListener from "@carbon/web-components/es/globals/decorators/host-listener.js"; import HostListenerMixin from "@carbon/web-components/es/globals/mixins/host-listener.js"; import { selectorTabbable } from "@carbon/web-components/es/globals/settings.js"; import { carbonElement } from "@carbon/web-components/es/globals/decorators/carbon-element.js"; import '@carbon/web-components/es-custom/components/icon-button/index.js'; import { provide } from "@lit/context"; import '@carbon/web-components/es-custom/components/toggle/index.js'; import "@carbon-labs/wc-empty-state/es/index.js"; //#region src/components/notification-panel/notification-panel.ts /** * @license * * Copyright IBM Corp. 2023, 2024 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const blockClass = `c4p--notifications-panel`; /** * NotificationPanel. * * @element c4p-notification-panel * @csspart dialog The dialog. * @slot today - Today Section. * @slot previous - Previous Section. * @slot footer - Footer for the Panel. * @fires c4p-notification-panel-beingclosed - The custom event before notification-panel is closed. * @fires c4p-notification-dismiss-all - The custom event fired after notification-panel is closed upon a user gesture. * @fires c4p-notification-donot-disturb-change - The custom event fired after notification-panel is closed upon a user gesture. * @fires c4p-notification-click-outside - The custom event fired after user clicks outside the panel or Esc key is pressed. */ let CDSNotificationPanel = class CDSNotificationPanel extends HostListenerMixin(LitElement) { constructor(..._args) { super(..._args); this.open = false; this._hasTodayContent = false; this._hasPreviousContent = false; this._handleKeydown = ({ key, target }) => { if (key === "Escape") { this.open = false; const init = { bubbles: true, cancelable: true, composed: true, detail: { triggeredBy: target } }; this.dispatchEvent(new CustomEvent(this.constructor.eventClickOutside, init)); this.triggerButtonRef?.focus(); } }; this._handleClick = (event) => { const target = event.target; if (!this.open || this.contains(target)) return; const isActionable = [ "a", "button", "input", "select", "textarea", "[role=\"button\"]", "[role=\"link\"]", "[tabindex]:not([tabindex=\"-1\"]", "cds-custom-button", "cds-custom-link", "cds-custom-accordion-item", "cds-custom-breadcrumb-item", "cds-custom-icon-button", "cds-custom-checkbox", "cds-custom-dropdown", "cds-custom-dropdown-item", "cds-custom-file-uploader", "cds-custom-file-uploader-button", "cds-custom-menu-button", "cds-custom-menu", "cds-custom-menu-item", "cds-custom-multi-select", "cds-custom-multi-select-item", "cds-custom-number-input", "cds-custom-overflow-menu", "cds-custom-overflow-menu-body", "cds-custom-overflow-menu-item", "cds-custom-pagination", "cds-custom-select-item", "cds-custom-radio-button", "cds-custom-search", "cds-custom-select", "cds-custom-slider", "cds-custom-slider-input", "cds-custom-tabs", "cds-custom-tab", "cds-custom-textarea", "cds-custom-text-input", "cds-custom-time-picker", "cds-custom-time-picker-select", "cds-custom-select-item", "cds-custom-toggle", "cds-custom-toggletip" ].some((selector) => { if (!(target instanceof Element)) return false; if (target.closest(selector)) return true; const root = target.getRootNode(); return root instanceof ShadowRoot && root.host instanceof Element && root.host.matches(selector); }); if (this.open && this.triggerButtonRef?.contains(target)) return; const init = { bubbles: true, cancelable: true, composed: true, detail: { triggeredBy: target } }; if (this.dispatchEvent(new CustomEvent(this.constructor.eventBeforeClose, init))) { this.open = false; this.dispatchEvent(new CustomEvent(this.constructor.eventClickOutside, init)); if (!isActionable) this.triggerButtonRef?.focus(); } }; } willUpdate(changedProperties) { if (changedProperties.has("dateTimeLocale")) this._providedLocale = this.dateTimeLocale; if (changedProperties.has("_todayElements") || changedProperties.has("_previousElements")) { this._hasTodayContent = this._todayElements.length > 0; this._hasPreviousContent = this._previousElements.length > 0; } } firstUpdated() { this.todaySlot?.addEventListener("slotchange", () => this._handleSlotChange("today")); this.previousSlot?.addEventListener("slotchange", () => this._handleSlotChange("previous")); this._markFirstNotification(); const addNotificationListeners = (slotName) => { const slottedElements = (this.shadowRoot?.querySelector(`slot[name="${slotName}"]`))?.assignedElements({ flatten: true }) || []; for (const el of slottedElements) { el.addEventListener("mouseenter", () => { this._handleMouseEnter(el); }); el.addEventListener("mouseleave", () => { this._handleMouseLeave(el); }); el.addEventListener("focusin", () => { this._handleFocusIn(el); }); el.addEventListener("focusout", () => { this._handleFocusOut(el); }); } }; addNotificationListeners("today"); addNotificationListeners("previous"); } updated() { this._markFirstNotification(); if (this.open) requestAnimationFrame(() => this._tryFocusDismissButton()); } render() { const { titleText, todayText, previousText, dismissAllLabel, emptyStateLabel, doNotDisturbLabel, open, _hasTodayContent, _hasPreviousContent, _onDismissAllNotifications: onDismissAllNotifications, _handleToggle: handleToggle } = this; return html` <div role="dialog" tabindex="0" class=${classMap({ [`${blockClass}__container`]: true, [`${blockClass}__entrance`]: open, [`${blockClass}__exit`]: !open })}> <div class="${blockClass}__header-container"> <div class="${blockClass}__header-flex"> <h2 class="${blockClass}__header">${titleText}</h2> <cds-custom-button size="sm" kind="ghost" class="${blockClass}__dismiss-button" @click=${onDismissAllNotifications} > ${dismissAllLabel} </cds-custom-button> </div> <cds-custom-toggle size="sm" class="${blockClass}__do-not-disturb-toggle" id="${blockClass}__do-not-disturb-toggle-component" label-a=${doNotDisturbLabel} label-b=${doNotDisturbLabel} aria-label=${doNotDisturbLabel} @cds-custom-toggle-changed=${handleToggle} ></cds-custom-toggle> </div> <div class=${classMap({ [`${blockClass}__main-section`]: true, [`${blockClass}__main-section-empty`]: !_hasTodayContent && !_hasPreviousContent })}> ${_hasTodayContent ? html` <h3 class="${blockClass}__time-section-label ${blockClass}__time-section-label--today" > ${todayText} </h3> ` : ""} <slot name="today"></slot> ${_hasPreviousContent ? html` <h3 class="${blockClass}__time-section-label ${blockClass}__time-section-label--previous" > ${previousText} </h3> ` : ""} <slot name="previous"></slot> ${!_hasTodayContent && !_hasPreviousContent ? html` <slot name="empty-state"> <clabs-empty-state subtitle="${emptyStateLabel}" kind="notifications" > </clabs-empty-state> </slot>` : ""} </div> ${_hasTodayContent || _hasPreviousContent ? html`<div class="${blockClass}__bottom-actions-container"> <slot name="footer"></slot> </div>` : ""} </div> `; } disconnectedCallback() { super.disconnectedCallback(); this._mutationObserver?.disconnect(); } _handleMouseEnter(el) { const next = el.nextElementSibling; if (next?.tagName.toLowerCase() === `c4p-notification`) next.classList.add(`${blockClass}__notification--next`); } _handleMouseLeave(el) { const next = el.nextElementSibling; if (next?.tagName.toLowerCase() === `c4p-notification`) next.classList.remove(`${blockClass}__notification--next`); } _handleFocusIn(el) { const next = el.nextElementSibling; if (next?.tagName.toLowerCase() === `c4p-notification`) next.classList.add(`${blockClass}__notification--next`); } _handleFocusOut(el) { const next = el.nextElementSibling; if (next?.tagName.toLowerCase() === `c4p-notification`) next.classList.remove(`${blockClass}__notification--next`); } _markFirstNotification() { this.querySelectorAll(`c4p-notification`).forEach((el, i) => { el.classList.toggle("first-notification", i === 0); }); } _handleSlotChange(slotName) { slotName === "today" ? this._hasTodayContent = this._todayElements.length > 0 : this._hasPreviousContent = this._previousElements.length > 0; } _tryFocusDismissButton() { const button = this.renderRoot.querySelector(`.${blockClass}__dismiss-button`); if (button) button.focus(); else { this._mutationObserver?.disconnect(); this._mutationObserver = new MutationObserver(() => { const btn = this.renderRoot.querySelector(`.${blockClass}__dismiss-button`); if (btn) { btn.focus(); this._mutationObserver?.disconnect(); } }); this._mutationObserver.observe(this.renderRoot, { childList: true, subtree: true }); } } /** * Handles user-initiated dismiss of all notifications. * * @param event The event that triggered the click. */ _onDismissAllNotifications(event) { const triggeredBy = event.target; event.stopPropagation(); const init = { bubbles: true, cancelable: true, composed: true, detail: { triggeredBy } }; this.dispatchEvent(new CustomEvent(this.constructor.eventDismissAll, init)); } _handleToggle(event) { const init = { bubbles: true, cancelable: true, composed: true, detail: { triggeredBy: event.target } }; this.dispatchEvent(new CustomEvent(this.constructor.eventDonotDisturbChange, init)); } /** * A selector selecting tabbable nodes. */ static get selectorTabbable() { return selectorTabbable; } /** * The name of the custom event fired before this notification-panel is being closed upon a user gesture. * Cancellation of this event stops the user-initiated action of closing this notification-panel. */ static get eventBeforeClose() { return `c4p-notification-panel-beingclosed`; } /** * The name of the custom event fired after this notification-panel is closed upon a user gesture. */ static get eventClickOutside() { return `c4p-notification-click-outside`; } /** * The name of the custom event fired after dismiss all button click */ static get eventDismissAll() { return `c4p-notification-dismiss-all`; } /** * The name of the custom event fired after this do not disturb button toggled. */ static get eventDonotDisturbChange() { return `c4p-notification-donot-disturb-change`; } static { this.styles = notification_panel_default$1; } }; __decorate([property({ type: String, attribute: "title-text" })], CDSNotificationPanel.prototype, "titleText", void 0); __decorate([property({ type: String, attribute: "today-text" })], CDSNotificationPanel.prototype, "todayText", void 0); __decorate([property({ type: String, attribute: "previous-text" })], CDSNotificationPanel.prototype, "previousText", void 0); __decorate([property({ reflect: true, type: Boolean })], CDSNotificationPanel.prototype, "open", void 0); __decorate([property({ type: String, attribute: "dismiss-all-label" })], CDSNotificationPanel.prototype, "dismissAllLabel", void 0); __decorate([property({ type: String, attribute: "empty-state-label" })], CDSNotificationPanel.prototype, "emptyStateLabel", void 0); __decorate([property({ type: String, attribute: "donot-disturb-label" })], CDSNotificationPanel.prototype, "doNotDisturbLabel", void 0); __decorate([property({ type: Object })], CDSNotificationPanel.prototype, "triggerButtonRef", void 0); __decorate([property({ type: String, attribute: "date-time-locale" })], CDSNotificationPanel.prototype, "dateTimeLocale", void 0); __decorate([provide({ context: dateTimeLocaleContext })], CDSNotificationPanel.prototype, "_providedLocale", void 0); __decorate([query("slot[name=\"today\"]")], CDSNotificationPanel.prototype, "todaySlot", void 0); __decorate([query("slot[name=\"previous\"]")], CDSNotificationPanel.prototype, "previousSlot", void 0); __decorate([state()], CDSNotificationPanel.prototype, "_hasTodayContent", void 0); __decorate([state()], CDSNotificationPanel.prototype, "_hasPreviousContent", void 0); __decorate([queryAssignedElements({ slot: "today", flatten: true })], CDSNotificationPanel.prototype, "_todayElements", void 0); __decorate([queryAssignedElements({ slot: "previous", flatten: true })], CDSNotificationPanel.prototype, "_previousElements", void 0); __decorate([HostListener("keydown")], CDSNotificationPanel.prototype, "_handleKeydown", void 0); __decorate([HostListener("document:click")], CDSNotificationPanel.prototype, "_handleClick", void 0); CDSNotificationPanel = __decorate([carbonElement(`c4p-notification-panel`)], CDSNotificationPanel); var notification_panel_default = CDSNotificationPanel; //#endregion export { notification_panel_default as default }; //# sourceMappingURL=notification-panel.js.map