| // Copyright 2022 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 */ |
| |
| import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js'; |
| import {html, render} from '../../../ui/lit/lit.js'; |
| import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js'; |
| |
| import editableSpanStyles from './EditableSpan.css.js'; |
| |
| export interface EditableSpanData { |
| value: string; |
| } |
| |
| export class EditableSpan extends HTMLElement { |
| readonly #shadow = this.attachShadow({mode: 'open'}); |
| #value = ''; |
| |
| connectedCallback(): void { |
| this.#shadow.addEventListener('focusin', this.#selectAllText.bind(this)); |
| this.#shadow.addEventListener('keydown', this.#onKeyDown.bind(this)); |
| this.#shadow.addEventListener('input', this.#onInput.bind(this)); |
| } |
| |
| set data(data: EditableSpanData) { |
| this.#value = data.value; |
| void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render); |
| } |
| |
| get value(): string { |
| return this.#shadow.querySelector<HTMLSpanElement>('span')?.innerText || ''; |
| } |
| |
| set value(value: string) { |
| this.#value = value; |
| const span = this.#shadow.querySelector<HTMLSpanElement>('span'); |
| if (span) { |
| span.innerText = value; |
| } |
| } |
| |
| #onKeyDown(event: Event): void { |
| if ((event as KeyboardEvent).key === 'Enter') { |
| event.preventDefault(); |
| (event.target as HTMLElement)?.blur(); |
| } |
| } |
| |
| #onInput(event: Event): void { |
| this.#value = (event.target as HTMLElement).innerText; |
| } |
| |
| #selectAllText(event: Event): void { |
| const target = event.target as HTMLElement; |
| const selection = window.getSelection(); |
| const range = document.createRange(); |
| range.selectNodeContents(target); |
| selection?.removeAllRanges(); |
| selection?.addRange(range); |
| } |
| |
| #render(): void { |
| if (!ComponentHelpers.ScheduledRender.isScheduledRender(this)) { |
| throw new Error('HeaderSectionRow render was not scheduled'); |
| } |
| |
| // Disabled until https://crbug.com/1079231 is fixed. |
| // clang-format off |
| render(html` |
| <style>${editableSpanStyles}</style> |
| <span |
| contenteditable="plaintext-only" |
| class="editable" |
| tabindex="0" |
| .innerText=${this.#value} |
| jslog=${VisualLogging.value('header-editor').track({change: true, keydown: 'Enter|Escape'})} |
| ></span>`, this.#shadow, {host: this}); |
| // clang-format on |
| } |
| |
| override focus(): void { |
| requestAnimationFrame(() => { |
| const span = this.#shadow.querySelector<HTMLElement>('.editable'); |
| span?.focus(); |
| }); |
| } |
| } |
| |
| customElements.define('devtools-editable-span', EditableSpan); |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'devtools-editable-span': EditableSpan; |
| } |
| } |