[go: nahoru, domu]

blob: e6f25e71bc3b8693def197f5b54b3bae39b2d2c1 [file] [log] [blame]
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Buttons from '../../../ui/components/buttons/buttons.js';
import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
import * as LitHtml from '../../../ui/lit-html/lit-html.js';
import * as Dialogs from '../../../ui/components/dialogs/dialogs.js';
import * as Menus from '../../../ui/components/menus/menus.js';
import * as Models from '../models/models.js';
import type * as Actions from '../recorder-actions.js'; // eslint-disable-line rulesdir/es_modules_import
import selectButtonStyles from './selectButton.css.js';
export const enum Variant {
PRIMARY = 'primary',
SECONDARY = 'secondary',
}
type SelectMenuGroup = {
name: string,
items: SelectButtonItem[],
};
interface SelectButtonProps {
/**
* Whether the button is disabled or not
* Defaults to false
*/
disabled: boolean;
/**
* Current value of the button
* The same value must correspond to an item in the `items` array
*/
value: string;
/**
* Items for the select menu of the button
* Selected item is shown in the button itself
*/
items: SelectButtonItem[];
/**
* Groups for the select menu of the button.
*/
groups: Array<SelectMenuGroup>;
/**
* Similar to the button variant
*/
variant: Variant;
/**
* Action that the button is linked to
*/
action?: Actions.RecorderActions;
}
export interface SelectButtonItem {
/**
* Specifies the clicked item
*/
value: string;
/**
* `icon` to be shown on the button
*/
buttonIconUrl?: string;
/**
* Text to be shown in the select menu
*/
label: () => string;
/**
* Text to be shown in the button when the item is selected for the button
*/
buttonLabel?: () => string;
}
export class SelectButtonClickEvent extends Event {
static readonly eventName = 'selectbuttonclick';
constructor(public value: string) {
super(SelectButtonClickEvent.eventName, {bubbles: true, composed: true});
}
}
export class SelectButton extends HTMLElement {
static readonly litTagName = LitHtml.literal`devtools-select-button`;
readonly #shadow = this.attachShadow({mode: 'open'});
readonly #props: SelectButtonProps = {
disabled: false,
value: '',
items: [],
groups: [],
variant: Variant.PRIMARY,
};
connectedCallback(): void {
this.#shadow.adoptedStyleSheets = [selectButtonStyles];
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
}
get disabled(): boolean {
return this.#props.disabled;
}
set disabled(disabled: boolean) {
this.#props.disabled = disabled;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
}
get items(): SelectButtonItem[] {
return this.#props.items;
}
set items(items: SelectButtonItem[]) {
this.#props.items = items;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
}
set groups(groups: Array<SelectMenuGroup>) {
this.#props.groups = groups;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
}
get value(): string {
return this.#props.value;
}
set value(value: string) {
this.#props.value = value;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
}
get variant(): Variant {
return this.#props.variant;
}
set variant(variant: Variant) {
this.#props.variant = variant;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
}
set action(value: Actions.RecorderActions) {
this.#props.action = value;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
}
#handleClick(ev: Event): void {
ev.stopPropagation();
this.dispatchEvent(new SelectButtonClickEvent(this.#props.value));
}
#handleSelectMenuSelect(
evt: Menus.SelectMenu.SelectMenuItemSelectedEvent,
): void {
this.dispatchEvent(new SelectButtonClickEvent(evt.itemValue as string));
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
}
#renderSelectItem(
item: SelectButtonItem,
selectedItem: SelectButtonItem,
): LitHtml.TemplateResult {
// clang-format off
return LitHtml.html`
<${Menus.Menu.MenuItem.litTagName} .value=${item.value} .selected=${
item.value === selectedItem.value
}>
${item.label()}
</${Menus.Menu.MenuItem.litTagName}>
`;
// clang-format on
}
#renderSelectGroup(
group: SelectMenuGroup,
selectedItem: SelectButtonItem,
): LitHtml.TemplateResult {
// clang-format off
return LitHtml.html`
<${Menus.Menu.MenuGroup.litTagName} .name=${group.name}>
${group.items.map(item => this.#renderSelectItem(item, selectedItem))}
</${Menus.Menu.MenuGroup.litTagName}>
`;
// clang-format on
}
#getTitle(label: string): string {
return this.#props.action ? Models.Tooltip.getTooltipForActions(label, this.#props.action) : '';
}
#render = (): void => {
const hasGroups = Boolean(this.#props.groups.length);
const items = hasGroups ? this.#props.groups.flatMap(group => group.items) : this.#props.items;
const selectedItem = items.find(item => item.value === this.#props.value) || items[0];
if (!selectedItem) {
return;
}
const classes = {
primary: this.#props.variant === Variant.PRIMARY,
secondary: this.#props.variant === Variant.SECONDARY,
};
const buttonVariant =
this.#props.variant === Variant.SECONDARY ? Buttons.Button.Variant.SECONDARY : Buttons.Button.Variant.PRIMARY;
const label = selectedItem.buttonLabel ? selectedItem.buttonLabel() : selectedItem.label();
// clang-format off
LitHtml.render(
LitHtml.html`
<div class="select-button" title=${
this.#getTitle(label) || LitHtml.nothing
}>
${
selectedItem
? LitHtml.html`
<${Buttons.Button.Button.litTagName}
.disabled=${this.#props.disabled}
.variant=${buttonVariant}
.iconUrl=${selectedItem.buttonIconUrl}
@click=${this.#handleClick}>
${label}
</${Buttons.Button.Button.litTagName}>`
: ''
}
<${Menus.SelectMenu.SelectMenu.litTagName}
class=${LitHtml.Directives.classMap(classes)}
@selectmenuselected=${this.#handleSelectMenuSelect}
?disabled=${this.#props.disabled}
.showArrow=${true}
.sideButton=${false}
.showSelectedItem=${true}
.disabled=${this.#props.disabled}
.buttonTitle=${LitHtml.html``}
.position=${Dialogs.Dialog.DialogVerticalPosition.BOTTOM}
.horizontalAlignment=${
Dialogs.Dialog.DialogHorizontalAlignment.RIGHT
}
>
${
hasGroups
? this.#props.groups.map(group =>
this.#renderSelectGroup(group, selectedItem),
)
: this.#props.items.map(item =>
this.#renderSelectItem(item, selectedItem),
)
}
</${Menus.SelectMenu.SelectMenu.litTagName}>
</div>`,
this.#shadow,
{ host: this },
);
// clang-format on
};
}
ComponentHelpers.CustomElements.defineComponent(
'devtools-select-button',
SelectButton,
);
declare global {
interface HTMLElementEventMap {
selectbuttonclick: SelectButtonClickEvent;
}
interface HTMLElementTagNameMap {
'devtools-select-button': SelectButton;
}
}