chrome-devtools-frontend
Version:
Chrome DevTools UI
142 lines (125 loc) • 4.25 kB
text/typescript
// 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 */
import '../icon_button/icon_button.js';
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
import * as Lit from '../../lit/lit.js';
import floatingButtonStyles from './floatingButton.css.js';
const {html} = Lit;
/**
* A simple floating button component, primarily used to display the 'Ask AI!'
* teaser when hovering over specific UI elements.
*
* Usage is simple:
*
* ```js
* // Instantiate programmatically via the `create()` helper:
* const button = Buttons.FloatingButton.create('smart-assistant', 'Ask AI!');
*
* // Use within a template:
* html`
* <devtools-floating-button icon-name="smart-assistant"
* title="Ask AI!">
* </devtools-floating-button>
* `;
* ```
*
* @property iconName - The `"icon-name"` attribute is reflected as a property.
* @property jslogContext - The `"jslogcontext"` attribute is reflected as a property.
* @attribute icon-name - The basename of the icon file (not including the `.svg`
* suffix).
* @attribute jslogcontext - The context for the `jslog` attribute. A `jslog`
* attribute is generated automatically with the
* provided context.
*/
export class FloatingButton extends HTMLElement {
static readonly observedAttributes = ['icon-name', 'jslogcontext'];
readonly #shadow = this.attachShadow({mode: 'open'});
constructor() {
super();
this.role = 'presentation';
this.#render();
}
/**
* Yields the value of the `"icon-name"` attribute of this `FloatingButton`
* (`null` in case there's no `"icon-name"` on this element).
*/
get iconName(): string|null {
return this.getAttribute('icon-name');
}
/**
* Changes the value of the `"icon-name"` attribute of this `FloatingButton`.
* If you pass `null`, the `"icon-name"` attribute will be removed from this
* element.
*
* @param the new icon name or `null` to unset.
*/
set iconName(iconName: string|null) {
if (iconName === null) {
this.removeAttribute('icon-name');
} else {
this.setAttribute('icon-name', iconName);
}
}
get jslogContext(): string|null {
return this.getAttribute('jslogcontext');
}
set jslogContext(jslogContext: string|null) {
if (jslogContext === null) {
this.removeAttribute('jslogcontext');
} else {
this.setAttribute('jslogcontext', jslogContext);
}
}
attributeChangedCallback(name: string, oldValue: string|null, newValue: string|null): void {
if (oldValue === newValue) {
return;
}
if (name === 'icon-name') {
this.#render();
}
if (name === 'jslogcontext') {
this.#updateJslog();
}
}
#render(): void {
// clang-format off
Lit.render(html`
<style>${floatingButtonStyles}</style>
<button><devtools-icon .name=${this.iconName}></devtools-icon></button>`,
this.#shadow, {host: this});
// clang-format on
}
#updateJslog(): void {
if (this.jslogContext) {
this.setAttribute('jslog', `${VisualLogging.action().track({click: true}).context(this.jslogContext)}`);
} else {
this.removeAttribute('jslog');
}
}
}
/**
* Helper function to programmatically create a `FloatingButton` instance with a
* given `iconName` and `title`.
*
* @param iconName the name of the icon to use
* @param title the tooltip for the `FloatingButton`
* @param jslogContext the context string for the `jslog` attribute
* @returns the newly created `FloatingButton` instance.
*/
export const create = (iconName: string, title: string, jslogContext?: string): FloatingButton => {
const floatingButton = new FloatingButton();
floatingButton.iconName = iconName;
floatingButton.title = title;
if (jslogContext) {
floatingButton.jslogContext = jslogContext;
}
return floatingButton;
};
customElements.define('devtools-floating-button', FloatingButton);
declare global {
interface HTMLElementTagNameMap {
'devtools-floating-button': FloatingButton;
}
}