0
# Utilities
1
2
Helper functions and utilities for styling, DOM manipulation, positioning, and React integration. These utilities provide common functionality needed when building JupyterLab applications and widgets.
3
4
## Capabilities
5
6
### CSS Class Utilities
7
8
Functions for managing and combining CSS class names in a clean, efficient way.
9
10
```typescript { .api }
11
/**
12
* Combine CSS class names, filtering out falsy values
13
* @param classes - Array of class names, objects, or falsy values
14
* @returns Single combined class string
15
*/
16
function classes(
17
...classes: (string | false | undefined | null | { [className: string]: any })[]
18
): string;
19
20
/**
21
* Combine CSS class names removing duplicates
22
* @param classes - Array of class names, objects, or falsy values
23
* @returns Single combined class string without duplicates
24
*/
25
function classesDedupe(
26
...classes: (string | false | undefined | null | { [className: string]: any })[]
27
): string;
28
```
29
30
**Usage Examples:**
31
32
```typescript
33
import { classes, classesDedupe } from '@jupyterlab/ui-components';
34
35
// Basic class combination
36
const buttonClass = classes(
37
'jp-Button',
38
isActive && 'jp-mod-active',
39
isDisabled && 'jp-mod-disabled',
40
customClass
41
);
42
43
// Conditional classes with objects
44
const widgetClass = classes(
45
'jp-Widget',
46
{
47
'jp-mod-hidden': !visible,
48
'jp-mod-focused': hasFocus,
49
'jp-mod-current': isCurrent
50
},
51
additionalClasses
52
);
53
54
// Remove duplicates when combining from multiple sources
55
const combinedClass = classesDedupe(
56
baseClasses,
57
themeClasses,
58
stateClasses,
59
userClasses
60
);
61
62
// Use in React components
63
function MyButton({ active, disabled, className }: ButtonProps) {
64
return (
65
<button
66
className={classes(
67
'my-button',
68
active && 'active',
69
disabled && 'disabled',
70
className
71
)}
72
>
73
Click me
74
</button>
75
);
76
}
77
78
// Complex conditional styling
79
const iconClass = classes(
80
'icon',
81
size === 'small' && 'icon-small',
82
size === 'large' && 'icon-large',
83
{
84
'icon-spin': spinning,
85
'icon-disabled': !enabled,
86
'icon-primary': variant === 'primary',
87
'icon-secondary': variant === 'secondary'
88
}
89
);
90
```
91
92
### DOM Utilities
93
94
Functions for working with DOM elements and converting between DOM and React patterns.
95
96
```typescript { .api }
97
/**
98
* Convert DOM element attributes to React props
99
* @param elem - DOM element to extract attributes from
100
* @param options - Options for conversion
101
* @returns Object with React-compatible props
102
*/
103
function getReactAttrs(
104
elem: Element,
105
options?: { ignore?: string[] }
106
): { [key: string]: string | null };
107
108
/**
109
* Find tree item element in DOM hierarchy
110
* @param el - Element to start search from
111
* @returns Tree item element or null if not found
112
*/
113
function getTreeItemElement(el: HTMLElement): HTMLElement | null;
114
```
115
116
**Usage Examples:**
117
118
```typescript
119
import { getReactAttrs, getTreeItemElement } from '@jupyterlab/ui-components';
120
121
// Convert DOM element to React props
122
function domElementToReact(element: Element) {
123
const props = getReactAttrs(element, {
124
ignore: ['style', 'class'] // Ignore these attributes
125
});
126
127
return (
128
<div {...props}>
129
Converted from DOM element
130
</div>
131
);
132
}
133
134
// Handle tree interactions
135
function handleTreeClick(event: MouseEvent) {
136
const target = event.target as HTMLElement;
137
const treeItem = getTreeItemElement(target);
138
139
if (treeItem) {
140
const itemId = treeItem.dataset.itemId;
141
console.log('Clicked tree item:', itemId);
142
143
// Handle tree item selection
144
selectTreeItem(itemId);
145
}
146
}
147
148
// Widget that uses DOM utilities
149
class InteractiveWidget extends Widget {
150
onAfterAttach() {
151
super.onAfterAttach();
152
153
// Set up tree interaction handlers
154
this.node.addEventListener('click', (event) => {
155
const treeItem = getTreeItemElement(event.target as HTMLElement);
156
if (treeItem) {
157
this.handleTreeItemClick(treeItem);
158
}
159
});
160
}
161
162
private handleTreeItemClick(element: HTMLElement) {
163
// Convert DOM attributes to work with
164
const attrs = getReactAttrs(element);
165
console.log('Tree item attributes:', attrs);
166
}
167
}
168
169
// Extract data attributes
170
function extractDataAttributes(element: Element) {
171
const attrs = getReactAttrs(element);
172
const dataAttrs: { [key: string]: string } = {};
173
174
Object.entries(attrs).forEach(([key, value]) => {
175
if (key.startsWith('data-') && value !== null) {
176
dataAttrs[key] = value;
177
}
178
});
179
180
return dataAttrs;
181
}
182
```
183
184
### HoverBox Positioning
185
186
Utility for positioning hover elements and tooltips relative to anchor elements.
187
188
```typescript { .api }
189
/**
190
* HoverBox positioning utilities
191
*/
192
type OutOfViewDisplay = 'hidden-inside' | 'hidden-outside' | 'stick-inside' | 'stick-outside';
193
194
namespace HoverBox {
195
interface IOptions {
196
/** Anchor element or position to attach to */
197
anchor: IAnchor;
198
/** Host element that contains the positioned element */
199
host: HTMLElement;
200
/** Maximum height for the positioned element */
201
maxHeight: number;
202
/** Minimum height for the positioned element */
203
minHeight: number;
204
/** Element to be positioned */
205
node: HTMLElement;
206
/** Positioning offsets */
207
offset?: {
208
horizontal?: number;
209
vertical?: { above?: number; below?: number };
210
};
211
/** Vertical positioning preference */
212
privilege?: 'above' | 'below' | 'forceAbove' | 'forceBelow';
213
/** Custom style overrides */
214
style?: CSSStyleDeclaration;
215
/** Behavior when element goes out of view */
216
outOfViewDisplay?: {
217
top?: OutOfViewDisplay;
218
bottom?: OutOfViewDisplay;
219
left?: OutOfViewDisplay;
220
right?: OutOfViewDisplay;
221
};
222
/** Fixed size for the element */
223
size?: { width: number; height: number; };
224
}
225
226
/** Anchor position interface */
227
interface IAnchor extends Pick<DOMRect, 'left' | 'right' | 'top' | 'bottom'> {}
228
229
/**
230
* Set geometry for positioned element
231
* @param options - Positioning configuration
232
*/
233
function setGeometry(options: IOptions): void;
234
}
235
```
236
237
**Usage Examples:**
238
239
```typescript
240
import { HoverBox } from '@jupyterlab/ui-components';
241
242
// Create tooltip positioning
243
function createTooltip(anchorElement: HTMLElement, content: string) {
244
const tooltip = document.createElement('div');
245
tooltip.className = 'tooltip';
246
tooltip.textContent = content;
247
tooltip.style.position = 'absolute';
248
tooltip.style.zIndex = '1000';
249
250
document.body.appendChild(tooltip);
251
252
// Position tooltip relative to anchor
253
const anchorRect = anchorElement.getBoundingClientRect();
254
255
HoverBox.setGeometry({
256
anchor: {
257
left: anchorRect.left,
258
right: anchorRect.right,
259
top: anchorRect.top,
260
bottom: anchorRect.bottom
261
},
262
host: document.body,
263
node: tooltip,
264
maxHeight: 200,
265
minHeight: 20,
266
privilege: 'above',
267
offset: {
268
vertical: { above: 5, below: 5 },
269
horizontal: 0
270
}
271
});
272
273
return tooltip;
274
}
275
276
// Advanced positioning for dropdown
277
class DropdownWidget extends Widget {
278
private _dropdown: HTMLElement;
279
280
constructor() {
281
super();
282
this._dropdown = this.createDropdown();
283
}
284
285
showDropdown() {
286
const buttonRect = this.node.getBoundingClientRect();
287
288
HoverBox.setGeometry({
289
anchor: {
290
left: buttonRect.left,
291
right: buttonRect.right,
292
top: buttonRect.top,
293
bottom: buttonRect.bottom
294
},
295
host: document.body,
296
node: this._dropdown,
297
maxHeight: 300,
298
minHeight: 50,
299
privilege: 'below',
300
offset: {
301
vertical: { below: 2 }
302
},
303
outOfViewDisplay: {
304
bottom: 'stick',
305
top: 'stick'
306
}
307
});
308
309
this._dropdown.style.display = 'block';
310
}
311
312
hideDropdown() {
313
this._dropdown.style.display = 'none';
314
}
315
316
private createDropdown(): HTMLElement {
317
const dropdown = document.createElement('div');
318
dropdown.className = 'dropdown-menu';
319
dropdown.style.position = 'absolute';
320
dropdown.style.display = 'none';
321
dropdown.style.zIndex = '1000';
322
323
document.body.appendChild(dropdown);
324
return dropdown;
325
}
326
}
327
328
// Context menu positioning
329
function showContextMenu(event: MouseEvent, items: MenuItem[]) {
330
const menu = document.createElement('div');
331
menu.className = 'context-menu';
332
333
items.forEach(item => {
334
const menuItem = document.createElement('div');
335
menuItem.className = 'context-menu-item';
336
menuItem.textContent = item.label;
337
menuItem.onclick = item.action;
338
menu.appendChild(menuItem);
339
});
340
341
document.body.appendChild(menu);
342
343
// Position at mouse location
344
HoverBox.setGeometry({
345
anchor: {
346
left: event.clientX,
347
right: event.clientX,
348
top: event.clientY,
349
bottom: event.clientY
350
},
351
host: document.body,
352
node: menu,
353
maxHeight: 400,
354
minHeight: 30,
355
privilege: 'below',
356
outOfViewDisplay: {
357
right: 'stick',
358
bottom: 'stick'
359
}
360
});
361
}
362
```
363
364
### Element Reference Utilities
365
366
Interface for managing element references in React components.
367
368
```typescript { .api }
369
/**
370
* Interface for element reference properties
371
*/
372
interface IElementRefProps<E extends HTMLElement> {
373
/** Callback function to receive element reference */
374
elementRef?: (ref: E | null) => void;
375
}
376
377
/**
378
* Default style class constant
379
*/
380
const DEFAULT_STYLE_CLASS = 'jp-DefaultStyle';
381
```
382
383
**Usage Examples:**
384
385
```typescript
386
import { IElementRefProps, DEFAULT_STYLE_CLASS } from '@jupyterlab/ui-components';
387
388
// Component with element reference
389
interface CustomInputProps extends IElementRefProps<HTMLInputElement> {
390
placeholder?: string;
391
value?: string;
392
onChange?: (value: string) => void;
393
}
394
395
function CustomInput({ elementRef, placeholder, value, onChange }: CustomInputProps) {
396
const handleRef = (element: HTMLInputElement | null) => {
397
if (element) {
398
element.classList.add(DEFAULT_STYLE_CLASS);
399
}
400
elementRef?.(element);
401
};
402
403
return (
404
<input
405
ref={handleRef}
406
placeholder={placeholder}
407
value={value}
408
onChange={(e) => onChange?.(e.target.value)}
409
className="custom-input"
410
/>
411
);
412
}
413
414
// Use with element reference
415
function ParentComponent() {
416
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null);
417
418
const focusInput = () => {
419
inputElement?.focus();
420
};
421
422
return (
423
<div>
424
<CustomInput
425
elementRef={setInputElement}
426
placeholder="Enter text..."
427
/>
428
<button onClick={focusInput}>Focus Input</button>
429
</div>
430
);
431
}
432
433
// Widget that uses element references
434
class FormWidget extends ReactWidget {
435
private _formElement: HTMLFormElement | null = null;
436
437
protected render() {
438
return (
439
<form
440
ref={(el) => this._formElement = el}
441
className={DEFAULT_STYLE_CLASS}
442
>
443
<CustomInput
444
elementRef={(el) => console.log('Input element:', el)}
445
placeholder="Widget input"
446
/>
447
</form>
448
);
449
}
450
451
submitForm() {
452
if (this._formElement) {
453
this._formElement.requestSubmit();
454
}
455
}
456
}
457
458
// Generic element ref hook pattern
459
function useElementRef<T extends HTMLElement>() {
460
const [element, setElement] = useState<T | null>(null);
461
462
const ref = useCallback((el: T | null) => {
463
if (el) {
464
el.classList.add(DEFAULT_STYLE_CLASS);
465
}
466
setElement(el);
467
}, []);
468
469
return [element, ref] as const;
470
}
471
```
472
473
### Node Styling Utilities
474
475
Utilities for applying consistent styling to DOM elements and their children.
476
477
```typescript { .api }
478
/**
479
* Namespace for DOM node styling utilities
480
*/
481
namespace Styling {
482
/**
483
* Style a node and its child elements with default tag names
484
* @param node - Base DOM element to style
485
* @param className - Optional CSS class to add to styled nodes
486
*/
487
function styleNode(node: HTMLElement, className?: string): void;
488
489
/**
490
* Style a node and elements with a specific tag name
491
* @param node - Base DOM element to style
492
* @param tagName - HTML tag name to target for styling
493
* @param className - Optional CSS class to add to styled nodes
494
*/
495
function styleNodeByTag(node: HTMLElement, tagName: string, className?: string): void;
496
497
/**
498
* Wrap select elements with custom styling container
499
* @param select - Select element to wrap
500
* @param multiple - Whether select allows multiple selections
501
*/
502
function wrapSelect(select: HTMLSelectElement, multiple?: boolean): void;
503
}
504
```
505
506
**Usage Examples:**
507
508
```typescript
509
import { Styling } from '@jupyterlab/ui-components';
510
511
// Style all form elements in a container
512
function styleFormContainer(container: HTMLElement) {
513
// Apply default styling to common form elements
514
Styling.styleNode(container, 'custom-form-style');
515
516
// This will add 'jp-mod-styled' class to all:
517
// - select elements
518
// - textarea elements
519
// - input elements
520
// - button elements
521
}
522
523
// Style specific element types
524
function styleSpecificElements(container: HTMLElement) {
525
// Style only input elements
526
Styling.styleNodeByTag(container, 'input', 'styled-input');
527
528
// Style only buttons
529
Styling.styleNodeByTag(container, 'button', 'styled-button');
530
531
// Style textareas with special class
532
Styling.styleNodeByTag(container, 'textarea', 'styled-textarea');
533
}
534
535
// Widget that applies styling on render
536
class StyledFormWidget extends Widget {
537
onAfterAttach() {
538
super.onAfterAttach();
539
540
// Apply JupyterLab styling to all form elements
541
Styling.styleNode(this.node);
542
}
543
544
addFormField(fieldType: string, className?: string) {
545
const field = document.createElement(fieldType);
546
this.node.appendChild(field);
547
548
// Style the new field specifically
549
Styling.styleNodeByTag(this.node, fieldType, className);
550
}
551
}
552
553
// Custom select wrapper usage
554
function createStyledSelect(options: string[], multiple = false) {
555
const select = document.createElement('select');
556
select.multiple = multiple;
557
558
options.forEach(option => {
559
const optionElement = document.createElement('option');
560
optionElement.value = option;
561
optionElement.textContent = option;
562
select.appendChild(optionElement);
563
});
564
565
// Wrap with custom styling - handled automatically by styleNode
566
const container = document.createElement('div');
567
container.appendChild(select);
568
Styling.styleNode(container);
569
570
return container;
571
}
572
573
// Apply styling to dynamically created content
574
function createStyledDialog(content: HTMLElement) {
575
const dialog = document.createElement('div');
576
dialog.className = 'dialog-container';
577
dialog.appendChild(content);
578
579
// Ensure all form controls are properly styled
580
Styling.styleNode(dialog, 'dialog-form');
581
582
return dialog;
583
}
584
585
// Utility for styling imported HTML content
586
function styleImportedContent(htmlContent: string) {
587
const container = document.createElement('div');
588
container.innerHTML = htmlContent;
589
590
// Style all form elements in the imported content
591
Styling.styleNode(container);
592
593
return container;
594
}
595
```
596
597
### Type Definitions and Constants
598
599
Additional type definitions and constants used throughout the utilities.
600
601
```typescript { .api }
602
// SVG type definitions (from svg.d.ts)
603
declare module '*.svg' {
604
const value: string;
605
export default value;
606
}
607
608
// React render element type
609
type ReactRenderElement = React.ReactElement<any> | null;
610
611
// Common interfaces and types used across utilities
612
interface IChangedArgs<T, U, K extends string> {
613
name: K;
614
oldValue: T;
615
newValue: U;
616
}
617
```
618
619
**Usage Examples:**
620
621
```typescript
622
// Import SVG files as strings
623
import iconSvg from './my-icon.svg';
624
625
// Create LabIcon from imported SVG
626
const myIcon = new LabIcon({
627
name: 'my-app:my-icon',
628
svgstr: iconSvg
629
});
630
631
// Use changed args in signals
632
class CounterModel extends VDomModel {
633
private _count = 0;
634
635
get count(): number {
636
return this._count;
637
}
638
639
set count(value: number) {
640
const oldValue = this._count;
641
this._count = value;
642
643
// Emit change signal with typed args
644
this.stateChanged.emit({
645
name: 'count',
646
oldValue,
647
newValue: value
648
} as IChangedArgs<number, number, 'count'>);
649
}
650
}
651
652
// Utility functions that work with React elements
653
function isValidRenderElement(element: any): element is ReactRenderElement {
654
return element === null || React.isValidElement(element);
655
}
656
657
function renderIfValid(element: ReactRenderElement) {
658
if (isValidRenderElement(element)) {
659
return ReactDOM.render(element, container);
660
}
661
return null;
662
}
663
```