Skip to content

Commit d6e80b5

Browse files
committed
fixes
1 parent 768fc45 commit d6e80b5

5 files changed

Lines changed: 115 additions & 32 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,11 @@ Limits of automatic cleanup:
107107

108108
## Jupyter Widgets
109109

110-
The kernel provides built-in support for [Jupyter Widgets](https://ipywidgets.readthedocs.io/) (`ipywidgets`-compatible). Widget classes are available as globals in the kernel runtime — just instantiate and call `.display()`:
110+
The kernel provides built-in support for [Jupyter Widgets](https://ipywidgets.readthedocs.io/) (`ipywidgets`-compatible). Widget classes are available under `Jupyter.widgets`; destructure the ones you need before using them:
111111

112112
```javascript
113+
const { IntSlider } = Jupyter.widgets;
114+
113115
const slider = new IntSlider({
114116
value: 50,
115117
min: 0,

examples/widgets.ipynb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
{
44
"cell_type": "markdown",
55
"metadata": {},
6-
"source": "# Jupyter Widgets\n\nThe JavaScript kernel provides built-in widget classes that mirror the\n[ipywidgets](https://ipywidgets.readthedocs.io/) API. Widget classes are\navailable as globals — just instantiate and configure.\n\nWidgets **auto-display** when they are the last expression in a cell.\nYou can also call the global `display()` function explicitly, e.g. when assigning to a variable.\n\n> **Requirements:** `jupyterlab-widgets` and `@jupyter-widgets/controls` must\n> be available in the JupyterLite deployment."
6+
"source": "# Jupyter Widgets\n\nThe JavaScript kernel provides built-in widget classes that mirror the\n[ipywidgets](https://ipywidgets.readthedocs.io/) API. Widget classes are\navailable under `Jupyter.widgets`.\n\nRun the setup cell below first to destructure the widget classes used in this\nnotebook.\n\nWidgets **auto-display** when they are the last expression in a cell.\nYou can also call the global `display()` function explicitly, e.g. when assigning to a variable.\n\n> **Requirements:** `jupyterlab-widgets` and `@jupyter-widgets/controls` must\n> be available in the JupyterLite deployment."
7+
},
8+
{
9+
"cell_type": "code",
10+
"execution_count": null,
11+
"metadata": {},
12+
"outputs": [],
13+
"source": "const {\n IntSlider,\n FloatSlider,\n SelectionSlider,\n IntProgress,\n IntText,\n FloatText,\n Text,\n Textarea,\n Password,\n Checkbox,\n ToggleButton,\n Valid,\n Dropdown,\n RadioButtons,\n Select,\n ToggleButtons,\n Button,\n HTML,\n Label,\n ColorPicker,\n VBox,\n HBox,\n Tab,\n Accordion,\n Stack\n} = Jupyter.widgets;"
714
},
815
{
916
"cell_type": "markdown",
@@ -359,4 +366,4 @@
359366
},
360367
"nbformat": 4,
361368
"nbformat_minor": 5
362-
}
369+
}

packages/javascript-kernel/src/comm.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@ export class CommManager {
7676
this._targets.set(targetName, handler);
7777
}
7878

79+
/**
80+
* Register a widget instance by comm ID.
81+
*/
82+
registerWidget<T>(commId: string, widget: T): void {
83+
this._widgets.set(commId, widget);
84+
}
85+
86+
/**
87+
* Look up a widget instance by comm ID.
88+
*/
89+
getWidget<T>(commId: string): T | undefined {
90+
return this._widgets.get(commId) as T | undefined;
91+
}
92+
93+
/**
94+
* Remove a widget registration.
95+
*/
96+
unregisterWidget(commId: string): void {
97+
this._widgets.delete(commId);
98+
}
99+
79100
/**
80101
* Handle a comm_open message from the frontend.
81102
*/
@@ -120,6 +141,7 @@ export class CommManager {
120141
const comm = this._comms.get(commId);
121142
if (comm) {
122143
comm.onClose?.(data, buffers);
144+
this._widgets.delete(commId);
123145
this._comms.delete(commId);
124146
}
125147
}
@@ -149,6 +171,7 @@ export class CommManager {
149171
* Dispose all comms and clear state.
150172
*/
151173
dispose(): void {
174+
this._widgets.clear();
152175
this._comms.clear();
153176
this._targets.clear();
154177
}
@@ -187,6 +210,8 @@ export class CommManager {
187210
data
188211
}
189212
});
213+
comm.onClose?.(data);
214+
this._widgets.delete(commId);
190215
this._comms.delete(commId);
191216
},
192217
display: (): void => {
@@ -201,4 +226,5 @@ export class CommManager {
201226
private _onOutput: RuntimeOutputHandler;
202227
private _comms = new Map<string, IComm>();
203228
private _targets = new Map<string, CommTargetHandler>();
229+
private _widgets = new Map<string, unknown>();
204230
}

packages/javascript-kernel/src/runtime_evaluator.ts

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { JavaScriptExecutor } from './executor';
77
import { normalizeError } from './errors';
88
import { CommManager } from './comm';
99
import type { RuntimeOutputHandler } from './runtime_protocol';
10-
import { Widget, widgetClasses } from './widgets';
10+
import { Widget, createWidgetClasses } from './widgets';
1111

1212
/**
1313
* Shared execution logic for iframe and worker runtime backends.
@@ -23,8 +23,8 @@ export class JavaScriptRuntimeEvaluator {
2323
options.executor ?? new JavaScriptExecutor(options.globalScope);
2424

2525
this._commManager = new CommManager(options.onOutput);
26-
this._setupJupyterGlobal();
2726
this._setupWidgets();
27+
this._setupJupyterGlobal();
2828
this._setupDisplay();
2929
this._setupConsoleOverrides();
3030
}
@@ -367,44 +367,33 @@ export class JavaScriptRuntimeEvaluator {
367367
}
368368

369369
/**
370-
* Install widget classes in the runtime global scope.
370+
* Create runtime-local widget classes.
371371
*/
372372
private _setupWidgets(): void {
373-
Widget.setDefaultManager(this._commManager);
374-
375-
this._previousWidgetGlobals = {};
376-
for (const [name, cls] of Object.entries(widgetClasses)) {
377-
this._previousWidgetGlobals[name] = this._globalScope[name];
378-
this._globalScope[name] = cls;
379-
}
373+
this._widgetClasses = createWidgetClasses(this._commManager);
380374
}
381375

382376
/**
383-
* Remove widget classes from the global scope and reset the manager.
377+
* Clear runtime-local widget classes.
384378
*/
385379
private _restoreWidgets(): void {
386-
Widget.setDefaultManager(null);
387-
388-
if (this._previousWidgetGlobals) {
389-
for (const [name, prev] of Object.entries(this._previousWidgetGlobals)) {
390-
if (prev === undefined) {
391-
delete this._globalScope[name];
392-
} else {
393-
this._globalScope[name] = prev;
394-
}
395-
}
396-
this._previousWidgetGlobals = null;
397-
}
380+
this._widgetClasses = null;
398381
}
399382

400383
/**
401384
* Install Jupyter.comm in runtime scope.
402385
*/
403386
private _setupJupyterGlobal(): void {
404387
this._previousJupyter = this._globalScope.Jupyter;
388+
const previousJupyter =
389+
typeof this._previousJupyter === 'object' &&
390+
this._previousJupyter !== null
391+
? this._previousJupyter
392+
: {};
405393
this._globalScope.Jupyter = {
394+
...previousJupyter,
406395
comm: this._commManager,
407-
widgets: widgetClasses
396+
widgets: this._widgetClasses ?? {}
408397
};
409398
}
410399

@@ -423,7 +412,7 @@ export class JavaScriptRuntimeEvaluator {
423412
private _onOutput: RuntimeOutputHandler;
424413
private _executor: JavaScriptExecutor;
425414
private _commManager: CommManager;
426-
private _previousWidgetGlobals: Record<string, any> | null = null;
415+
private _widgetClasses: Record<string, typeof Widget> | null = null;
427416
private _previousJupyter: any;
428417
private _previousDisplay: any;
429418
private _originalOnError: any;

packages/javascript-kernel/src/widgets.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,25 @@ export class Widget {
1919
static modelName = '';
2020
static viewName = '';
2121

22-
private static _defaultManager: CommManager | null = null;
22+
protected static _defaultManager: CommManager | null = null;
2323

2424
/**
2525
* Set the default CommManager used by all Widget instances.
2626
*/
2727
static setDefaultManager(manager: CommManager | null): void {
28-
Widget._defaultManager = manager;
28+
this._defaultManager = manager;
2929
}
3030

3131
constructor(state?: Record<string, unknown>) {
32-
const manager = Widget._defaultManager;
32+
const ctor = this.constructor as typeof Widget;
33+
const manager = ctor._defaultManager;
3334
if (!manager) {
3435
throw new Error(
3536
'Widget manager not initialized. Widgets can only be created inside the kernel runtime.'
3637
);
3738
}
3839

39-
const ctor = this.constructor as typeof Widget;
40+
this._manager = manager;
4041
this._state = {
4142
...this._defaults(),
4243
...state,
@@ -59,8 +60,10 @@ export class Widget {
5960
this._handleMsg(data, buffers);
6061
};
6162
this._comm.onClose = data => {
63+
this._manager.unregisterWidget(this.commId);
6264
this._trigger('close', data);
6365
};
66+
this._manager.registerWidget(this.commId, this);
6467
}
6568

6669
/**
@@ -199,6 +202,7 @@ export class Widget {
199202
}
200203

201204
protected _comm: IComm;
205+
protected _manager: CommManager;
202206
protected _state: Record<string, unknown>;
203207
private _listeners: Map<string, Set<WidgetEventCallback>>;
204208
}
@@ -880,6 +884,20 @@ export class Box extends Widget {
880884
set box_style(v: string) {
881885
this.set('box_style', v);
882886
}
887+
888+
protected override _handleMsg(
889+
data: Record<string, unknown>,
890+
buffers?: ArrayBuffer[]
891+
): void {
892+
super._handleMsg(data, buffers);
893+
894+
if (data.method === 'update' && data.state) {
895+
const state = data.state as Record<string, unknown>;
896+
if (Object.prototype.hasOwnProperty.call(state, 'children')) {
897+
this._children = _deserializeChildren(this._manager, state.children);
898+
}
899+
}
900+
}
883901
}
884902

885903
export class HBox extends Box {
@@ -1032,3 +1050,44 @@ export const widgetClasses: Record<string, typeof Widget> = {
10321050
Tab,
10331051
Stack
10341052
};
1053+
1054+
/**
1055+
* Create runtime-local widget classes bound to a specific comm manager.
1056+
*/
1057+
export function createWidgetClasses(
1058+
manager: CommManager
1059+
): Record<string, typeof Widget> {
1060+
const classes: Record<string, typeof Widget> = {};
1061+
1062+
for (const [name, cls] of Object.entries(widgetClasses)) {
1063+
const BoundWidgetClass = class extends cls {};
1064+
Object.defineProperty(BoundWidgetClass, 'name', { value: name });
1065+
BoundWidgetClass.setDefaultManager(manager);
1066+
classes[name] = BoundWidgetClass;
1067+
}
1068+
1069+
return classes;
1070+
}
1071+
1072+
/**
1073+
* Resolve serialized `IPY_MODEL_<id>` references back to known widget instances.
1074+
*/
1075+
function _deserializeChildren(
1076+
manager: CommManager,
1077+
children: unknown
1078+
): Widget[] {
1079+
if (!Array.isArray(children)) {
1080+
return [];
1081+
}
1082+
1083+
return children
1084+
.map(child => {
1085+
if (typeof child !== 'string' || !child.startsWith('IPY_MODEL_')) {
1086+
return null;
1087+
}
1088+
return (
1089+
manager.getWidget<Widget>(child.slice('IPY_MODEL_'.length)) ?? null
1090+
);
1091+
})
1092+
.filter((child): child is Widget => child instanceof Widget);
1093+
}

0 commit comments

Comments
 (0)