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
3296 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, 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 list: List<number>;
126
private _model!: IPagedModel<T>;
127
128
constructor(
129
user: string,
130
container: HTMLElement,
131
virtualDelegate: IListVirtualDelegate<number>,
132
renderers: IPagedRenderer<T, any>[],
133
options: IPagedListOptions<T> = {}
134
) {
135
const modelProvider = () => this.model;
136
const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, modelProvider));
137
this.list = new List(user, container, virtualDelegate, pagedRenderers, fromPagedListOptions(modelProvider, options));
138
}
139
140
updateOptions(options: IListOptionsUpdate) {
141
this.list.updateOptions(options);
142
}
143
144
getHTMLElement(): HTMLElement {
145
return this.list.getHTMLElement();
146
}
147
148
isDOMFocused(): boolean {
149
return isActiveElement(this.getHTMLElement());
150
}
151
152
domFocus(): void {
153
this.list.domFocus();
154
}
155
156
get onDidFocus(): Event<void> {
157
return this.list.onDidFocus;
158
}
159
160
get onDidBlur(): Event<void> {
161
return this.list.onDidBlur;
162
}
163
164
get widget(): List<number> {
165
return this.list;
166
}
167
168
get onDidDispose(): Event<void> {
169
return this.list.onDidDispose;
170
}
171
172
get onMouseClick(): Event<IListMouseEvent<T>> {
173
return Event.map(this.list.onMouseClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
174
}
175
176
get onMouseDblClick(): Event<IListMouseEvent<T>> {
177
return Event.map(this.list.onMouseDblClick, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
178
}
179
180
get onTap(): Event<IListMouseEvent<T>> {
181
return Event.map(this.list.onTap, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
182
}
183
184
get onPointer(): Event<IListMouseEvent<T>> {
185
return Event.map(this.list.onPointer, ({ element, index, browserEvent }) => ({ element: element === undefined ? undefined : this._model.get(element), index, browserEvent }));
186
}
187
188
get onDidChangeFocus(): Event<IListEvent<T>> {
189
return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
190
}
191
192
get onDidChangeSelection(): Event<IListEvent<T>> {
193
return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
194
}
195
196
get onContextMenu(): Event<IListContextMenuEvent<T>> {
197
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 }));
198
}
199
200
get model(): IPagedModel<T> {
201
return this._model;
202
}
203
204
set model(model: IPagedModel<T>) {
205
this._model = model;
206
this.list.splice(0, this.list.length, range(model.length));
207
}
208
209
get length(): number {
210
return this.list.length;
211
}
212
213
get scrollTop(): number {
214
return this.list.scrollTop;
215
}
216
217
set scrollTop(scrollTop: number) {
218
this.list.scrollTop = scrollTop;
219
}
220
221
get scrollLeft(): number {
222
return this.list.scrollLeft;
223
}
224
225
set scrollLeft(scrollLeft: number) {
226
this.list.scrollLeft = scrollLeft;
227
}
228
229
setAnchor(index: number | undefined): void {
230
this.list.setAnchor(index);
231
}
232
233
getAnchor(): number | undefined {
234
return this.list.getAnchor();
235
}
236
237
setFocus(indexes: number[]): void {
238
this.list.setFocus(indexes);
239
}
240
241
focusNext(n?: number, loop?: boolean): void {
242
this.list.focusNext(n, loop);
243
}
244
245
focusPrevious(n?: number, loop?: boolean): void {
246
this.list.focusPrevious(n, loop);
247
}
248
249
focusNextPage(): Promise<void> {
250
return this.list.focusNextPage();
251
}
252
253
focusPreviousPage(): Promise<void> {
254
return this.list.focusPreviousPage();
255
}
256
257
focusLast(): void {
258
this.list.focusLast();
259
}
260
261
focusFirst(): void {
262
this.list.focusFirst();
263
}
264
265
getFocus(): number[] {
266
return this.list.getFocus();
267
}
268
269
setSelection(indexes: number[], browserEvent?: UIEvent): void {
270
this.list.setSelection(indexes, browserEvent);
271
}
272
273
getSelection(): number[] {
274
return this.list.getSelection();
275
}
276
277
getSelectedElements(): T[] {
278
return this.getSelection().map(i => this.model.get(i));
279
}
280
281
layout(height?: number, width?: number): void {
282
this.list.layout(height, width);
283
}
284
285
triggerTypeNavigation(): void {
286
this.list.triggerTypeNavigation();
287
}
288
289
reveal(index: number, relativeTop?: number): void {
290
this.list.reveal(index, relativeTop);
291
}
292
293
style(styles: IListStyles): void {
294
this.list.style(styles);
295
}
296
297
dispose(): void {
298
this.list.dispose();
299
}
300
}
301
302