| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /* eslint-disable @devtools/no-lit-render-outside-of-view, @devtools/enforce-custom-element-definitions-location */ |
| |
| import type * as Buttons from '../../../ui/components/buttons/buttons.js'; |
| import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js'; |
| import { |
| Directives, |
| html, |
| render, |
| } from '../../../ui/lit/lit.js'; |
| |
| import buttonDialogStyles from './buttonDialog.css.js'; |
| import { |
| type ClickOutsideDialogEvent, |
| type Dialog as DialogElement, |
| DialogHorizontalAlignment, |
| DialogState, |
| DialogVerticalPosition, |
| } from './Dialog.js'; |
| |
| const {ref} = Directives; |
| |
| export type ButtonDialogState = DialogState; |
| |
| export interface ButtonDialogData { |
| openOnRender?: boolean; |
| jslogContext?: string; |
| // The below are parts of ButtonData. See comments of ButtonData. |
| variant: Buttons.Button.Variant.PRIMARY_TOOLBAR|Buttons.Button.Variant.TOOLBAR|Buttons.Button.Variant.ICON; |
| iconName: string; |
| disabled?: boolean; |
| iconTitle?: string; |
| // The below are parts of DialogData. See comments of DialogData. |
| position?: DialogVerticalPosition; |
| horizontalAlignment?: DialogHorizontalAlignment; |
| closeOnESC?: boolean; |
| closeOnScroll?: boolean; |
| closeButton?: boolean; |
| state?: ButtonDialogState; |
| dialogTitle: string; |
| } |
| |
| export class ButtonDialog extends HTMLElement { |
| readonly #shadow = this.attachShadow({mode: 'open'}); |
| |
| #dialog: DialogElement|null = null; |
| #showButton: Buttons.Button.Button|null = null; |
| #data: ButtonDialogData|null = null; |
| |
| set data(data: ButtonDialogData) { |
| this.#data = data; |
| void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render); |
| } |
| |
| #showDialog(): void { |
| if (!this.#dialog) { |
| throw new Error('Dialog not found'); |
| } |
| |
| if (this.#data?.state === DialogState.DISABLED) { |
| // If dialog is disabled start teardown process to return |
| // focus to caller. |
| void this.#dialog.setDialogVisible(false); |
| } else { |
| void this.#dialog.setDialogVisible(true); |
| } |
| |
| void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render); |
| } |
| |
| #closeDialog(evt?: ClickOutsideDialogEvent): void { |
| if (!this.#dialog) { |
| throw new Error('Dialog not found'); |
| } |
| void this.#dialog.setDialogVisible(false); |
| if (evt) { |
| evt.stopImmediatePropagation(); |
| } |
| void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render); |
| } |
| |
| set state(state: ButtonDialogState) { |
| if (this.#data) { |
| this.#data.state = state; |
| void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render); |
| } |
| } |
| |
| #render(): void { |
| if (!this.#data) { |
| throw new Error('ButtonDialog.data is not set'); |
| } |
| if (!ComponentHelpers.ScheduledRender.isScheduledRender(this)) { |
| throw new Error('Button dialog render was not scheduled'); |
| } |
| |
| // clang-format off |
| render( |
| html` |
| <style>${buttonDialogStyles}</style> |
| <devtools-button |
| @click=${this.#showDialog} |
| ${ref(el => { |
| if (el instanceof HTMLElement) { |
| this.#showButton = el as Buttons.Button.Button; |
| } |
| })} |
| .data=${{ |
| variant: this.#data.variant, |
| iconName: this.#data.iconName, |
| disabled: this.#data.disabled, |
| title: this.#data.iconTitle, |
| jslogContext: this.#data.jslogContext, |
| } as Buttons.Button.ButtonData} |
| ></devtools-button> |
| <devtools-dialog |
| @clickoutsidedialog=${this.#closeDialog} |
| .origin=${() => { |
| if (!this.#showButton) { |
| throw new Error('Button not found'); |
| } |
| return this.#showButton; |
| }} |
| .position=${this.#data.position ?? DialogVerticalPosition.BOTTOM} |
| .horizontalAlignment=${this.#data.horizontalAlignment ?? DialogHorizontalAlignment.RIGHT} |
| .closeOnESC=${this.#data.closeOnESC ?? false} |
| .closeOnScroll=${this.#data.closeOnScroll ?? false} |
| .closeButton=${this.#data.closeButton ?? false} |
| .dialogTitle=${this.#data.dialogTitle} |
| .jslogContext=${this.#data.jslogContext ?? ''} |
| .state=${this.#data.state ?? DialogState.EXPANDED} |
| ${ref(el => { |
| if (el instanceof HTMLElement) { |
| this.#dialog = el as DialogElement; |
| } |
| })} |
| > |
| <slot></slot> |
| </devtools-dialog> |
| `, |
| this.#shadow, {host: this}); |
| // clang-format on |
| |
| if (this.#data.openOnRender) { |
| this.#showDialog(); |
| this.#data.openOnRender = false; |
| } |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'devtools-button-dialog': ButtonDialog; |
| } |
| } |
| customElements.define('devtools-button-dialog', ButtonDialog); |