Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/domWidget/browser/domWidget.ts
4776 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { isHotReloadEnabled } from '../../../base/common/hotReload.js';
7
import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
8
import { ISettableObservable, IObservable, autorun, constObservable, derived, observableValue } from '../../../base/common/observable.js';
9
import { IInstantiationService, GetLeadingNonServiceArgs } from '../../instantiation/common/instantiation.js';
10
11
/**
12
* The DomWidget class provides a standard to define reusable UI components.
13
* It is disposable and defines a single root element of type HTMLElement.
14
* It also provides static helper methods to create and append widgets to the DOM,
15
* with support for hot module replacement during development.
16
*/
17
export abstract class DomWidget extends Disposable {
18
/**
19
* Appends the widget to the provided DOM element.
20
*/
21
public static createAppend<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, dom: HTMLElement, store: DisposableStore, ...params: TArgs): void {
22
if (!isHotReloadEnabled()) {
23
const widget = new this(...params);
24
dom.appendChild(widget.element);
25
store.add(widget);
26
return;
27
}
28
29
const observable = this.createObservable(store, ...params);
30
store.add(autorun((reader) => {
31
const widget = observable.read(reader);
32
dom.appendChild(widget.element);
33
reader.store.add(toDisposable(() => widget.element.remove()));
34
reader.store.add(widget);
35
}));
36
}
37
38
/**
39
* Creates the widget in a new div element with "display: contents".
40
*/
41
public static createInContents<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, store: DisposableStore, ...params: TArgs): HTMLDivElement {
42
const div = document.createElement('div');
43
div.style.display = 'contents';
44
this.createAppend(div, store, ...params);
45
return div;
46
}
47
48
/**
49
* Creates an observable instance of the widget.
50
* The observable will change when hot module replacement occurs.
51
*/
52
public static createObservable<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, store: DisposableStore, ...params: TArgs): IObservable<T> {
53
if (!isHotReloadEnabled()) {
54
return constObservable(new this(...params));
55
}
56
57
const id = (this as unknown as HotReloadable)[_hotReloadId];
58
const observable = id ? hotReloadedWidgets.get(id) : undefined;
59
60
if (!observable) {
61
return constObservable(new this(...params));
62
}
63
64
return derived(reader => {
65
const Ctor = observable.read(reader);
66
return new Ctor(...params) as T;
67
});
68
}
69
70
/**
71
* Appends the widget to the provided DOM element.
72
*/
73
public static instantiateAppend<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, dom: HTMLElement, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): void {
74
if (!isHotReloadEnabled()) {
75
const widget = instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params);
76
dom.appendChild(widget.element);
77
store.add(widget);
78
return;
79
}
80
81
const observable = this.instantiateObservable(instantiationService, store, ...params);
82
let lastWidget: DomWidget | undefined = undefined;
83
store.add(autorun((reader) => {
84
const widget = observable.read(reader);
85
if (lastWidget) {
86
lastWidget.element.replaceWith(widget.element);
87
} else {
88
dom.appendChild(widget.element);
89
}
90
lastWidget = widget;
91
92
reader.delayedStore.add(widget);
93
}));
94
}
95
96
/**
97
* Creates the widget in a new div element with "display: contents".
98
* If possible, prefer `instantiateAppend`, as it avoids an extra div in the DOM.
99
*/
100
public static instantiateInContents<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): HTMLDivElement {
101
const div = document.createElement('div');
102
div.style.display = 'contents';
103
this.instantiateAppend(instantiationService, div, store, ...params);
104
return div;
105
}
106
107
/**
108
* Creates an observable instance of the widget.
109
* The observable will change when hot module replacement occurs.
110
*/
111
public static instantiateObservable<TArgs extends unknown[], T extends DomWidget>(this: DomWidgetCtor<TArgs, T>, instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): IObservable<T> {
112
if (!isHotReloadEnabled()) {
113
return constObservable(instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params));
114
}
115
116
const id = (this as unknown as HotReloadable)[_hotReloadId];
117
const observable = id ? hotReloadedWidgets.get(id) : undefined;
118
119
if (!observable) {
120
return constObservable(instantiationService.createInstance(this as unknown as new (...args: unknown[]) => T, ...params));
121
}
122
123
return derived(reader => {
124
const Ctor = observable.read(reader);
125
return instantiationService.createInstance(Ctor, ...params) as T;
126
});
127
}
128
129
/**
130
* @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).
131
*/
132
// eslint-disable-next-line @typescript-eslint/no-explicit-any
133
public static registerWidgetHotReplacement(this: new (...args: any[]) => DomWidget, id: string): void {
134
if (!isHotReloadEnabled()) {
135
return;
136
}
137
let observable = hotReloadedWidgets.get(id);
138
if (!observable) {
139
observable = observableValue(id, this);
140
hotReloadedWidgets.set(id, observable);
141
} else {
142
observable.set(this, undefined);
143
}
144
(this as unknown as HotReloadable)[_hotReloadId] = id;
145
}
146
147
/** Always returns the same element. */
148
abstract get element(): HTMLElement;
149
}
150
151
const _hotReloadId = Symbol('DomWidgetHotReloadId');
152
// eslint-disable-next-line @typescript-eslint/no-explicit-any
153
const hotReloadedWidgets = new Map<string, ISettableObservable<new (...args: any[]) => DomWidget>>();
154
155
interface HotReloadable {
156
[_hotReloadId]?: string;
157
}
158
159
type DomWidgetCtor<TArgs extends unknown[], T extends DomWidget> = {
160
new(...args: TArgs): T;
161
162
createObservable(store: DisposableStore, ...params: TArgs): IObservable<T>;
163
instantiateObservable(instantiationService: IInstantiationService, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): IObservable<T>;
164
createAppend(dom: HTMLElement, store: DisposableStore, ...params: TArgs): void;
165
instantiateAppend(instantiationService: IInstantiationService, dom: HTMLElement, store: DisposableStore, ...params: GetLeadingNonServiceArgs<TArgs>): void;
166
};
167
168