Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d482927
Update Widget List.ipynb
liamb27 Apr 1, 2024
3fec38a
Update package imports
May 26, 2024
2baf3d6
Fix/unify tooltips.
May 26, 2024
53adf75
DirectionalLinkModel - remove link to source and target on cleanup.
May 26, 2024
b9d043e
Add weakref with opt in automatic widget deletion using 'enable_weakr…
May 26, 2024
cf8ef63
Add Children for Box (and subclasses) to support validation of any ty…
May 26, 2024
54bd2c6
Update yarn.lock (ran jlpm dlx yarn-berry-deduplicate).
May 26, 2024
75da2f9
Run lint.
May 28, 2024
0c1dafe
Fix tests that need dummy comm & update yarn.lock.
Jun 9, 2024
077d950
Merge branch 'main' of https://github.com/jupyter-widgets/ipywidgets …
Aug 31, 2024
3e49c8e
Fix tests and lock file.
Aug 31, 2024
7fa9cba
Simplify tooltips to work everywhere
Sep 10, 2024
234752c
Remove unnecessary code.
Sep 11, 2024
9d5a482
Update the widget model specification for 'Children'.
Sep 12, 2024
8286c7c
Make _model_id a property
Oct 26, 2024
dec5524
Merge branch 'main' of https://github.com/jupyter-widgets/ipywidgets …
Nov 2, 2024
ca3d0cb
Change _instances from 'global' variable to class variable.
Nov 4, 2024
1833ca0
Merge branch 'jupyter-widgets:main' into weakref
fleming79 Oct 15, 2025
525e3ef
Per-kernel widget manager.
May 27, 2024
02b37f6
Add attachToRendermime function.
May 29, 2024
245c9b2
Update lock file.
May 29, 2024
bc5a447
WidgetModel: set comm_live to false when comm closed and remove redun…
May 31, 2024
d735e58
Improve ManagerBase_loadFromKernel
May 31, 2024
4f21fb7
Update jupyterlab_widgets package.json
May 31, 2024
55e89c3
Improved restoring widgets when opening and closing of notebooks.
May 31, 2024
cbf6c40
manager and render code refactoring.
Jun 1, 2024
390b25b
Avoid generating a warning "Failed to fetch ipywidgets through the "j…
Jun 1, 2024
2b83783
WidgetModel.close - catch any error when closing comm to ensure closi…
Jun 2, 2024
3b5f4d7
Improved manager logic and re-rendering when the kernel is re-connected.
Jun 2, 2024
23667a9
Added delays for getWidgetManager and get_model if the model isn't im…
Jun 2, 2024
09a1020
Manager, WidgetManager and plugin refactoring (simplification) . Warn…
Jun 3, 2024
fdf6994
Change packaging workflow to jupyterlab~4.0.
Jun 4, 2024
0b3340f
WidgetManager - do nothing if rendermime is the global rendermime ins…
Jun 9, 2024
dded2e3
Fix not awaiting delay promise in get_model.
Jun 9, 2024
4ef40e4
Update yarn.lock
Aug 31, 2024
0b1a7f1
Add kernel monitoring.
Sep 8, 2024
70c3125
Changed getWidgetManager to be a static method of KernelWidgetManage…
Sep 25, 2024
1040d27
Tweak restoration
Nov 2, 2024
1e8256a
Improved KernelWidgetManager creation and kernel restoration.
Nov 3, 2024
af38eca
Create new comm for existing models when restoring from kernel.
Nov 8, 2024
dfedfa6
Remove istanbul-instrumenter-loaderjl
Oct 15, 2025
1af0cff
Use jupyterlab/mathjax-extension for typesetting.
Oct 17, 2025
4aff0cd
Simplify tests (some were failing for an unknown reason when run toge…
Oct 17, 2025
4869104
Fix create_markdown accessing deprecated (removed _widget_types).
Oct 18, 2025
ec26a09
Merge remote-tracking branch 'origin/weakref' into combine_v1
Oct 18, 2025
f6313f6
Change mininum version of widgetsnbextension.
Oct 18, 2025
9647eea
Minimum python version for jupyterlab_widgets of 3.10.
Oct 18, 2025
7bd0bbd
Merge remote-tracking branch 'origin/OutputWidget-enhancements' into …
Oct 18, 2025
adfee9c
Change from using Widget._instances class variable to the global _wid…
Oct 19, 2025
b22a726
Merge branch 'weakref' into combine_v1
Oct 19, 2025
3541511
Change from using Widget._instances class variable to the global _wid…
Oct 19, 2025
12cafe1
Merge branch 'weakref' into combine_v1
Oct 19, 2025
1243f70
Improved resilience of Widget.get_state to omit close items in boxes …
Oct 21, 2025
8e05944
Merge branch 'weakref' into combine_v1
Oct 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu, windows]
python: ['3.9', '3.13']
python: ['3.10', '3.13']
dist: ['ipywidgets*.tar.gz']
include:
- python: '3.13'
Expand Down Expand Up @@ -134,7 +134,7 @@ jobs:
- name: Check the JupyterLab extension is installed
if: matrix.dist != 'widgetsnbextension*.tar.gz'
run: |
python -m pip install jupyterlab~=3.0
python -m pip install jupyterlab~=4.0

jupyter labextension list
jupyter labextension list 2>&1 | grep -ie "@jupyter-widgets/jupyterlab-manager.*enabled.*ok" -
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']

steps:
- uses: actions/checkout@v4
Expand All @@ -96,7 +96,7 @@ jobs:
pip install file://$PWD/python/ipywidgets#egg=ipywidgets[test]
- name: Test with pytest
run: |
pip install "pytest<8"
pip install "pytest"
cd python/ipywidgets
pytest --cov=ipywidgets ipywidgets

Expand Down
1 change: 1 addition & 0 deletions docs/source/dev_install.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ To update the widget model specification with changes, do something like this in
```
python ./packages/schema/generate-spec.py -f json-pretty packages/schema/jupyterwidgetmodels.latest.json
python ./packages/schema/generate-spec.py -f markdown packages/schema/jupyterwidgetmodels.latest.md
jlpm prettier
```

## Releasing new versions
Expand Down
2 changes: 1 addition & 1 deletion docs/source/dev_release.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ conda deactivate
conda remove --all -y -n releasewidgets
rm -rf ipywidgets

conda create -c conda-forge --override-channels -y -n releasewidgets notebook nodejs "yarn=3.*" twine jupyterlab=4 jupyter-packaging python-build jq "python==3.9.*"
conda create -c conda-forge --override-channels -y -n releasewidgets notebook nodejs "yarn=3.*" twine jupyterlab=4 jupyter-packaging python-build jq "python==3.10.*"
conda activate releasewidgets

git clone [email protected]:jupyter-widgets/ipywidgets.git
Expand Down
5 changes: 4 additions & 1 deletion examples/web1/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ module.exports = {
path: path.resolve(__dirname, 'built'),
},
module: {
rules: [{ test: /\.css$/i, use: ['style-loader', 'css-loader'] }],
rules: [
{ test: /\.css$/i, use: ['style-loader', 'css-loader'] },
{ test: /\.svg$/, type: 'asset/resource' },
],
},
};
1 change: 0 additions & 1 deletion packages/base-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
"chai": "^4.0.0",
"chai-as-promised": "^7.0.0",
"expect.js": "^0.3.1",
"istanbul-instrumenter-loader": "^3.0.1",
"karma": "^6.3.3",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.0.3",
Expand Down
38 changes: 29 additions & 9 deletions packages/base-manager/src/manager-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,14 @@ export abstract class ManagerBase implements IWidgetManager {
*
* If you would like to synchronously test if a model exists, use .has_model().
*/
async get_model(model_id: string): Promise<WidgetModel> {
async get_model(model_id: string, delays = [500]): Promise<WidgetModel> {
let i = 0;
while (!this._models[model_id] && i < delays.length) {
await new Promise((resolve) => setTimeout(resolve, delays[i++]));
}
const modelPromise = this._models[model_id];
if (modelPromise === undefined) {
throw new Error('widget model not found');
throw new Error(`widget model '${model_id}' not found`);
}
return modelPromise;
}
Expand Down Expand Up @@ -383,15 +387,21 @@ export abstract class ManagerBase implements IWidgetManager {
// Try fetching all widget states through the control comm
let data: any;
let buffers: any;
let timeoutID: number | undefined;
const comm_ids = await this._get_comm_info();
if (Object.keys(comm_ids).length === 0) {
// There is nothing to load, either a new kernel or no widgets in the kernel.
return Promise.resolve();
}
try {
const initComm = await this._create_comm(
CONTROL_COMM_TARGET,
uuid(),
{},
{ version: CONTROL_COMM_PROTOCOL_VERSION }
);

await new Promise((resolve, reject) => {
let succeeded = false;
initComm.on_msg((msg: any) => {
data = msg['content']['data'];

Expand All @@ -409,24 +419,31 @@ export abstract class ManagerBase implements IWidgetManager {
return new DataView(b instanceof ArrayBuffer ? b : b.buffer);
}
});

succeeded = true;
clearTimeout(timeoutID);
resolve(null);
});

initComm.on_close(() => reject('Control comm was closed too early'));
initComm.on_close(() => {
if (!succeeded) reject('Control comm was closed too early');
});

// Send a states request msg
initComm.send({ method: 'request_states' }, {});

// Reject if we didn't get a response in time
setTimeout(
timeoutID = window.setTimeout(
() => reject('Control comm did not respond in time'),
CONTROL_COMM_TIMEOUT
);
});

initComm.close();
} catch (error) {
console.warn(
'Failed to fetch ipywidgets through the "jupyter.widget.control" comm channel, fallback to fetching individual model state. Reason:',
error
);
clearTimeout(timeoutID);
// Fall back to the old implementation for old ipywidgets backend versions (ipywidgets<=7.6)
return this._loadFromKernelModels();
}
Expand Down Expand Up @@ -487,6 +504,9 @@ export abstract class ManagerBase implements IWidgetManager {
model.constructor as typeof WidgetModel
)._deserialize_state(state.state, this);
model!.set_state(deserializedState);
if (!model.comm_live) {
model.comm = await this._create_comm('jupyter.widget', widget_id);
}
}
} catch (error) {
// Failed to create a widget model, we continue creating other models so that
Expand Down Expand Up @@ -738,12 +758,12 @@ export abstract class ManagerBase implements IWidgetManager {

/**
* Disconnect the widget manager from the kernel, setting each model's comm
* as dead.
* as undefined.
*/
disconnect(): void {
Object.keys(this._models).forEach((i) => {
this._models[i].then((model) => {
model.comm_live = false;
model.comm = undefined;
});
});
}
Expand Down
1 change: 0 additions & 1 deletion packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
"chai": "^4.0.0",
"chai-as-promised": "^7.0.0",
"expect.js": "^0.3.1",
"istanbul-instrumenter-loader": "^3.0.1",
"karma": "^6.3.3",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.0.3",
Expand Down
4 changes: 3 additions & 1 deletion packages/base/src/errorwidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export function createErrorWidgetModel(
error: error,
};
super(attributes, options);
this.comm_live = true;
}
get comm_live(): boolean {
return true;
}
}
return ErrorWidget;
Expand Down
71 changes: 36 additions & 35 deletions packages/base/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class WidgetModel extends Backbone.Model {
// Attributes should be initialized here, since user initialization may depend on it
this.widget_manager = options.widget_manager;
this.model_id = options.model_id;
const comm = options.comm;
this.comm = options.comm;

this.views = Object.create(null);
this.state_change = Promise.resolve();
Expand All @@ -174,27 +174,23 @@ export class WidgetModel extends Backbone.Model {
// _buffered_state_diff must be created *after* the super.initialize
// call above. See the note in the set() method below.
this._buffered_state_diff = {};
}

if (comm) {
// Remember comm associated with the model.
this.comm = comm;
get comm() {
return this._comm;
}

// Hook comm messages up to model.
set comm(comm: IClassicComm | undefined) {
this._comm = comm;
if (comm) {
comm.on_close(this._handle_comm_closed.bind(this));
comm.on_msg(this._handle_comm_msg.bind(this));

this.comm_live = true;
} else {
this.comm_live = false;
}
this.trigger('comm_live_update');
}

get comm_live(): boolean {
return this._comm_live;
}
set comm_live(x) {
this._comm_live = x;
this.trigger('comm_live_update');
get comm_live() {
return Boolean(this.comm);
}

/**
Expand All @@ -218,39 +214,46 @@ export class WidgetModel extends Backbone.Model {
*
* @returns - a promise that is fulfilled when all the associated views have been removed.
*/
close(comm_closed = false): Promise<void> {
async close(comm_closed = false): Promise<void> {
// can only be closed once.
if (this._closed) {
return Promise.resolve();
return;
}
this._closed = true;
if (this.comm && !comm_closed) {
this.comm.close();
if (this._comm && !comm_closed) {
try {
this._comm.close();
} catch (err) {
// Do Nothing
}
}
this.stopListening();
this.trigger('destroy', this);
if (this.comm) {
delete this.comm;
}
delete this._comm;

// Delete all views of this model
if (this.views) {
const views = Object.keys(this.views).map((id: string) => {
return this.views![id].then((view) => view.remove());
});
delete this.views;
return Promise.all(views).then(() => {
return;
});
await Promise.all(views);
return;
}
return Promise.resolve();
}

/**
* Handle when a widget comm is closed.
*/
_handle_comm_closed(msg: KernelMessage.ICommCloseMsg): void {
if (!this.comm) {
return;
}
this.comm = undefined;
this.trigger('comm:close');
this.close(true);
if (!this._closed) {
this.close(true);
}
}

/**
Expand Down Expand Up @@ -635,7 +638,7 @@ export class WidgetModel extends Backbone.Model {
* This invokes a Backbone.Sync.
*/
save_changes(callbacks?: {}): void {
if (this.comm_live) {
if (this.comm) {
const options: any = { patch: true };
if (callbacks) {
options.callbacks = callbacks;
Expand Down Expand Up @@ -721,11 +724,9 @@ export class WidgetModel extends Backbone.Model {
model_id: string;
views?: { [key: string]: Promise<WidgetView> };
state_change: Promise<any>;
comm?: IClassicComm;
name: string;
module: string;

private _comm_live: boolean;
private _comm?: IClassicComm;
private _closed: boolean;
private _state_lock: any;
private _buffered_state_diff: any;
Expand Down Expand Up @@ -1101,11 +1102,11 @@ export class DOMWidgetView extends WidgetView {
}

updateTooltip(): void {
const title = this.model.get('tooltip');
if (!title) {
this.el.removeAttribute('title');
} else if (this.model.get('description').length === 0) {
const title = this.model.get('tooltip') ?? this.model.get('description');
if (title) {
this.el.setAttribute('title', title);
} else {
this.el.removeAttribute('title');
}
}

Expand Down
14 changes: 7 additions & 7 deletions packages/base/test/src/widget_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ describe('WidgetModel', function () {
});
});

it('sets the widget_manager, id, comm, and comm_live properties', function () {
const widgetDead = new WidgetModel({}, {
model_id: 'widgetDead',
it('sets the widget_manager, id, comm, properties', function () {
const widgetNoComm = new WidgetModel({}, {
model_id: 'noComm',
widget_manager: this.manager,
} as IBackboneModelOptions);
expect(widgetDead.model_id).to.equal('widgetDead');
expect(widgetDead.widget_manager).to.equal(this.manager);
expect(widgetDead.comm).to.be.undefined;
expect(widgetDead.comm_live).to.be.false;
expect(widgetNoComm.model_id).to.equal('noComm');
expect(widgetNoComm.widget_manager).to.equal(this.manager);
expect(widgetNoComm.comm).to.be.undefined;
expect(widgetNoComm.comm_live).to.be.false;

const comm = new MockComm();
const widgetLive = new WidgetModel({}, {
Expand Down
10 changes: 9 additions & 1 deletion packages/controls/css/widgets-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@
flex-direction: column;
}

.jupyter-widget-output {
box-sizing: border-box;
display: flex;
margin: 0;
overflow: auto;
flex-direction: column;
}

/* General Tags Styling */

.jupyter-widget-tagsinput {
Expand Down Expand Up @@ -907,7 +915,7 @@
flex: 1 1 var(--jp-widgets-inline-width-short);
outline: none !important;
overflow: auto;
height: inherit;
height: 100%;

/* Because Firefox defines the baseline of a select as the bottom of the
control, we align the entire control to the top and add padding to the
Expand Down
Loading