Path: blob/main/src/vs/base/browser/ui/list/listPaging.ts
5251 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 { range } from '../../../common/arrays.js';6import { CancellationTokenSource } from '../../../common/cancellation.js';7import { Event } from '../../../common/event.js';8import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js';9import { IPagedModel } from '../../../common/paging.js';10import { ScrollbarVisibility } from '../../../common/scrollable.js';11import './list.css';12import { IListContextMenuEvent, IListElementRenderDetails, IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from './list.js';13import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List, TypeNavigationMode } from './listWidget.js';14import { isActiveElement } from '../../dom.js';1516export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {17renderPlaceholder(index: number, templateData: TTemplateData): void;18}1920export interface ITemplateData<T> {21data?: T;22disposable?: IDisposable;23}2425class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, ITemplateData<TTemplateData>> {2627get templateId(): string { return this.renderer.templateId; }2829constructor(30private renderer: IPagedRenderer<TElement, TTemplateData>,31private modelProvider: () => IPagedModel<TElement>32) { }3334renderTemplate(container: HTMLElement): ITemplateData<TTemplateData> {35const data = this.renderer.renderTemplate(container);36return { data, disposable: Disposable.None };37}3839renderElement(index: number, _: number, data: ITemplateData<TTemplateData>, details?: IListElementRenderDetails): void {40data.disposable?.dispose();4142if (!data.data) {43return;44}4546const model = this.modelProvider();4748if (model.isResolved(index)) {49return this.renderer.renderElement(model.get(index), index, data.data, details);50}5152const cts = new CancellationTokenSource();53const promise = model.resolve(index, cts.token);54data.disposable = { dispose: () => cts.cancel() };5556this.renderer.renderPlaceholder(index, data.data);57promise.then(entry => this.renderer.renderElement(entry, index, data.data!, details));58}5960disposeTemplate(data: ITemplateData<TTemplateData>): void {61if (data.disposable) {62data.disposable.dispose();63data.disposable = undefined;64}65if (data.data) {66this.renderer.disposeTemplate(data.data);67data.data = undefined;68}69}70}7172class PagedAccessibilityProvider<T> implements IListAccessibilityProvider<number> {7374constructor(75private modelProvider: () => IPagedModel<T>,76private accessibilityProvider: IListAccessibilityProvider<T>77) { }7879getWidgetAriaLabel() {80return this.accessibilityProvider.getWidgetAriaLabel();81}8283getAriaLabel(index: number) {84const model = this.modelProvider();8586if (!model.isResolved(index)) {87return null;88}8990return this.accessibilityProvider.getAriaLabel(model.get(index));91}92}9394export interface IPagedListOptions<T> {95readonly typeNavigationEnabled?: boolean;96readonly typeNavigationMode?: TypeNavigationMode;97readonly ariaLabel?: string;98readonly keyboardSupport?: boolean;99readonly multipleSelectionSupport?: boolean;100readonly accessibilityProvider?: IListAccessibilityProvider<T>;101102// list view options103readonly useShadows?: boolean;104readonly verticalScrollMode?: ScrollbarVisibility;105readonly setRowLineHeight?: boolean;106readonly setRowHeight?: boolean;107readonly supportDynamicHeights?: boolean;108readonly mouseSupport?: boolean;109readonly horizontalScrolling?: boolean;110readonly scrollByPage?: boolean;111readonly paddingBottom?: number;112readonly alwaysConsumeMouseWheel?: boolean;113}114115function fromPagedListOptions<T>(modelProvider: () => IPagedModel<T>, options: IPagedListOptions<T>): IListOptions<number> {116return {117...options,118accessibilityProvider: options.accessibilityProvider && new PagedAccessibilityProvider(modelProvider, options.accessibilityProvider)119};120}121122export class PagedList<T> implements IDisposable {123124private readonly list: List<number>;125private _model!: IPagedModel<T>;126private readonly modelDisposables = new DisposableStore();127128constructor(129user: string,130container: HTMLElement,131virtualDelegate: IListVirtualDelegate<number>,132renderers: IPagedRenderer<T, any>[],133options: IPagedListOptions<T> = {}134) {135const modelProvider = () => this.model;136const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, modelProvider));137this.list = new List(user, container, virtualDelegate, pagedRenderers, fromPagedListOptions(modelProvider, options));138}139140updateOptions(options: IListOptionsUpdate) {141this.list.updateOptions(options);142}143144getHTMLElement(): HTMLElement {145return this.list.getHTMLElement();146}147148isDOMFocused(): boolean {149return isActiveElement(this.getHTMLElement());150}151152domFocus(): void {153this.list.domFocus();154}155156get onDidFocus(): Event<void> {157return this.list.onDidFocus;158}159160get onDidBlur(): Event<void> {161return this.list.onDidBlur;162}163164get widget(): List<number> {165return this.list;166}167168get onDidDispose(): Event<void> {169return this.list.onDidDispose;170}171172get onMouseClick(): Event<IListMouseEvent<T>> {173return Event.map(this.list.onMouseClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));174}175176get onMouseDblClick(): Event<IListMouseEvent<T>> {177return Event.map(this.list.onMouseDblClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));178}179180get onTap(): Event<IListMouseEvent<T>> {181return Event.map(this.list.onTap, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));182}183184get onPointer(): Event<IListMouseEvent<T>> {185return Event.map(this.list.onPointer, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));186}187188get onDidChangeFocus(): Event<IListEvent<T>> {189return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));190}191192get onDidChangeSelection(): Event<IListEvent<T>> {193return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));194}195196get onContextMenu(): Event<IListContextMenuEvent<T>> {197return Event.map(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent }));198}199200get model(): IPagedModel<T> {201return this._model;202}203204set model(model: IPagedModel<T>) {205this.modelDisposables.clear();206this._model = model;207this.list.splice(0, this.list.length, range(model.length));208this.modelDisposables.add(model.onDidIncrementLength(newLength => this.list.splice(this.list.length, 0, range(this.list.length, newLength))));209}210211get length(): number {212return this.list.length;213}214215get scrollTop(): number {216return this.list.scrollTop;217}218219set scrollTop(scrollTop: number) {220this.list.scrollTop = scrollTop;221}222223get scrollLeft(): number {224return this.list.scrollLeft;225}226227set scrollLeft(scrollLeft: number) {228this.list.scrollLeft = scrollLeft;229}230231setAnchor(index: number | undefined): void {232this.list.setAnchor(index);233}234235getAnchor(): number | undefined {236return this.list.getAnchor();237}238239setFocus(indexes: number[]): void {240this.list.setFocus(indexes);241}242243focusNext(n?: number, loop?: boolean): void {244this.list.focusNext(n, loop);245}246247focusPrevious(n?: number, loop?: boolean): void {248this.list.focusPrevious(n, loop);249}250251focusNextPage(): Promise<void> {252return this.list.focusNextPage();253}254255focusPreviousPage(): Promise<void> {256return this.list.focusPreviousPage();257}258259focusLast(): void {260this.list.focusLast();261}262263focusFirst(): void {264this.list.focusFirst();265}266267getFocus(): number[] {268return this.list.getFocus();269}270271setSelection(indexes: number[], browserEvent?: UIEvent): void {272this.list.setSelection(indexes, browserEvent);273}274275getSelection(): number[] {276return this.list.getSelection();277}278279getSelectedElements(): T[] {280return this.getSelection().map(i => this.model.get(i));281}282283layout(height?: number, width?: number): void {284this.list.layout(height, width);285}286287triggerTypeNavigation(): void {288this.list.triggerTypeNavigation();289}290291reveal(index: number, relativeTop?: number): void {292this.list.reveal(index, relativeTop);293}294295style(styles: IListStyles): void {296this.list.style(styles);297}298299dispose(): void {300this.list.dispose();301this.modelDisposables.dispose();302}303}304305306