Path: blob/main/src/vs/platform/domWidget/browser/domWidget.ts
4776 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { isHotReloadEnabled } from '../../../base/common/hotReload.js';6import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';7import { ISettableObservable, IObservable, autorun, constObservable, derived, observableValue } from '../../../base/common/observable.js';8import { IInstantiationService, GetLeadingNonServiceArgs } from '../../instantiation/common/instantiation.js';910/**11* The DomWidget class provides a standard to define reusable UI components.12* It is disposable and defines a single root element of type HTMLElement.13* It also provides static helper methods to create and append widgets to the DOM,14* with support for hot module replacement during development.15*/16export abstract class DomWidget extends Disposable {17/**18* Appends the widget to the provided DOM element.19*/20public static createAppend<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, dom: HTMLElement, store: DisposableStore, ...params: TArgs): void {21if (!isHotReloadEnabled()) {22const widget = new this(...params);23dom.appendChild(widget.element);24store.add(widget);25return;26}2728const observable = this.createObservable(store, ...params);29store.add(autorun((reader) => {30const widget = observable.read(reader);31dom.appendChild(widget.element);32reader.store.add(toDisposable(() => widget.element.remove()));33reader.store.add(widget);34}));35}3637/**38* Creates the widget in a new div element with "display: contents".39*/40public static createInContents<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, store: DisposableStore, ...params: TArgs): HTMLDivElement {41const div = document.createElement('div');42div.style.display = 'contents';43this.createAppend(div, store, ...params);44return div;45}4647/**48* Creates an observable instance of the widget.49* The observable will change when hot module replacement occurs.50*/51public static createObservable<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, store: DisposableStore, ...params: TArgs): IObservable<T> {52if (!isHotReloadEnabled()) {53return constObservable(new this(...params));54}5556const id = (this as unknown as HotReloadable)[_hotReloadId];57const observable = id ? hotReloadedWidgets.get(id) : undefined;5859if (!observable) {60return constObservable(new this(...params));61}6263return derived(reader => {64const Ctor = observable.read(reader);65return new Ctor(...params) as T;66});67}6869/**70* Appends the widget to the provided DOM element.71*/72public static instantiateAppend<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, dom: HTMLElement, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): void {73if (!isHotReloadEnabled()) {74const widget = instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params);75dom.appendChild(widget.element);76store.add(widget);77return;78}7980const observable = this.instantiateObservable(instantiationService, store, ...params);81let lastWidget: DomWidget | undefined = undefined;82store.add(autorun((reader) => {83const widget = observable.read(reader);84if (lastWidget) {85lastWidget.element.replaceWith(widget.element);86} else {87dom.appendChild(widget.element);88}89lastWidget = widget;9091reader.delayedStore.add(widget);92}));93}9495/**96* Creates the widget in a new div element with "display: contents".97* If possible, prefer `instantiateAppend`, as it avoids an extra div in the DOM.98*/99public static instantiateInContents<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): HTMLDivElement {100const div = document.createElement('div');101div.style.display = 'contents';102this.instantiateAppend(instantiationService, div, store, ...params);103return div;104}105106/**107* Creates an observable instance of the widget.108* The observable will change when hot module replacement occurs.109*/110public static instantiateObservable<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): IObservable<T> {111if (!isHotReloadEnabled()) {112return constObservable(instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params));113}114115const id = (this as unknown as HotReloadable)[_hotReloadId];116const observable = id ? hotReloadedWidgets.get(id) : undefined;117118if (!observable) {119return constObservable(instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params));120}121122return derived(reader => {123const Ctor = observable.read(reader);124return instantiationService.createInstance(Ctor, ...params) as T;125});126}127128/**129* @deprecated Do not call manually! Only for use by the hot reload system (a vite plugin will inject calls to this method in dev mode).130*/131// eslint-disable-next-line @typescript-eslint/no-explicit-any132public static registerWidgetHotReplacement(this: new (...args: any[]) => DomWidget, id: string): void {133if (!isHotReloadEnabled()) {134return;135}136let observable = hotReloadedWidgets.get(id);137if (!observable) {138observable = observableValue(id, this);139hotReloadedWidgets.set(id, observable);140} else {141observable.set(this, undefined);142}143(this as unknown as HotReloadable)[_hotReloadId] = id;144}145146/** Always returns the same element. */147abstract get element(): HTMLElement;148}149150const _hotReloadId = Symbol('DomWidgetHotReloadId');151// eslint-disable-next-line @typescript-eslint/no-explicit-any152const hotReloadedWidgets = new Map<string, ISettableObservable<new (...args: any[]) => DomWidget>>();153154interface HotReloadable {155[_hotReloadId]?: string;156}157158type DomWidgetCtor<TArgs extends unknown[], T extends DomWidget> = {159new(...args: TArgs): T;160161createObservable(store: DisposableStore, ...params: TArgs): IObservable<T>;162instantiateObservable(instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): IObservable<T>;163createAppend(dom: HTMLElement, store: DisposableStore, ...params: TArgs): void;164instantiateAppend(instantiationService: IInstantiationService, dom: HTMLElement, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): void;165};166167168