Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/table/tableWidget.ts
3296 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 { $, append, clearNode, getContentHeight, getContentWidth } from '../../dom.js';
7
import { createStyleSheet } from '../../domStylesheets.js';
8
import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js';
9
import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js';
10
import { IListElementRenderDetails, IListRenderer, IListVirtualDelegate } from '../list/list.js';
11
import { IListOptions, IListOptionsUpdate, IListStyles, List, unthemedListStyles } from '../list/listWidget.js';
12
import { ISplitViewDescriptor, IView, Orientation, SplitView } from '../splitview/splitview.js';
13
import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from './table.js';
14
import { Emitter, Event } from '../../../common/event.js';
15
import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js';
16
import { ScrollbarVisibility, ScrollEvent } from '../../../common/scrollable.js';
17
import { ISpliceable } from '../../../common/sequence.js';
18
import './table.css';
19
20
// TODO@joao
21
type TCell = any;
22
23
interface RowTemplateData {
24
readonly container: HTMLElement;
25
readonly cellContainers: HTMLElement[];
26
readonly cellTemplateData: unknown[];
27
}
28
29
class TableListRenderer<TRow> implements IListRenderer<TRow, RowTemplateData> {
30
31
static TemplateId = 'row';
32
readonly templateId = TableListRenderer.TemplateId;
33
private renderers: ITableRenderer<TCell, unknown>[];
34
private renderedTemplates = new Set<RowTemplateData>();
35
36
constructor(
37
private columns: ITableColumn<TRow, TCell>[],
38
renderers: ITableRenderer<TCell, unknown>[],
39
private getColumnSize: (index: number) => number
40
) {
41
const rendererMap = new Map(renderers.map(r => [r.templateId, r]));
42
this.renderers = [];
43
44
for (const column of columns) {
45
const renderer = rendererMap.get(column.templateId);
46
47
if (!renderer) {
48
throw new Error(`Table cell renderer for template id ${column.templateId} not found.`);
49
}
50
51
this.renderers.push(renderer);
52
}
53
}
54
55
renderTemplate(container: HTMLElement) {
56
const rowContainer = append(container, $('.monaco-table-tr'));
57
const cellContainers: HTMLElement[] = [];
58
const cellTemplateData: unknown[] = [];
59
60
for (let i = 0; i < this.columns.length; i++) {
61
const renderer = this.renderers[i];
62
const cellContainer = append(rowContainer, $('.monaco-table-td', { 'data-col-index': i }));
63
64
cellContainer.style.width = `${this.getColumnSize(i)}px`;
65
cellContainers.push(cellContainer);
66
cellTemplateData.push(renderer.renderTemplate(cellContainer));
67
}
68
69
const result = { container, cellContainers, cellTemplateData };
70
this.renderedTemplates.add(result);
71
72
return result;
73
}
74
75
renderElement(element: TRow, index: number, templateData: RowTemplateData, renderDetails?: IListElementRenderDetails): void {
76
for (let i = 0; i < this.columns.length; i++) {
77
const column = this.columns[i];
78
const cell = column.project(element);
79
const renderer = this.renderers[i];
80
renderer.renderElement(cell, index, templateData.cellTemplateData[i], renderDetails);
81
}
82
}
83
84
disposeElement(element: TRow, index: number, templateData: RowTemplateData, renderDetails?: IListElementRenderDetails): void {
85
for (let i = 0; i < this.columns.length; i++) {
86
const renderer = this.renderers[i];
87
88
if (renderer.disposeElement) {
89
const column = this.columns[i];
90
const cell = column.project(element);
91
92
renderer.disposeElement(cell, index, templateData.cellTemplateData[i], renderDetails);
93
}
94
}
95
}
96
97
disposeTemplate(templateData: RowTemplateData): void {
98
for (let i = 0; i < this.columns.length; i++) {
99
const renderer = this.renderers[i];
100
renderer.disposeTemplate(templateData.cellTemplateData[i]);
101
}
102
103
clearNode(templateData.container);
104
this.renderedTemplates.delete(templateData);
105
}
106
107
layoutColumn(index: number, size: number): void {
108
for (const { cellContainers } of this.renderedTemplates) {
109
cellContainers[index].style.width = `${size}px`;
110
}
111
}
112
}
113
114
function asListVirtualDelegate<TRow>(delegate: ITableVirtualDelegate<TRow>): IListVirtualDelegate<TRow> {
115
return {
116
getHeight(row) { return delegate.getHeight(row); },
117
getTemplateId() { return TableListRenderer.TemplateId; },
118
};
119
}
120
121
class ColumnHeader<TRow, TCell> extends Disposable implements IView {
122
123
readonly element: HTMLElement;
124
125
get minimumSize() { return this.column.minimumWidth ?? 120; }
126
get maximumSize() { return this.column.maximumWidth ?? Number.POSITIVE_INFINITY; }
127
get onDidChange() { return this.column.onDidChangeWidthConstraints ?? Event.None; }
128
129
private _onDidLayout = new Emitter<[number, number]>();
130
readonly onDidLayout = this._onDidLayout.event;
131
132
constructor(readonly column: ITableColumn<TRow, TCell>, private index: number) {
133
super();
134
135
this.element = $('.monaco-table-th', { 'data-col-index': index }, column.label);
136
137
if (column.tooltip) {
138
this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip));
139
}
140
}
141
142
layout(size: number): void {
143
this._onDidLayout.fire([this.index, size]);
144
}
145
}
146
147
export interface ITableOptions<TRow> extends IListOptions<TRow> { }
148
export interface ITableOptionsUpdate extends IListOptionsUpdate { }
149
export interface ITableStyles extends IListStyles { }
150
151
export class Table<TRow> implements ISpliceable<TRow>, IDisposable {
152
153
private static InstanceCount = 0;
154
readonly domId = `table_id_${++Table.InstanceCount}`;
155
156
readonly domNode: HTMLElement;
157
private splitview: SplitView;
158
private list: List<TRow>;
159
private styleElement: HTMLStyleElement;
160
protected readonly disposables = new DisposableStore();
161
162
private cachedWidth: number = 0;
163
private cachedHeight: number = 0;
164
165
get onDidChangeFocus(): Event<ITableEvent<TRow>> { return this.list.onDidChangeFocus; }
166
get onDidChangeSelection(): Event<ITableEvent<TRow>> { return this.list.onDidChangeSelection; }
167
168
get onDidScroll(): Event<ScrollEvent> { return this.list.onDidScroll; }
169
get onMouseClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseClick; }
170
get onMouseDblClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseDblClick; }
171
get onMouseMiddleClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseMiddleClick; }
172
get onPointer(): Event<ITableMouseEvent<TRow>> { return this.list.onPointer; }
173
get onMouseUp(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseUp; }
174
get onMouseDown(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseDown; }
175
get onMouseOver(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseOver; }
176
get onMouseMove(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseMove; }
177
get onMouseOut(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseOut; }
178
get onTouchStart(): Event<ITableTouchEvent<TRow>> { return this.list.onTouchStart; }
179
get onTap(): Event<ITableGestureEvent<TRow>> { return this.list.onTap; }
180
get onContextMenu(): Event<ITableContextMenuEvent<TRow>> { return this.list.onContextMenu; }
181
182
get onDidFocus(): Event<void> { return this.list.onDidFocus; }
183
get onDidBlur(): Event<void> { return this.list.onDidBlur; }
184
185
get scrollTop(): number { return this.list.scrollTop; }
186
set scrollTop(scrollTop: number) { this.list.scrollTop = scrollTop; }
187
get scrollLeft(): number { return this.list.scrollLeft; }
188
set scrollLeft(scrollLeft: number) { this.list.scrollLeft = scrollLeft; }
189
get scrollHeight(): number { return this.list.scrollHeight; }
190
get renderHeight(): number { return this.list.renderHeight; }
191
get onDidDispose(): Event<void> { return this.list.onDidDispose; }
192
193
constructor(
194
user: string,
195
container: HTMLElement,
196
private virtualDelegate: ITableVirtualDelegate<TRow>,
197
private columns: ITableColumn<TRow, TCell>[],
198
renderers: ITableRenderer<TCell, unknown>[],
199
_options?: ITableOptions<TRow>
200
) {
201
this.domNode = append(container, $(`.monaco-table.${this.domId}`));
202
203
const headers = columns.map((c, i) => this.disposables.add(new ColumnHeader(c, i)));
204
const descriptor: ISplitViewDescriptor = {
205
size: headers.reduce((a, b) => a + b.column.weight, 0),
206
views: headers.map(view => ({ size: view.column.weight, view }))
207
};
208
209
this.splitview = this.disposables.add(new SplitView(this.domNode, {
210
orientation: Orientation.HORIZONTAL,
211
scrollbarVisibility: ScrollbarVisibility.Hidden,
212
getSashOrthogonalSize: () => this.cachedHeight,
213
descriptor
214
}));
215
216
this.splitview.el.style.height = `${virtualDelegate.headerRowHeight}px`;
217
this.splitview.el.style.lineHeight = `${virtualDelegate.headerRowHeight}px`;
218
219
const renderer = new TableListRenderer(columns, renderers, i => this.splitview.getViewSize(i));
220
this.list = this.disposables.add(new List(user, this.domNode, asListVirtualDelegate(virtualDelegate), [renderer], _options));
221
222
Event.any(...headers.map(h => h.onDidLayout))
223
(([index, size]) => renderer.layoutColumn(index, size), null, this.disposables);
224
225
this.splitview.onDidSashReset(index => {
226
const totalWeight = columns.reduce((r, c) => r + c.weight, 0);
227
const size = columns[index].weight / totalWeight * this.cachedWidth;
228
this.splitview.resizeView(index, size);
229
}, null, this.disposables);
230
231
this.styleElement = createStyleSheet(this.domNode);
232
this.style(unthemedListStyles);
233
}
234
235
getColumnLabels(): string[] {
236
return this.columns.map(c => c.label);
237
}
238
239
resizeColumn(index: number, percentage: number): void {
240
const size = Math.round((percentage / 100.00) * this.cachedWidth);
241
this.splitview.resizeView(index, size);
242
}
243
244
updateOptions(options: ITableOptionsUpdate): void {
245
this.list.updateOptions(options);
246
}
247
248
splice(start: number, deleteCount: number, elements: readonly TRow[] = []): void {
249
this.list.splice(start, deleteCount, elements);
250
}
251
252
rerender(): void {
253
this.list.rerender();
254
}
255
256
row(index: number): TRow {
257
return this.list.element(index);
258
}
259
260
indexOf(element: TRow): number {
261
return this.list.indexOf(element);
262
}
263
264
get length(): number {
265
return this.list.length;
266
}
267
268
getHTMLElement(): HTMLElement {
269
return this.domNode;
270
}
271
272
layout(height?: number, width?: number): void {
273
height = height ?? getContentHeight(this.domNode);
274
width = width ?? getContentWidth(this.domNode);
275
276
this.cachedWidth = width;
277
this.cachedHeight = height;
278
this.splitview.layout(width);
279
280
const listHeight = height - this.virtualDelegate.headerRowHeight;
281
this.list.getHTMLElement().style.height = `${listHeight}px`;
282
this.list.layout(listHeight, width);
283
}
284
285
triggerTypeNavigation(): void {
286
this.list.triggerTypeNavigation();
287
}
288
289
style(styles: ITableStyles): void {
290
const content: string[] = [];
291
292
content.push(`.monaco-table.${this.domId} > .monaco-split-view2 .monaco-sash.vertical::before {
293
top: ${this.virtualDelegate.headerRowHeight + 1}px;
294
height: calc(100% - ${this.virtualDelegate.headerRowHeight}px);
295
}`);
296
297
this.styleElement.textContent = content.join('\n');
298
this.list.style(styles);
299
}
300
301
domFocus(): void {
302
this.list.domFocus();
303
}
304
305
setAnchor(index: number | undefined): void {
306
this.list.setAnchor(index);
307
}
308
309
getAnchor(): number | undefined {
310
return this.list.getAnchor();
311
}
312
313
getSelectedElements(): TRow[] {
314
return this.list.getSelectedElements();
315
}
316
317
setSelection(indexes: number[], browserEvent?: UIEvent): void {
318
this.list.setSelection(indexes, browserEvent);
319
}
320
321
getSelection(): number[] {
322
return this.list.getSelection();
323
}
324
325
setFocus(indexes: number[], browserEvent?: UIEvent): void {
326
this.list.setFocus(indexes, browserEvent);
327
}
328
329
focusNext(n = 1, loop = false, browserEvent?: UIEvent): void {
330
this.list.focusNext(n, loop, browserEvent);
331
}
332
333
focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void {
334
this.list.focusPrevious(n, loop, browserEvent);
335
}
336
337
focusNextPage(browserEvent?: UIEvent): Promise<void> {
338
return this.list.focusNextPage(browserEvent);
339
}
340
341
focusPreviousPage(browserEvent?: UIEvent): Promise<void> {
342
return this.list.focusPreviousPage(browserEvent);
343
}
344
345
focusFirst(browserEvent?: UIEvent): void {
346
this.list.focusFirst(browserEvent);
347
}
348
349
focusLast(browserEvent?: UIEvent): void {
350
this.list.focusLast(browserEvent);
351
}
352
353
getFocus(): number[] {
354
return this.list.getFocus();
355
}
356
357
getFocusedElements(): TRow[] {
358
return this.list.getFocusedElements();
359
}
360
361
getRelativeTop(index: number): number | null {
362
return this.list.getRelativeTop(index);
363
}
364
365
reveal(index: number, relativeTop?: number): void {
366
this.list.reveal(index, relativeTop);
367
}
368
369
dispose(): void {
370
this.disposables.dispose();
371
}
372
}
373
374