Path: blob/main/src/vs/base/browser/ui/table/tableWidget.ts
3296 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 { $, append, clearNode, getContentHeight, getContentWidth } from '../../dom.js';6import { createStyleSheet } from '../../domStylesheets.js';7import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js';8import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js';9import { IListElementRenderDetails, IListRenderer, IListVirtualDelegate } from '../list/list.js';10import { IListOptions, IListOptionsUpdate, IListStyles, List, unthemedListStyles } from '../list/listWidget.js';11import { ISplitViewDescriptor, IView, Orientation, SplitView } from '../splitview/splitview.js';12import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from './table.js';13import { Emitter, Event } from '../../../common/event.js';14import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js';15import { ScrollbarVisibility, ScrollEvent } from '../../../common/scrollable.js';16import { ISpliceable } from '../../../common/sequence.js';17import './table.css';1819// TODO@joao20type TCell = any;2122interface RowTemplateData {23readonly container: HTMLElement;24readonly cellContainers: HTMLElement[];25readonly cellTemplateData: unknown[];26}2728class TableListRenderer<TRow> implements IListRenderer<TRow, RowTemplateData> {2930static TemplateId = 'row';31readonly templateId = TableListRenderer.TemplateId;32private renderers: ITableRenderer<TCell, unknown>[];33private renderedTemplates = new Set<RowTemplateData>();3435constructor(36private columns: ITableColumn<TRow, TCell>[],37renderers: ITableRenderer<TCell, unknown>[],38private getColumnSize: (index: number) => number39) {40const rendererMap = new Map(renderers.map(r => [r.templateId, r]));41this.renderers = [];4243for (const column of columns) {44const renderer = rendererMap.get(column.templateId);4546if (!renderer) {47throw new Error(`Table cell renderer for template id ${column.templateId} not found.`);48}4950this.renderers.push(renderer);51}52}5354renderTemplate(container: HTMLElement) {55const rowContainer = append(container, $('.monaco-table-tr'));56const cellContainers: HTMLElement[] = [];57const cellTemplateData: unknown[] = [];5859for (let i = 0; i < this.columns.length; i++) {60const renderer = this.renderers[i];61const cellContainer = append(rowContainer, $('.monaco-table-td', { 'data-col-index': i }));6263cellContainer.style.width = `${this.getColumnSize(i)}px`;64cellContainers.push(cellContainer);65cellTemplateData.push(renderer.renderTemplate(cellContainer));66}6768const result = { container, cellContainers, cellTemplateData };69this.renderedTemplates.add(result);7071return result;72}7374renderElement(element: TRow, index: number, templateData: RowTemplateData, renderDetails?: IListElementRenderDetails): void {75for (let i = 0; i < this.columns.length; i++) {76const column = this.columns[i];77const cell = column.project(element);78const renderer = this.renderers[i];79renderer.renderElement(cell, index, templateData.cellTemplateData[i], renderDetails);80}81}8283disposeElement(element: TRow, index: number, templateData: RowTemplateData, renderDetails?: IListElementRenderDetails): void {84for (let i = 0; i < this.columns.length; i++) {85const renderer = this.renderers[i];8687if (renderer.disposeElement) {88const column = this.columns[i];89const cell = column.project(element);9091renderer.disposeElement(cell, index, templateData.cellTemplateData[i], renderDetails);92}93}94}9596disposeTemplate(templateData: RowTemplateData): void {97for (let i = 0; i < this.columns.length; i++) {98const renderer = this.renderers[i];99renderer.disposeTemplate(templateData.cellTemplateData[i]);100}101102clearNode(templateData.container);103this.renderedTemplates.delete(templateData);104}105106layoutColumn(index: number, size: number): void {107for (const { cellContainers } of this.renderedTemplates) {108cellContainers[index].style.width = `${size}px`;109}110}111}112113function asListVirtualDelegate<TRow>(delegate: ITableVirtualDelegate<TRow>): IListVirtualDelegate<TRow> {114return {115getHeight(row) { return delegate.getHeight(row); },116getTemplateId() { return TableListRenderer.TemplateId; },117};118}119120class ColumnHeader<TRow, TCell> extends Disposable implements IView {121122readonly element: HTMLElement;123124get minimumSize() { return this.column.minimumWidth ?? 120; }125get maximumSize() { return this.column.maximumWidth ?? Number.POSITIVE_INFINITY; }126get onDidChange() { return this.column.onDidChangeWidthConstraints ?? Event.None; }127128private _onDidLayout = new Emitter<[number, number]>();129readonly onDidLayout = this._onDidLayout.event;130131constructor(readonly column: ITableColumn<TRow, TCell>, private index: number) {132super();133134this.element = $('.monaco-table-th', { 'data-col-index': index }, column.label);135136if (column.tooltip) {137this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip));138}139}140141layout(size: number): void {142this._onDidLayout.fire([this.index, size]);143}144}145146export interface ITableOptions<TRow> extends IListOptions<TRow> { }147export interface ITableOptionsUpdate extends IListOptionsUpdate { }148export interface ITableStyles extends IListStyles { }149150export class Table<TRow> implements ISpliceable<TRow>, IDisposable {151152private static InstanceCount = 0;153readonly domId = `table_id_${++Table.InstanceCount}`;154155readonly domNode: HTMLElement;156private splitview: SplitView;157private list: List<TRow>;158private styleElement: HTMLStyleElement;159protected readonly disposables = new DisposableStore();160161private cachedWidth: number = 0;162private cachedHeight: number = 0;163164get onDidChangeFocus(): Event<ITableEvent<TRow>> { return this.list.onDidChangeFocus; }165get onDidChangeSelection(): Event<ITableEvent<TRow>> { return this.list.onDidChangeSelection; }166167get onDidScroll(): Event<ScrollEvent> { return this.list.onDidScroll; }168get onMouseClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseClick; }169get onMouseDblClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseDblClick; }170get onMouseMiddleClick(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseMiddleClick; }171get onPointer(): Event<ITableMouseEvent<TRow>> { return this.list.onPointer; }172get onMouseUp(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseUp; }173get onMouseDown(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseDown; }174get onMouseOver(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseOver; }175get onMouseMove(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseMove; }176get onMouseOut(): Event<ITableMouseEvent<TRow>> { return this.list.onMouseOut; }177get onTouchStart(): Event<ITableTouchEvent<TRow>> { return this.list.onTouchStart; }178get onTap(): Event<ITableGestureEvent<TRow>> { return this.list.onTap; }179get onContextMenu(): Event<ITableContextMenuEvent<TRow>> { return this.list.onContextMenu; }180181get onDidFocus(): Event<void> { return this.list.onDidFocus; }182get onDidBlur(): Event<void> { return this.list.onDidBlur; }183184get scrollTop(): number { return this.list.scrollTop; }185set scrollTop(scrollTop: number) { this.list.scrollTop = scrollTop; }186get scrollLeft(): number { return this.list.scrollLeft; }187set scrollLeft(scrollLeft: number) { this.list.scrollLeft = scrollLeft; }188get scrollHeight(): number { return this.list.scrollHeight; }189get renderHeight(): number { return this.list.renderHeight; }190get onDidDispose(): Event<void> { return this.list.onDidDispose; }191192constructor(193user: string,194container: HTMLElement,195private virtualDelegate: ITableVirtualDelegate<TRow>,196private columns: ITableColumn<TRow, TCell>[],197renderers: ITableRenderer<TCell, unknown>[],198_options?: ITableOptions<TRow>199) {200this.domNode = append(container, $(`.monaco-table.${this.domId}`));201202const headers = columns.map((c, i) => this.disposables.add(new ColumnHeader(c, i)));203const descriptor: ISplitViewDescriptor = {204size: headers.reduce((a, b) => a + b.column.weight, 0),205views: headers.map(view => ({ size: view.column.weight, view }))206};207208this.splitview = this.disposables.add(new SplitView(this.domNode, {209orientation: Orientation.HORIZONTAL,210scrollbarVisibility: ScrollbarVisibility.Hidden,211getSashOrthogonalSize: () => this.cachedHeight,212descriptor213}));214215this.splitview.el.style.height = `${virtualDelegate.headerRowHeight}px`;216this.splitview.el.style.lineHeight = `${virtualDelegate.headerRowHeight}px`;217218const renderer = new TableListRenderer(columns, renderers, i => this.splitview.getViewSize(i));219this.list = this.disposables.add(new List(user, this.domNode, asListVirtualDelegate(virtualDelegate), [renderer], _options));220221Event.any(...headers.map(h => h.onDidLayout))222(([index, size]) => renderer.layoutColumn(index, size), null, this.disposables);223224this.splitview.onDidSashReset(index => {225const totalWeight = columns.reduce((r, c) => r + c.weight, 0);226const size = columns[index].weight / totalWeight * this.cachedWidth;227this.splitview.resizeView(index, size);228}, null, this.disposables);229230this.styleElement = createStyleSheet(this.domNode);231this.style(unthemedListStyles);232}233234getColumnLabels(): string[] {235return this.columns.map(c => c.label);236}237238resizeColumn(index: number, percentage: number): void {239const size = Math.round((percentage / 100.00) * this.cachedWidth);240this.splitview.resizeView(index, size);241}242243updateOptions(options: ITableOptionsUpdate): void {244this.list.updateOptions(options);245}246247splice(start: number, deleteCount: number, elements: readonly TRow[] = []): void {248this.list.splice(start, deleteCount, elements);249}250251rerender(): void {252this.list.rerender();253}254255row(index: number): TRow {256return this.list.element(index);257}258259indexOf(element: TRow): number {260return this.list.indexOf(element);261}262263get length(): number {264return this.list.length;265}266267getHTMLElement(): HTMLElement {268return this.domNode;269}270271layout(height?: number, width?: number): void {272height = height ?? getContentHeight(this.domNode);273width = width ?? getContentWidth(this.domNode);274275this.cachedWidth = width;276this.cachedHeight = height;277this.splitview.layout(width);278279const listHeight = height - this.virtualDelegate.headerRowHeight;280this.list.getHTMLElement().style.height = `${listHeight}px`;281this.list.layout(listHeight, width);282}283284triggerTypeNavigation(): void {285this.list.triggerTypeNavigation();286}287288style(styles: ITableStyles): void {289const content: string[] = [];290291content.push(`.monaco-table.${this.domId} > .monaco-split-view2 .monaco-sash.vertical::before {292top: ${this.virtualDelegate.headerRowHeight + 1}px;293height: calc(100% - ${this.virtualDelegate.headerRowHeight}px);294}`);295296this.styleElement.textContent = content.join('\n');297this.list.style(styles);298}299300domFocus(): void {301this.list.domFocus();302}303304setAnchor(index: number | undefined): void {305this.list.setAnchor(index);306}307308getAnchor(): number | undefined {309return this.list.getAnchor();310}311312getSelectedElements(): TRow[] {313return this.list.getSelectedElements();314}315316setSelection(indexes: number[], browserEvent?: UIEvent): void {317this.list.setSelection(indexes, browserEvent);318}319320getSelection(): number[] {321return this.list.getSelection();322}323324setFocus(indexes: number[], browserEvent?: UIEvent): void {325this.list.setFocus(indexes, browserEvent);326}327328focusNext(n = 1, loop = false, browserEvent?: UIEvent): void {329this.list.focusNext(n, loop, browserEvent);330}331332focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void {333this.list.focusPrevious(n, loop, browserEvent);334}335336focusNextPage(browserEvent?: UIEvent): Promise<void> {337return this.list.focusNextPage(browserEvent);338}339340focusPreviousPage(browserEvent?: UIEvent): Promise<void> {341return this.list.focusPreviousPage(browserEvent);342}343344focusFirst(browserEvent?: UIEvent): void {345this.list.focusFirst(browserEvent);346}347348focusLast(browserEvent?: UIEvent): void {349this.list.focusLast(browserEvent);350}351352getFocus(): number[] {353return this.list.getFocus();354}355356getFocusedElements(): TRow[] {357return this.list.getFocusedElements();358}359360getRelativeTop(index: number): number | null {361return this.list.getRelativeTop(index);362}363364reveal(index: number, relativeTop?: number): void {365this.list.reveal(index, relativeTop);366}367368dispose(): void {369this.disposables.dispose();370}371}372373374