import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; import overlayMessageStyle from '../scss/overlay-message.scss'; import { MetadataField, OverlayMessage, OverlayMessageControl } from '../types.js'; import { dispatchDismissOverlayMessageEvent } from '../utils/overlay-message.js'; import './icon.js'; @customElement('advanced-camera-card-overlay-message') export class AdvancedCameraCardOverlayMessage extends LitElement { @property({ attribute: false }) public message: OverlayMessage | null = null; private _refMessage: Ref = createRef(); public connectedCallback(): void { super.connectedCallback(); window.addEventListener('click', this._handleOutsideInteraction); window.addEventListener('focusin', this._handleOutsideInteraction); window.addEventListener('keydown', this._handleKeyDown); } public disconnectedCallback(): void { window.removeEventListener('click', this._handleOutsideInteraction); window.removeEventListener('focusin', this._handleOutsideInteraction); window.removeEventListener('keydown', this._handleKeyDown); super.disconnectedCallback(); } protected render(): TemplateResult | void { if (!this.message) { return; } const heading = this.message.heading; const details = this.message.details ?? []; const text = this.message.text; const controls = this.message.controls ?? []; return html`
${heading ? this._renderDetail(heading, true) : ''} ${details.map((detail) => this._renderDetail(detail))} ${text ? html`
${text}
` : ''}
${controls.length ? html`
${controls.map((control) => this._renderControl(control))}
` : ''}
`; } private _renderControl(control: OverlayMessageControl): TemplateResult { const emphasisClass = control.emphasis ? `emphasis-${control.emphasis}` : ''; return html`
this._handleControlClick(control)} > ${control.icon ? html`` : ''}
`; } private async _handleControlClick(control: OverlayMessageControl): Promise { const result = await control.callback(); if (result === null) { // null = close the message this._dismiss(); } else { // Updated message = keep open and refresh this.message = result; } } private _renderDetail(detail: MetadataField, isHeading = false): TemplateResult { const classes = { detail: true, heading: isHeading, [`emphasis-${detail.emphasis}`]: !!detail.emphasis, }; return html`
${detail.icon ? html`` : ''} ${detail.title}
`; } private _dismiss = (): void => { this._refMessage.value?.classList.add('exiting'); }; private _handleAnimationEnd = (ev: AnimationEvent): void => { if (ev.animationName === 'slideDown') { dispatchDismissOverlayMessageEvent(this); } }; private _handleOutsideInteraction = (ev: Event): void => { if (!ev.composedPath().includes(this)) { this._dismiss(); } }; private _handleKeyDown = (ev: KeyboardEvent): void => { if (ev.key === 'Escape') { this._dismiss(); ev.stopPropagation(); ev.preventDefault(); } }; static get styles(): CSSResultGroup { return unsafeCSS(overlayMessageStyle); } } declare global { interface HTMLElementTagNameMap { 'advanced-camera-card-overlay-message': AdvancedCameraCardOverlayMessage; } }