/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import 'vs/css!./list'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent } from './list'; import { List, IListStyles, IListOptions, IListAccessibilityProvider, IListOptionsUpdate } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import { Event } from 'vs/base/common/event'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { IThemable } from 'vs/base/common/styler'; export interface IPagedRenderer extends IListRenderer { renderPlaceholder(index: number, templateData: TTemplateData): void; } export interface ITemplateData { data?: T; disposable?: IDisposable; } class PagedRenderer implements IListRenderer> { get templateId(): string { return this.renderer.templateId; } constructor( private renderer: IPagedRenderer, private modelProvider: () => IPagedModel ) { } renderTemplate(container: HTMLElement): ITemplateData { const data = this.renderer.renderTemplate(container); return { data, disposable: Disposable.None }; } renderElement(index: number, _: number, data: ITemplateData, height: number | undefined): void { if (data.disposable) { data.disposable.dispose(); } if (!data.data) { return; } const model = this.modelProvider(); if (model.isResolved(index)) { return this.renderer.renderElement(model.get(index), index, data.data, height); } const cts = new CancellationTokenSource(); const promise = model.resolve(index, cts.token); data.disposable = { dispose: () => cts.cancel() }; this.renderer.renderPlaceholder(index, data.data); promise.then(entry => this.renderer.renderElement(entry, index, data.data!, height)); } disposeTemplate(data: ITemplateData): void { if (data.disposable) { data.disposable.dispose(); data.disposable = undefined; } if (data.data) { this.renderer.disposeTemplate(data.data); data.data = undefined; } } } class PagedAccessibilityProvider implements IListAccessibilityProvider { constructor( private modelProvider: () => IPagedModel, private accessibilityProvider: IListAccessibilityProvider ) { } getWidgetAriaLabel(): string { return this.accessibilityProvider.getWidgetAriaLabel(); } getAriaLabel(index: number): string | null { const model = this.modelProvider(); if (!model.isResolved(index)) { return null; } return this.accessibilityProvider.getAriaLabel(model.get(index)); } } export interface IPagedListOptions { readonly enableKeyboardNavigation?: boolean; readonly automaticKeyboardNavigation?: boolean; readonly ariaLabel?: string; readonly keyboardSupport?: boolean; readonly multipleSelectionSupport?: boolean; readonly accessibilityProvider?: IListAccessibilityProvider; // list view options readonly useShadows?: boolean; readonly verticalScrollMode?: ScrollbarVisibility; readonly setRowLineHeight?: boolean; readonly setRowHeight?: boolean; readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; readonly additionalScrollHeight?: number; } function fromPagedListOptions(modelProvider: () => IPagedModel, options: IPagedListOptions): IListOptions { return { ...options, accessibilityProvider: options.accessibilityProvider && new PagedAccessibilityProvider(modelProvider, options.accessibilityProvider) }; } export class PagedList implements IThemable, IDisposable { private list: List; private _model!: IPagedModel; constructor( user: string, container: HTMLElement, virtualDelegate: IListVirtualDelegate, renderers: IPagedRenderer[], options: IPagedListOptions = {} ) { const modelProvider = () => this.model; const pagedRenderers = renderers.map(r => new PagedRenderer>(r, modelProvider)); this.list = new List(user, container, virtualDelegate, pagedRenderers, fromPagedListOptions(modelProvider, options)); } updateOptions(options: IListOptionsUpdate) { this.list.updateOptions(options); } getHTMLElement(): HTMLElement { return this.list.getHTMLElement(); } isDOMFocused(): boolean { return this.list.getHTMLElement() === document.activeElement; } domFocus(): void { this.list.domFocus(); } get onDidFocus(): Event { return this.list.onDidFocus; } get onDidBlur(): Event { return this.list.onDidBlur; } get widget(): List { return this.list; } get onDidDispose(): Event { return this.list.onDidDispose; } get onMouseClick(): Event> { return Event.map(this.list.onMouseClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent })); } get onMouseDblClick(): Event> { return Event.map(this.list.onMouseDblClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent })); } get onTap(): Event> { return Event.map(this.list.onTap, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent })); } get onPointer(): Event> { return Event.map(this.list.onPointer, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent })); } get onDidChangeFocus(): Event> { return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } get onDidChangeSelection(): Event> { return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } get onContextMenu(): Event> { return Event.map(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent })); } get model(): IPagedModel { return this._model; } set model(model: IPagedModel) { this._model = model; this.list.splice(0, this.list.length, range(model.length)); } get length(): number { return this.list.length; } get scrollTop(): number { return this.list.scrollTop; } set scrollTop(scrollTop: number) { this.list.scrollTop = scrollTop; } get scrollLeft(): number { return this.list.scrollLeft; } set scrollLeft(scrollLeft: number) { this.list.scrollLeft = scrollLeft; } setFocus(indexes: number[]): void { this.list.setFocus(indexes); } focusNext(n?: number, loop?: boolean): void { this.list.focusNext(n, loop); } focusPrevious(n?: number, loop?: boolean): void { this.list.focusPrevious(n, loop); } focusNextPage(): void { this.list.focusNextPage(); } focusPreviousPage(): void { this.list.focusPreviousPage(); } getFocus(): number[] { return this.list.getFocus(); } setSelection(indexes: number[], browserEvent?: UIEvent): void { this.list.setSelection(indexes, browserEvent); } getSelection(): number[] { return this.list.getSelection(); } layout(height?: number, width?: number): void { this.list.layout(height, width); } toggleKeyboardNavigation(): void { this.list.toggleKeyboardNavigation(); } reveal(index: number, relativeTop?: number): void { this.list.reveal(index, relativeTop); } style(styles: IListStyles): void { this.list.style(styles); } dispose(): void { this.list.dispose(); } }