0
# Advanced Widgets
1
2
Specialized widgets for complex use cases including windowed lists, collapsible panels, search interfaces, and layout components. These widgets handle large datasets, provide advanced interactions, and support complex UI patterns.
3
4
## Capabilities
5
6
### WindowedList
7
8
High-performance widget for rendering large lists with virtual scrolling and dynamic sizing.
9
10
```typescript { .api }
11
/**
12
* Abstract model for windowed list data and behavior
13
*/
14
abstract class WindowedListModel implements WindowedList.IModel {
15
/**
16
* Create windowed list model
17
* @param options - Model configuration options
18
*/
19
constructor(options?: WindowedList.IModelOptions);
20
21
// Abstract methods that must be implemented
22
/** Estimate size of widget at given index */
23
abstract estimateWidgetSize: (index: number) => number;
24
/** Create widget for rendering item at index */
25
abstract widgetRenderer: (index: number) => Widget;
26
27
// Scroll behavior configuration
28
readonly scrollDownThreshold: number = 1;
29
readonly scrollUpThreshold: number = 0;
30
paddingTop: number = 0;
31
32
/** Total height of the viewport */
33
get height(): number;
34
set height(h: number);
35
36
/** Observable list of items */
37
get itemsList(): ISimpleObservableList | null;
38
set itemsList(v: ISimpleObservableList | null);
39
40
/** Number of items to render outside visible area */
41
get overscanCount(): number;
42
set overscanCount(newValue: number);
43
44
/** Current scroll position */
45
get scrollOffset(): number;
46
set scrollOffset(offset: number);
47
48
/** Total number of widgets/items */
49
get widgetCount(): number;
50
set widgetCount(newValue: number);
51
52
/** Whether windowing optimization is active */
53
get windowingActive(): boolean;
54
set windowingActive(newValue: boolean);
55
56
/** Signal emitted when model state changes */
57
get stateChanged(): ISignal<WindowedListModel, IChangedArgs<any, any, string>>;
58
59
// Methods for size and scroll calculations
60
getEstimatedTotalSize(): number;
61
getOffsetForIndexAndAlignment(/* parameters */): number;
62
getRangeToRender(): WindowedList.WindowIndex | null;
63
getSpan(startIndex: number, stopIndex: number): [number, number];
64
resetAfterIndex(index: number): void;
65
setWidgetSize(sizes: { index: number; size: number }[]): boolean;
66
}
67
68
/**
69
* High-performance windowed list widget for large datasets
70
*/
71
class WindowedList<T extends WindowedList.IModel = WindowedList.IModel, U = any> extends Widget {
72
static readonly DEFAULT_WIDGET_SIZE = 50;
73
74
/**
75
* Create windowed list widget
76
* @param options - Widget configuration options
77
*/
78
constructor(options: WindowedList.IOptions<T, U>);
79
80
/** Whether parent container is hidden */
81
get isParentHidden(): boolean;
82
set isParentHidden(v: boolean);
83
84
/** Layout manager for the list */
85
get layout(): WindowedLayout;
86
87
/** Outer container element */
88
get outerNode(): HTMLElement;
89
90
/** Viewport scrolling element */
91
get viewportNode(): HTMLElement;
92
93
/** Whether scrollbar is enabled */
94
get scrollbar(): boolean;
95
set scrollbar(enabled: boolean);
96
97
/**
98
* Scroll to specific offset
99
* @param scrollOffset - Pixel offset to scroll to
100
*/
101
scrollTo(scrollOffset: number): void;
102
103
/**
104
* Scroll to specific item index
105
* @param index - Item index to scroll to
106
* @param align - Alignment behavior
107
* @param margin - Additional margin
108
* @param alignPreference - Alignment preference
109
* @returns Promise that resolves when scroll completes
110
*/
111
scrollToItem(
112
index: number,
113
align?: WindowedList.ScrollToAlign,
114
margin?: number,
115
alignPreference?: WindowedList.BaseScrollToAlignment
116
): Promise<void>;
117
}
118
119
namespace WindowedList {
120
interface IModel<T = any> extends IDisposable {
121
estimateWidgetSize: (index: number) => number;
122
widgetRenderer: (index: number) => Widget;
123
// ... extensive interface with many properties
124
}
125
126
interface IOptions<T extends WindowedList.IModel = WindowedList.IModel, U = any> {
127
/** Model instance */
128
model: T;
129
/** Custom layout */
130
layout?: WindowedLayout;
131
/** Custom renderer */
132
renderer?: IRenderer<U>;
133
/** Enable scrollbar */
134
scrollbar?: boolean;
135
}
136
137
interface IRenderer<T = any> {
138
createOuter(): HTMLElement;
139
createScrollbar(): HTMLElement;
140
createScrollbarViewportIndicator?(): HTMLElement;
141
createScrollbarItem(list: WindowedList, index: number, item: T | undefined): HTMLElement | IRenderer.IScrollbarItem;
142
createViewport(): HTMLElement;
143
}
144
145
type BaseScrollToAlignment = 'center' | 'top-center' | 'start' | 'end';
146
type ScrollToAlign = 'auto' | 'smart' | BaseScrollToAlignment;
147
type WindowIndex = [number, number, number, number];
148
}
149
150
/**
151
* Observable list interface for windowed lists
152
*/
153
interface ISimpleObservableList<T> {
154
readonly length: number;
155
get(index: number): T | undefined;
156
set(index: number, value: T): void;
157
push(...values: T[]): number;
158
insert(index: number, value: T): void;
159
remove(index: number): T | undefined;
160
clear(): void;
161
}
162
```
163
164
**Usage Examples:**
165
166
```typescript
167
import { WindowedList, WindowedListModel, ReactWidget } from '@jupyterlab/ui-components';
168
import { Widget } from '@lumino/widgets';
169
170
// Custom model for file list
171
class FileListModel extends WindowedListModel {
172
constructor(private files: FileInfo[]) {
173
super();
174
this.widgetCount = files.length;
175
}
176
177
estimateWidgetSize = (index: number): number => {
178
// Estimate size based on file type
179
const file = this.files[index];
180
return file?.isDirectory ? 40 : 30;
181
};
182
183
widgetRenderer = (index: number): Widget => {
184
const file = this.files[index];
185
return ReactWidget.create(
186
<div className="file-item">
187
<span className="file-icon">{file.isDirectory ? '๐' : '๐'}</span>
188
<span className="file-name">{file.name}</span>
189
<span className="file-size">{file.size}</span>
190
</div>
191
);
192
};
193
}
194
195
// Create windowed file list
196
const fileModel = new FileListModel(largeFileArray);
197
const fileList = new WindowedList({
198
model: fileModel,
199
scrollbar: true
200
});
201
202
// Handle large datasets efficiently
203
fileModel.itemsList = new ObservableList(largeFileArray);
204
fileModel.windowingActive = true;
205
fileModel.overscanCount = 10; // Render 10 extra items outside viewport
206
207
// Scroll to specific file
208
async function scrollToFile(fileName: string) {
209
const index = largeFileArray.findIndex(f => f.name === fileName);
210
if (index >= 0) {
211
await fileList.scrollToItem(index, 'center');
212
}
213
}
214
215
// Custom renderer for complex items
216
class CustomFileRenderer implements WindowedList.IRenderer<FileInfo> {
217
createOuter(): HTMLElement {
218
const outer = document.createElement('div');
219
outer.className = 'custom-file-list';
220
return outer;
221
}
222
223
createViewport(): HTMLElement {
224
const viewport = document.createElement('div');
225
viewport.className = 'file-viewport';
226
return viewport;
227
}
228
229
createScrollbar(): HTMLElement {
230
const scrollbar = document.createElement('div');
231
scrollbar.className = 'custom-scrollbar';
232
return scrollbar;
233
}
234
235
createScrollbarItem(list: WindowedList, index: number, file: FileInfo | undefined) {
236
const item = document.createElement('div');
237
item.className = 'scrollbar-item';
238
if (file) {
239
item.textContent = file.name.charAt(0).toUpperCase();
240
}
241
return item;
242
}
243
}
244
```
245
246
### PanelWithToolbar
247
248
Panel widget that combines content area with integrated toolbar.
249
250
```typescript { .api }
251
/**
252
* Panel widget with integrated toolbar
253
*/
254
class PanelWithToolbar extends Panel implements Toolbar.IWidgetToolbar {
255
/**
256
* Create panel with toolbar
257
* @param options - Panel configuration options
258
*/
259
constructor(options?: PanelWithToolbar.IOptions);
260
261
/** Integrated toolbar instance */
262
get toolbar(): Toolbar;
263
}
264
265
namespace PanelWithToolbar {
266
interface IOptions extends Panel.IOptions {
267
/** Custom toolbar instance */
268
toolbar?: Toolbar;
269
}
270
}
271
```
272
273
**Usage Examples:**
274
275
```typescript
276
import { PanelWithToolbar, Toolbar, ToolbarButton } from '@jupyterlab/ui-components';
277
import { saveIcon, refreshIcon } from '@jupyterlab/ui-components';
278
279
// Create panel with integrated toolbar
280
const panel = new PanelWithToolbar();
281
panel.title.label = 'File Editor';
282
panel.addClass('editor-panel');
283
284
// Add buttons to toolbar
285
const saveButton = new ToolbarButton({
286
icon: saveIcon,
287
tooltip: 'Save file',
288
onClick: () => saveCurrentFile()
289
});
290
291
const refreshButton = new ToolbarButton({
292
icon: refreshIcon,
293
tooltip: 'Refresh content',
294
onClick: () => refreshContent()
295
});
296
297
panel.toolbar.addItem('save', saveButton);
298
panel.toolbar.addItem('refresh', refreshButton);
299
300
// Add content widgets
301
const editorWidget = new CodeEditorWrapper();
302
const statusWidget = new StatusBar();
303
304
panel.addWidget(editorWidget);
305
panel.addWidget(statusWidget);
306
307
// Custom toolbar for specific use case
308
const customToolbar = new Toolbar();
309
customToolbar.addClass('custom-editor-toolbar');
310
311
const customPanel = new PanelWithToolbar({
312
toolbar: customToolbar
313
});
314
```
315
316
### SidePanel
317
318
Widget for sidebars with accordion-style layout and toolbar integration.
319
320
```typescript { .api }
321
/**
322
* Widget for sidebars with accordion layout
323
*/
324
class SidePanel extends Widget {
325
/**
326
* Create side panel
327
* @param options - Side panel configuration options
328
*/
329
constructor(options?: SidePanel.IOptions);
330
331
/** Content panel for main widgets */
332
get content(): Panel;
333
334
/** Header panel for titles/controls */
335
get header(): Panel;
336
337
/** Integrated toolbar */
338
get toolbar(): Toolbar;
339
340
/** Array of contained widgets */
341
get widgets(): ReadonlyArray<Widget>;
342
343
/**
344
* Add widget to side panel
345
* @param widget - Widget with toolbar support
346
*/
347
addWidget(widget: Toolbar.IWidgetToolbar): void;
348
349
/**
350
* Insert widget at specific index
351
* @param index - Position to insert at
352
* @param widget - Widget with toolbar support
353
*/
354
insertWidget(index: number, widget: Toolbar.IWidgetToolbar): void;
355
}
356
357
namespace SidePanel {
358
interface IOptions extends AccordionPanel.IOptions {
359
/** Custom content panel */
360
content?: Panel;
361
/** Custom header panel */
362
header?: Panel;
363
/** Custom toolbar */
364
toolbar?: Toolbar;
365
/** Translator for internationalization */
366
translator?: ITranslator;
367
}
368
}
369
```
370
371
**Usage Examples:**
372
373
```typescript
374
import { SidePanel, ReactWidget } from '@jupyterlab/ui-components';
375
376
// Create side panel for file browser
377
const filePanel = new SidePanel();
378
filePanel.title.label = 'Files';
379
filePanel.addClass('file-browser-panel');
380
381
// Create widgets for side panel sections
382
class FileTreeWidget extends ReactWidget {
383
constructor() {
384
super();
385
this.title.label = 'File Tree';
386
this.addClass('file-tree-widget');
387
}
388
389
protected render() {
390
return <div>File tree content here</div>;
391
}
392
}
393
394
class SearchWidget extends ReactWidget {
395
constructor() {
396
super();
397
this.title.label = 'Search';
398
this.addClass('search-widget');
399
}
400
401
protected render() {
402
return <div>Search interface here</div>;
403
}
404
}
405
406
// Add sections to side panel
407
const fileTree = new FileTreeWidget();
408
const searchWidget = new SearchWidget();
409
410
filePanel.addWidget(fileTree);
411
filePanel.addWidget(searchWidget);
412
413
// Access panel components
414
console.log('Content panel:', filePanel.content);
415
console.log('Header panel:', filePanel.header);
416
console.log('Toolbar:', filePanel.toolbar);
417
418
// Add custom toolbar items
419
const refreshButton = new ToolbarButton({
420
icon: refreshIcon,
421
tooltip: 'Refresh files'
422
});
423
filePanel.toolbar.addItem('refresh', refreshButton);
424
```
425
426
### AccordionToolbar
427
428
Specialized accordion panel with toolbar support and custom rendering.
429
430
```typescript { .api }
431
/**
432
* Accordion panel with toolbar support
433
*/
434
namespace AccordionToolbar {
435
class Renderer extends AccordionPanel.Renderer {
436
/**
437
* Create collapse icon for section headers
438
* @param data - Title data for the section
439
* @returns HTML element for collapse icon
440
*/
441
createCollapseIcon(data: Title<Widget>): HTMLElement;
442
443
/**
444
* Create section title element
445
* @param data - Title data for the section
446
* @returns HTML element for section title
447
*/
448
createSectionTitle(data: Title<Widget>): HTMLElement;
449
}
450
451
/** Default renderer instance */
452
const defaultRenderer: Renderer;
453
454
/**
455
* Create layout for accordion with toolbar support
456
* @param options - Accordion panel options
457
* @returns Configured accordion layout
458
*/
459
function createLayout(options: AccordionPanel.IOptions): AccordionLayout;
460
}
461
```
462
463
**Usage Examples:**
464
465
```typescript
466
import { AccordionToolbar, AccordionPanel } from '@jupyterlab/ui-components';
467
468
// Create accordion with custom renderer
469
const accordion = new AccordionPanel({
470
renderer: AccordionToolbar.defaultRenderer
471
});
472
473
// Create layout with toolbar support
474
const layout = AccordionToolbar.createLayout({
475
renderer: AccordionToolbar.defaultRenderer
476
});
477
478
// Custom accordion with enhanced titles
479
class EnhancedAccordion extends AccordionPanel {
480
constructor() {
481
super({
482
renderer: new AccordionToolbar.Renderer()
483
});
484
}
485
}
486
487
const enhancedAccordion = new EnhancedAccordion();
488
489
// Add sections with custom titles
490
const section1 = new ReactWidget();
491
section1.title.label = 'Configuration';
492
section1.title.iconClass = 'jp-SettingsIcon';
493
494
const section2 = new ReactWidget();
495
section2.title.label = 'Advanced Options';
496
section2.title.iconClass = 'jp-AdvancedIcon';
497
498
enhancedAccordion.addWidget(section1);
499
enhancedAccordion.addWidget(section2);
500
```
501
502
### Menu Components
503
504
Enhanced menu system with ranking and disposable items.
505
506
```typescript { .api }
507
/**
508
* Extensible menu interface with ranking support
509
*/
510
interface IRankedMenu extends IDisposable {
511
/**
512
* Add group of menu items with shared rank
513
* @param items - Array of menu item options
514
* @param rank - Rank for ordering (default: 100)
515
* @returns Disposable for removing the group
516
*/
517
addGroup(items: Menu.IItemOptions[], rank?: number): IDisposable;
518
519
/**
520
* Add single menu item with rank
521
* @param options - Menu item options with rank
522
* @returns Disposable menu item
523
*/
524
addItem(options: IRankedMenu.IItemOptions): IDisposable;
525
526
/** Read-only array of menu items */
527
readonly items: ReadonlyArray<Menu.IItem>;
528
529
/** Optional rank for this menu */
530
readonly rank?: number;
531
}
532
533
namespace IRankedMenu {
534
const DEFAULT_RANK = 100;
535
536
interface IItemOptions extends Menu.IItemOptions {
537
/** Item rank for ordering */
538
rank?: number;
539
}
540
541
interface IOptions extends Menu.IOptions {
542
/** Include separators between ranked groups */
543
includeSeparators?: boolean;
544
/** Menu rank */
545
rank?: number;
546
}
547
}
548
549
/**
550
* Menu implementation with ranking support
551
*/
552
class RankedMenu extends Menu implements IRankedMenu {
553
/**
554
* Create ranked menu
555
* @param options - Menu configuration options
556
*/
557
constructor(options: IRankedMenu.IOptions);
558
559
/** Menu rank */
560
get rank(): number | undefined;
561
562
/**
563
* Add group of items with shared rank
564
*/
565
addGroup(items: IRankedMenu.IItemOptions[], rank?: number): IDisposable;
566
567
/**
568
* Add single item with rank
569
*/
570
addItem(options: IRankedMenu.IItemOptions): IDisposableMenuItem;
571
572
/**
573
* Get rank of item at index
574
* @param index - Item index
575
* @returns Item rank
576
*/
577
getRankAt(index: number): number;
578
}
579
580
/**
581
* Disposable menu item interface
582
*/
583
interface IDisposableMenuItem extends IDisposable {
584
// Menu item that can be disposed
585
}
586
```
587
588
**Usage Examples:**
589
590
```typescript
591
import { RankedMenu, IRankedMenu, CommandRegistry } from '@jupyterlab/ui-components';
592
593
// Create ranked menu with commands
594
const commands = new CommandRegistry();
595
596
commands.addCommand('file:new', {
597
label: 'New File',
598
execute: () => console.log('New file')
599
});
600
601
commands.addCommand('file:open', {
602
label: 'Open File',
603
execute: () => console.log('Open file')
604
});
605
606
const fileMenu = new RankedMenu({
607
commands,
608
includeSeparators: true
609
});
610
611
// Add file operations group (high priority)
612
const fileGroup = fileMenu.addGroup([
613
{ command: 'file:new', rank: 10 },
614
{ command: 'file:open', rank: 20 }
615
], 1);
616
617
// Add recent files group (lower priority)
618
const recentGroup = fileMenu.addGroup([
619
{ type: 'submenu', submenu: recentFilesMenu, rank: 10 }
620
], 50);
621
622
// Add individual items
623
const saveItem = fileMenu.addItem({
624
command: 'file:save',
625
rank: 30
626
});
627
628
// Clean up when done
629
function cleanup() {
630
fileGroup.dispose();
631
recentGroup.dispose();
632
saveItem.dispose();
633
}
634
635
// Check item ranks
636
for (let i = 0; i < fileMenu.items.length; i++) {
637
console.log(`Item ${i} rank:`, fileMenu.getRankAt(i));
638
}
639
```
640
641
### Styling Utilities
642
643
Node styling utilities for consistent widget appearance.
644
645
```typescript { .api }
646
/**
647
* Node styling utilities
648
*/
649
namespace Styling {
650
/**
651
* Apply styling to HTML element
652
* @param node - Element to style
653
* @param className - CSS class to apply
654
*/
655
function styleNode(node: HTMLElement, className?: string): void;
656
657
/**
658
* Apply styling to elements by tag name
659
* @param node - Container element
660
* @param tagName - Tag name to target
661
* @param className - CSS class to apply
662
*/
663
function styleNodeByTag(node: HTMLElement, tagName: string, className?: string): void;
664
665
/**
666
* Wrap select element with custom styling
667
* @param node - Select element to wrap
668
* @param multiple - Whether select allows multiple selections
669
* @returns Wrapped container element
670
*/
671
function wrapSelect(node: HTMLSelectElement, multiple?: boolean): HTMLElement;
672
}
673
```
674
675
**Usage Examples:**
676
677
```typescript
678
import { Styling } from '@jupyterlab/ui-components';
679
680
// Style widget nodes
681
class CustomWidget extends Widget {
682
onAfterAttach() {
683
super.onAfterAttach();
684
685
// Apply JupyterLab styling
686
Styling.styleNode(this.node, 'jp-CustomWidget');
687
688
// Style all buttons in widget
689
Styling.styleNodeByTag(this.node, 'button', 'jp-Button');
690
691
// Style all inputs
692
Styling.styleNodeByTag(this.node, 'input', 'jp-InputGroup-input');
693
}
694
}
695
696
// Wrap select elements
697
const selectElement = document.createElement('select');
698
selectElement.innerHTML = `
699
<option value="1">Option 1</option>
700
<option value="2">Option 2</option>
701
`;
702
703
const wrappedSelect = Styling.wrapSelect(selectElement);
704
document.body.appendChild(wrappedSelect);
705
706
// Multi-select wrapper
707
const multiSelect = document.createElement('select');
708
multiSelect.multiple = true;
709
const wrappedMulti = Styling.wrapSelect(multiSelect, true);
710
```