Path: blob/main/src/vs/base/browser/ui/list/listPaging.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 { range } from '../../../common/arrays.js';6import { CancellationTokenSource } from '../../../common/cancellation.js';7import { Event } from '../../../common/event.js';8import { Disposable, 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 list: List<number>;125private _model!: IPagedModel<T>;126127constructor(128user: string,129container: HTMLElement,130virtualDelegate: IListVirtualDelegate<number>,131renderers: IPagedRenderer<T, any>[],132options: IPagedListOptions<T> = {}133) {134const modelProvider = () => this.model;135const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, modelProvider));136this.list = new List(user, container, virtualDelegate, pagedRenderers, fromPagedListOptions(modelProvider, options));137}138139updateOptions(options: IListOptionsUpdate) {140this.list.updateOptions(options);141}142143getHTMLElement(): HTMLElement {144return this.list.getHTMLElement();145}146147isDOMFocused(): boolean {148return isActiveElement(this.getHTMLElement());149}150151domFocus(): void {152this.list.domFocus();153}154155get onDidFocus(): Event<void> {156return this.list.onDidFocus;157}158159get onDidBlur(): Event<void> {160return this.list.onDidBlur;161}162163get widget(): List<number> {164return this.list;165}166167get onDidDispose(): Event<void> {168return this.list.onDidDispose;169}170171get onMouseClick(): Event<IListMouseEvent<T>> {172return Event.map(this.list.onMouseClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));173}174175get onMouseDblClick(): Event<IListMouseEvent<T>> {176return Event.map(this.list.onMouseDblClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));177}178179get onTap(): Event<IListMouseEvent<T>> {180return Event.map(this.list.onTap, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));181}182183get onPointer(): Event<IListMouseEvent<T>> {184return Event.map(this.list.onPointer, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));185}186187get onDidChangeFocus(): Event<IListEvent<T>> {188return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));189}190191get onDidChangeSelection(): Event<IListEvent<T>> {192return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));193}194195get onContextMenu(): Event<IListContextMenuEvent<T>> {196return Event.map(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent }));197}198199get model(): IPagedModel<T> {200return this._model;201}202203set model(model: IPagedModel<T>) {204this._model = model;205this.list.splice(0, this.list.length, range(model.length));206}207208get length(): number {209return this.list.length;210}211212get scrollTop(): number {213return this.list.scrollTop;214}215216set scrollTop(scrollTop: number) {217this.list.scrollTop = scrollTop;218}219220get scrollLeft(): number {221return this.list.scrollLeft;222}223224set scrollLeft(scrollLeft: number) {225this.list.scrollLeft = scrollLeft;226}227228setAnchor(index: number | undefined): void {229this.list.setAnchor(index);230}231232getAnchor(): number | undefined {233return this.list.getAnchor();234}235236setFocus(indexes: number[]): void {237this.list.setFocus(indexes);238}239240focusNext(n?: number, loop?: boolean): void {241this.list.focusNext(n, loop);242}243244focusPrevious(n?: number, loop?: boolean): void {245this.list.focusPrevious(n, loop);246}247248focusNextPage(): Promise<void> {249return this.list.focusNextPage();250}251252focusPreviousPage(): Promise<void> {253return this.list.focusPreviousPage();254}255256focusLast(): void {257this.list.focusLast();258}259260focusFirst(): void {261this.list.focusFirst();262}263264getFocus(): number[] {265return this.list.getFocus();266}267268setSelection(indexes: number[], browserEvent?: UIEvent): void {269this.list.setSelection(indexes, browserEvent);270}271272getSelection(): number[] {273return this.list.getSelection();274}275276getSelectedElements(): T[] {277return this.getSelection().map(i => this.model.get(i));278}279280layout(height?: number, width?: number): void {281this.list.layout(height, width);282}283284triggerTypeNavigation(): void {285this.list.triggerTypeNavigation();286}287288reveal(index: number, relativeTop?: number): void {289this.list.reveal(index, relativeTop);290}291292style(styles: IListStyles): void {293this.list.style(styles);294}295296dispose(): void {297this.list.dispose();298}299}300301302