UNPKG

chrome-devtools-frontend

Version:
185 lines (161 loc) 5.15 kB
// Copyright 2025 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 * as i18n from '../../../core/i18n/i18n.js'; import {Directives, html, nothing, render, type TemplateResult} from '../../lit/lit.js'; import * as Buttons from '../buttons/buttons.js'; import listStyles from './list.css.js'; const UIStrings = { /**    * @description Title of the edit button for the list items.    */ edit: 'Edit', /** * @description Title of the remove button for the list items. */ remove: 'Remove', } as const; const str_ = i18n.i18n.registerUIStrings('ui/components/list/List.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export interface ListItemEventDetail { index: number; } export class ItemEditEvent extends CustomEvent<ListItemEventDetail> { constructor(detail: ListItemEventDetail) { super('edit', { bubbles: true, composed: true, detail, }); } } export class ItemRemoveEvent extends CustomEvent<ListItemEventDetail> { constructor(detail: ListItemEventDetail) { super('delete', { bubbles: true, composed: true, detail, }); } } export class List extends HTMLElement { static observedAttributes: string[] = ['editable', 'deletable', 'disable-li-focus']; #observer: MutationObserver; #editable = false; #deletable = false; #disableListItemFocus?: boolean; constructor() { super(); this.attachShadow({mode: 'open'}); this.#observer = new MutationObserver(this.#render.bind(this)); } set editable(isEditable: boolean) { if (this.#editable === isEditable) { return; } this.#editable = isEditable; this.#render(); } set deletable(isDeletable: boolean) { if (this.#deletable === isDeletable) { return; } this.#deletable = isDeletable; this.#render(); } set disableListItemFocus(disableFocus: boolean) { if (this.#disableListItemFocus === disableFocus) { return; } this.#disableListItemFocus = disableFocus; this.#render(); } attributeChangedCallback(name: string, oldValue: string|null, newValue: string|null): void { const isSet = newValue !== null; if (name === 'editable') { this.editable = isSet; } else if (name === 'deletable') { this.deletable = isSet; } else if (name === 'disable-li-focus') { this.disableListItemFocus = isSet; } } connectedCallback(): void { this.#observer.observe(this, {childList: true}); this.#render(); } disconnectedCallback(): void { this.#observer.disconnect(); } createSlottedListItem(index: number): TemplateResult { return html` <li role='listitem' tabindex=${this.#disableListItemFocus ? '-1' : '0'}> <slot name='slot-${index}'></slot> <div class='controls-container'> <div class='controls-gradient'></div> <div class='controls-buttons'> ${ this.#editable ? html` <devtools-button title=${i18nString(UIStrings.edit)} aria-label=${i18nString(UIStrings.edit)} .iconName=${'edit'} .jslogContext=${'edit-item'} .variant=${Buttons.Button.Variant.ICON} @click=${this.#dispatchEdit.bind(this, index)} ></devtools-button> ` : nothing} ${ this.#deletable ? html` <devtools-button title=${i18nString(UIStrings.remove)} aria-label=${i18nString(UIStrings.remove)} .iconName=${'bin'} .jslogContext=${'remove-item'} .variant=${Buttons.Button.Variant.ICON} @click=${this.#dispatchRemove.bind(this, index)} ></devtools-button> ` : nothing} </div> </div> </li>`; } #render(): void { if (this.shadowRoot) { const items = [...this.children] as HTMLElement[]; const listData = items.map((item, index) => { const slotName = `slot-${index}`; if (item.getAttribute('slot') !== slotName) { item.setAttribute('slot', slotName); } return {index, item}; }); render( html` <style>${listStyles}</style> <ul role='list'> ${Directives.repeat(listData, data => data.item, data => this.createSlottedListItem(data.index))} </ul> `, this.shadowRoot); } } #dispatchRemove(index: number): void { this.dispatchEvent(new ItemRemoveEvent({index})); } #dispatchEdit(index: number): void { this.dispatchEvent(new ItemEditEvent({index})); } } customElements.define('devtools-list', List); declare global { interface HTMLElementTagNameMap { 'devtools-list': List; } } declare global { interface HTMLElementEventMap extends Record<'delete', ItemRemoveEvent>, Record<'edit', ItemEditEvent> {} }