Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/list/listPaging.ts
5251 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 { range } from '../../../common/arrays.js';
7
import { CancellationTokenSource } from '../../../common/cancellation.js';
8
import { Event } from '../../../common/event.js';
9
import { Disposable, DisposableStore, IDisposable } from '../../../common/lifecycle.js';
10
import { IPagedModel } from '../../../common/paging.js';
11
import { ScrollbarVisibility } from '../../../common/scrollable.js';
12
import './list.css';
13
import { IListContextMenuEvent, IListElementRenderDetails, IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from './list.js';
14
import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List, TypeNavigationMode } from './listWidget.js';
15
import { isActiveElement } from '../../dom.js';
16
17
export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
18
renderPlaceholder(index: number, templateData: TTemplateData): void;
19
}
20
21
export interface ITemplateData<T> {
22
data?: T;
23
disposable?: IDisposable;
24
}
25
26
class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, ITemplateData<TTemplateData>> {
27
28
get templateId(): string { return this.renderer.templateId; }
29
30
constructor(
31
private renderer: IPagedRenderer<TElement, TTemplateData>,
32
private modelProvider: () => IPagedModel<TElement>
33
) { }
34
35
renderTemplate(container: HTMLElement): ITemplateData<TTemplateData> {
36
const data = this.renderer.renderTemplate(container);
37
return { data, disposable: Disposable.None };
38
}
39
40
renderElement(index: number, _: number, data: ITemplateData<TTemplateData>, details?: IListElementRenderDetails): void {
41
data.disposable?.dispose();
42
43
if (!data.data) {
44
return;
45
}
46
47
const model = this.modelProvider();
48
49
if (model.isResolved(index)) {
50
return this.renderer.renderElement(model.get(index), index, data.data, details);
51
}
52
53
const cts = new CancellationTokenSource();
54
const promise = model.resolve(index, cts.token);
55
data.disposable = { dispose: () => cts.cancel() };
56
57
this.renderer.renderPlaceholder(index, data.data);
58
promise.then(entry => this.renderer.renderElement(entry, index, data.data!, details));
59
}
60
61
disposeTemplate(data: ITemplateData<TTemplateData>): void {
62
if (data.disposable) {
63
data.disposable.dispose();
64
data.disposable = undefined;
65
}
66
if (data.data) {
67
this.renderer.disposeTemplate(data.data);
68
data.data = undefined;
69
}
70
}
71
}
72
73
class PagedAccessibilityProvider<T> implements IListAccessibilityProvider<number> {
74
75
constructor(
76
private modelProvider: () => IPagedModel<T>,
77
private accessibilityProvider: IListAccessibilityProvider<T>
78
) { }
79
80
getWidgetAriaLabel() {
81
return this.accessibilityProvider.getWidgetAriaLabel();
82
}
83
84
getAriaLabel(index: number) {
85
const model = this.modelProvider();
86
87
if (!model.isResolved(index)) {
88
return null;
89
}
90
91
return this.accessibilityProvider.getAriaLabel(model.get(index));
92
}
93
}
94
95
export interface IPagedListOptions<T> {
96
readonly typeNavigationEnabled?: boolean;
97
readonly typeNavigationMode?: TypeNavigationMode;
98
readonly ariaLabel?: string;
99
readonly keyboardSupport?: boolean;
100
readonly multipleSelectionSupport?: boolean;
101
readonly accessibilityProvider?: IListAccessibilityProvider<T>;
102
103
// list view options
104
readonly useShadows?: boolean;
105
readonly verticalScrollMode?: ScrollbarVisibility;
106
readonly setRowLineHeight?: boolean;
107
readonly setRowHeight?: boolean;
108
readonly supportDynamicHeights?: boolean;
109
readonly mouseSupport?: boolean;
110
readonly horizontalScrolling?: boolean;
111
readonly scrollByPage?: boolean;
112
readonly paddingBottom?: number;
113
readonly alwaysConsumeMouseWheel?: boolean;
114
}
115
116
function fromPagedListOptions<T>(modelProvider: () => IPagedModel<T>, options: IPagedListOptions<T>): IListOptions<number> {
117
return {
118
...options,
119
accessibilityProvider: options.accessibilityProvider && new PagedAccessibilityProvider(modelProvider, options.accessibilityProvider)
120
};
121
}
122
123
export class PagedList<T> implements IDisposable {
124
125
private readonly list: List<number>;
126
private _model!: IPagedModel<T>;
127
private readonly modelDisposables = new DisposableStore();
128
129
constructor(
130
user: string,
131
container: HTMLElement,
132
virtualDelegate: IListVirtualDelegate<number>,
133
renderers: IPagedRenderer<T, any>[],
134
options: IPagedListOptions<T> = {}
135
) {
136
const modelProvider = () => this.model;
137
const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, modelProvider));
138
this.list = new List(user, container, virtualDelegate, pagedRenderers, fromPagedListOptions(modelProvider, options));
139
}
140
141
updateOptions(options: IListOptionsUpdate) {
142
this.list.updateOptions(options);
143
}
144
145
getHTMLElement(): HTMLElement {
146
return this.list.getHTMLElement();
147
}
148
149
isDOMFocused(): boolean {
150
return isActiveElement(this.getHTMLElement());
151
}
152
153
domFocus(): void {
154
this.list.domFocus();
155
}
156
157
get onDidFocus(): Event<void> {
158
return this.list.onDidFocus;
159
}
160
161
get onDidBlur(): Event<void> {
162
return this.list.onDidBlur;
163
}
164
165
get widget(): List<number> {
166
return this.list;
167
}
168
169
get onDidDispose(): Event<void> {
170
return this.list.onDidDispose;
171
}
172
173
get onMouseClick(): Event<IListMouseEvent<T>> {
174
return Event.map(this.list.onMouseClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
175
}
176
177
get onMouseDblClick(): Event<IListMouseEvent<T>> {
178
return Event.map(this.list.onMouseDblClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
179
}
180
181
get onTap(): Event<IListMouseEvent<T>> {
182
return Event.map(this.list.onTap, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
183
}
184
185
get onPointer(): Event<IListMouseEvent<T>> {
186
return Event.map(this.list.onPointer, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
187
}
188
189
get onDidChangeFocus(): Event<IListEvent<T>> {
190
return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
191
}
192
193
get onDidChangeSelection(): Event<IListEvent<T>> {
194
return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
195
}
196
197
get onContextMenu(): Event<IListContextMenuEvent<T>> {
198
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 }));
199
}
200
201
get model(): IPagedModel<T> {
202
return this._model;
203
}
204
205
set model(model: IPagedModel<T>) {
206
this.modelDisposables.clear();
207
this._model = model;
208
this.list.splice(0, this.list.length, range(model.length));
209
this.modelDisposables.add(model.onDidIncrementLength(newLength => this.list.splice(this.list.length, 0, range(this.list.length, newLength))));
210
}
211
212
get length(): number {
213
return this.list.length;
214
}
215
216
get scrollTop(): number {
217
return this.list.scrollTop;
218
}
219
220
set scrollTop(scrollTop: number) {
221
this.list.scrollTop = scrollTop;
222
}
223
224
get scrollLeft(): number {
225
return this.list.scrollLeft;
226
}
227
228
set scrollLeft(scrollLeft: number) {
229
this.list.scrollLeft = scrollLeft;
230
}
231
232
setAnchor(index: number | undefined): void {
233
this.list.setAnchor(index);
234
}
235
236
getAnchor(): number | undefined {
237
return this.list.getAnchor();
238
}
239
240
setFocus(indexes: number[]): void {
241
this.list.setFocus(indexes);
242
}
243
244
focusNext(n?: number, loop?: boolean): void {
245
this.list.focusNext(n, loop);
246
}
247
248
focusPrevious(n?: number, loop?: boolean): void {
249
this.list.focusPrevious(n, loop);
250
}
251
252
focusNextPage(): Promise<void> {
253
return this.list.focusNextPage();
254
}
255
256
focusPreviousPage(): Promise<void> {
257
return this.list.focusPreviousPage();
258
}
259
260
focusLast(): void {
261
this.list.focusLast();
262
}
263
264
focusFirst(): void {
265
this.list.focusFirst();
266
}
267
268
getFocus(): number[] {
269
return this.list.getFocus();
270
}
271
272
setSelection(indexes: number[], browserEvent?: UIEvent): void {
273
this.list.setSelection(indexes, browserEvent);
274
}
275
276
getSelection(): number[] {
277
return this.list.getSelection();
278
}
279
280
getSelectedElements(): T[] {
281
return this.getSelection().map(i => this.model.get(i));
282
}
283
284
layout(height?: number, width?: number): void {
285
this.list.layout(height, width);
286
}
287
288
triggerTypeNavigation(): void {
289
this.list.triggerTypeNavigation();
290
}
291
292
reveal(index: number, relativeTop?: number): void {
293
this.list.reveal(index, relativeTop);
294
}
295
296
style(styles: IListStyles): void {
297
this.list.style(styles);
298
}
299
300
dispose(): void {
301
this.list.dispose();
302
this.modelDisposables.dispose();
303
}
304
}
305
306