or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

communication.mderror-handling.mdindex.mdlayout-style.mdnativeview.mdregistry.mdutilities.mdwidget-classes.mdwidget-manager.md
tile.json

registry.mddocs/

0

# Widget Registry

1

2

The widget registry system provides dynamic loading and registration of widget modules, enabling extensibility and plugin-like architecture for Jupyter widget ecosystems.

3

4

## Capabilities

5

6

### IJupyterWidgetRegistry Interface

7

8

Core interface for widget registries that support external widget module registration.

9

10

```typescript { .api }

11

/**

12

* Interface for Jupyter widget registries

13

*/

14

interface IJupyterWidgetRegistry {

15

/**

16

* Register a widget module with the registry

17

* @param data - Widget module registration data

18

*/

19

registerWidget(data: IWidgetRegistryData): void;

20

}

21

22

/**

23

* Lumino token for dependency injection of widget registries

24

*/

25

const IJupyterWidgetRegistry: Token<IJupyterWidgetRegistry>;

26

```

27

28

**Usage Examples:**

29

30

```typescript

31

// Get registry from dependency injection container

32

const registry = app.serviceManager.get(IJupyterWidgetRegistry);

33

34

// Register a widget module

35

registry.registerWidget({

36

name: '@my-org/custom-widgets',

37

version: '1.2.0',

38

exports: {

39

MySliderModel: MySliderModel,

40

MySliderView: MySliderView,

41

MyButtonModel: MyButtonModel,

42

MyButtonView: MyButtonView

43

}

44

});

45

```

46

47

### Widget Registration Data

48

49

Configuration structure for registering widget modules with their exports.

50

51

```typescript { .api }

52

/**

53

* Data structure for widget module registration

54

*/

55

interface IWidgetRegistryData {

56

/**

57

* Name of the widget module (typically npm package name)

58

*/

59

name: string;

60

61

/**

62

* Version of the widget module

63

*/

64

version: string;

65

66

/**

67

* Widget class exports from the module

68

*/

69

exports: ExportData;

70

}

71

72

/**

73

* Map of export names to widget classes

74

*/

75

type ExportMap = {

76

[key: string]: typeof WidgetModel | typeof WidgetView;

77

};

78

79

/**

80

* Flexible export data supporting synchronous and asynchronous loading

81

*/

82

type ExportData =

83

| ExportMap

84

| Promise<ExportMap>

85

| (() => ExportMap)

86

| (() => Promise<ExportMap>);

87

```

88

89

**Usage Examples:**

90

91

```typescript

92

// Synchronous registration with direct exports

93

const syncRegistration: IWidgetRegistryData = {

94

name: 'my-widgets',

95

version: '1.0.0',

96

exports: {

97

CounterModel: CounterModel,

98

CounterView: CounterView,

99

SliderModel: SliderModel,

100

SliderView: SliderView

101

}

102

};

103

104

// Asynchronous registration with promise

105

const asyncRegistration: IWidgetRegistryData = {

106

name: 'heavy-widgets',

107

version: '2.0.0',

108

exports: import('./heavy-widgets').then(module => ({

109

ChartModel: module.ChartModel,

110

ChartView: module.ChartView,

111

GraphModel: module.GraphModel,

112

GraphView: module.GraphView

113

}))

114

};

115

116

// Lazy loading with function

117

const lazyRegistration: IWidgetRegistryData = {

118

name: 'optional-widgets',

119

version: '1.5.0',

120

exports: () => {

121

// Load only when needed

122

const module = require('./optional-widgets');

123

return {

124

OptionalModel: module.OptionalModel,

125

OptionalView: module.OptionalView

126

};

127

}

128

};

129

130

// Async lazy loading

131

const asyncLazyRegistration: IWidgetRegistryData = {

132

name: 'remote-widgets',

133

version: '3.0.0',

134

exports: async () => {

135

// Dynamic import for code splitting

136

const module = await import('./remote-widgets');

137

return {

138

RemoteModel: module.RemoteModel,

139

RemoteView: module.RemoteView

140

};

141

}

142

};

143

```

144

145

## Registry Implementation Patterns

146

147

### Basic Registry Implementation

148

149

Example implementation of a widget registry:

150

151

```typescript

152

class WidgetRegistry implements IJupyterWidgetRegistry {

153

private _modules = new Map<string, IWidgetRegistryData>();

154

private _resolvedExports = new Map<string, ExportMap>();

155

156

registerWidget(data: IWidgetRegistryData): void {

157

const key = `${data.name}@${data.version}`;

158

this._modules.set(key, data);

159

160

console.log(`Registered widget module: ${key}`);

161

}

162

163

async getExports(name: string, version: string): Promise<ExportMap> {

164

const key = `${name}@${version}`;

165

166

// Check cache first

167

if (this._resolvedExports.has(key)) {

168

return this._resolvedExports.get(key)!;

169

}

170

171

const data = this._modules.get(key);

172

if (!data) {

173

throw new Error(`Widget module not found: ${key}`);

174

}

175

176

// Resolve exports based on type

177

let exports: ExportMap;

178

if (typeof data.exports === 'function') {

179

exports = await data.exports();

180

} else if (data.exports instanceof Promise) {

181

exports = await data.exports;

182

} else {

183

exports = data.exports;

184

}

185

186

// Cache resolved exports

187

this._resolvedExports.set(key, exports);

188

return exports;

189

}

190

191

async getWidgetClass(

192

name: string,

193

version: string,

194

className: string

195

): Promise<typeof WidgetModel | typeof WidgetView> {

196

const exports = await this.getExports(name, version);

197

const WidgetClass = exports[className];

198

199

if (!WidgetClass) {

200

throw new Error(`Widget class not found: ${className} in ${name}@${version}`);

201

}

202

203

return WidgetClass;

204

}

205

206

listRegisteredModules(): string[] {

207

return Array.from(this._modules.keys());

208

}

209

}

210

```

211

212

### Widget Manager Integration

213

214

Integration between registry and widget manager for dynamic loading:

215

216

```typescript

217

class ExtensibleWidgetManager implements IWidgetManager {

218

constructor(private registry: IJupyterWidgetRegistry) {}

219

220

async new_model(options: IModelOptions, state?: JSONObject): Promise<WidgetModel> {

221

try {

222

// Try to get model class from registry

223

const ModelClass = await this.registry.getWidgetClass(

224

options.model_module,

225

options.model_module_version,

226

options.model_name

227

) as typeof WidgetModel;

228

229

// Create model instance

230

const model = new ModelClass(state || {}, {

231

model_id: options.model_id || generateId(),

232

widget_manager: this,

233

comm: options.comm

234

});

235

236

this.register_model(model.model_id, Promise.resolve(model));

237

return model;

238

239

} catch (error) {

240

console.error('Failed to create model:', error);

241

throw error;

242

}

243

}

244

245

async create_view<VT extends WidgetView>(

246

model: WidgetModel,

247

options?: unknown

248

): Promise<VT> {

249

const viewName = model.get('_view_name');

250

const viewModule = model.get('_view_module');

251

const viewModuleVersion = model.get('_view_module_version');

252

253

if (!viewName || !viewModule) {

254

throw new Error('Model missing view information');

255

}

256

257

try {

258

// Get view class from registry

259

const ViewClass = await this.registry.getWidgetClass(

260

viewModule,

261

viewModuleVersion,

262

viewName

263

) as typeof WidgetView;

264

265

// Create view instance

266

const view = new ViewClass({

267

model: model,

268

options: options

269

});

270

271

return view as VT;

272

273

} catch (error) {

274

console.error('Failed to create view:', error);

275

throw error;

276

}

277

}

278

279

// ... other IWidgetManager methods

280

}

281

```

282

283

### Plugin-Style Registration

284

285

Pattern for plugin-style widget registration:

286

287

```typescript

288

// Plugin interface

289

interface IWidgetPlugin {

290

id: string;

291

activate(app: Application, registry: IJupyterWidgetRegistry): void;

292

deactivate?(): void;

293

}

294

295

// Example plugin

296

class ChartWidgetsPlugin implements IWidgetPlugin {

297

id = '@my-org/chart-widgets';

298

299

activate(app: Application, registry: IJupyterWidgetRegistry): void {

300

// Register widget classes

301

registry.registerWidget({

302

name: this.id,

303

version: '1.0.0',

304

exports: {

305

BarChartModel: BarChartModel,

306

BarChartView: BarChartView,

307

LineChartModel: LineChartModel,

308

LineChartView: LineChartView,

309

PieChartModel: PieChartModel,

310

PieChartView: PieChartView

311

}

312

});

313

314

console.log('Chart widgets plugin activated');

315

}

316

317

deactivate(): void {

318

console.log('Chart widgets plugin deactivated');

319

}

320

}

321

322

// Plugin management

323

class PluginManager {

324

private plugins = new Map<string, IWidgetPlugin>();

325

326

registerPlugin(plugin: IWidgetPlugin): void {

327

this.plugins.set(plugin.id, plugin);

328

}

329

330

activatePlugin(

331

pluginId: string,

332

app: Application,

333

registry: IJupyterWidgetRegistry

334

): void {

335

const plugin = this.plugins.get(pluginId);

336

if (plugin) {

337

plugin.activate(app, registry);

338

}

339

}

340

341

deactivatePlugin(pluginId: string): void {

342

const plugin = this.plugins.get(pluginId);

343

if (plugin && plugin.deactivate) {

344

plugin.deactivate();

345

}

346

}

347

}

348

349

// Usage

350

const pluginManager = new PluginManager();

351

const chartPlugin = new ChartWidgetsPlugin();

352

353

pluginManager.registerPlugin(chartPlugin);

354

pluginManager.activatePlugin(chartPlugin.id, app, registry);

355

```

356

357

## Advanced Registry Patterns

358

359

### Version Management

360

361

```typescript

362

class VersionedRegistry implements IJupyterWidgetRegistry {

363

private modules = new Map<string, Map<string, IWidgetRegistryData>>();

364

365

registerWidget(data: IWidgetRegistryData): void {

366

if (!this.modules.has(data.name)) {

367

this.modules.set(data.name, new Map());

368

}

369

370

this.modules.get(data.name)!.set(data.version, data);

371

}

372

373

findCompatibleVersion(name: string, versionRange: string): string | null {

374

const moduleVersions = this.modules.get(name);

375

if (!moduleVersions) return null;

376

377

// Simple version matching (in real implementation, use semver)

378

const availableVersions = Array.from(moduleVersions.keys());

379

380

// Find highest compatible version

381

return availableVersions

382

.filter(version => this.isCompatible(version, versionRange))

383

.sort(this.compareVersions)

384

.pop() || null;

385

}

386

387

private isCompatible(version: string, range: string): boolean {

388

// Simplified version compatibility check

389

// Real implementation would use semver library

390

return version.startsWith(range.replace('~', '').replace('^', ''));

391

}

392

393

private compareVersions(a: string, b: string): number {

394

// Simplified version comparison

395

return a.localeCompare(b, undefined, { numeric: true });

396

}

397

}

398

```

399

400

### Conditional Loading

401

402

```typescript

403

// Registry with conditional widget loading

404

class ConditionalRegistry implements IJupyterWidgetRegistry {

405

registerWidget(data: IWidgetRegistryData): void {

406

// Enhance registration data with conditions

407

const enhancedData = {

408

...data,

409

exports: this.wrapConditionalExports(data.exports)

410

};

411

412

this.doRegister(enhancedData);

413

}

414

415

private wrapConditionalExports(exports: ExportData): ExportData {

416

return async (): Promise<ExportMap> => {

417

// Check environment conditions

418

if (!this.checkEnvironment()) {

419

throw new Error('Widget not supported in current environment');

420

}

421

422

// Check feature detection

423

if (!this.checkFeatures()) {

424

throw new Error('Required features not available');

425

}

426

427

// Resolve actual exports

428

let actualExports: ExportMap;

429

if (typeof exports === 'function') {

430

actualExports = await exports();

431

} else if (exports instanceof Promise) {

432

actualExports = await exports;

433

} else {

434

actualExports = exports;

435

}

436

437

return actualExports;

438

};

439

}

440

441

private checkEnvironment(): boolean {

442

// Check browser capabilities, Jupyter environment, etc.

443

return typeof window !== 'undefined' &&

444

'requestAnimationFrame' in window;

445

}

446

447

private checkFeatures(): boolean {

448

// Check for required features

449

return 'WebGL' in window ||

450

'WebGL2RenderingContext' in window;

451

}

452

}

453

```

454

455

### Registry Events

456

457

```typescript

458

// Registry with event system

459

interface IRegistryEvents {

460

'widget-registered': (data: IWidgetRegistryData) => void;

461

'widget-loaded': (name: string, version: string, exports: ExportMap) => void;

462

'widget-error': (name: string, version: string, error: Error) => void;

463

}

464

465

class EventfulRegistry extends EventTarget implements IJupyterWidgetRegistry {

466

registerWidget(data: IWidgetRegistryData): void {

467

// Perform registration

468

this.doRegister(data);

469

470

// Emit event

471

this.dispatchEvent(new CustomEvent('widget-registered', {

472

detail: data

473

}));

474

}

475

476

async loadWidget(name: string, version: string): Promise<ExportMap> {

477

try {

478

const exports = await this.resolveExports(name, version);

479

480

this.dispatchEvent(new CustomEvent('widget-loaded', {

481

detail: { name, version, exports }

482

}));

483

484

return exports;

485

} catch (error) {

486

this.dispatchEvent(new CustomEvent('widget-error', {

487

detail: { name, version, error }

488

}));

489

throw error;

490

}

491

}

492

493

// Type-safe event listening

494

on<K extends keyof IRegistryEvents>(

495

type: K,

496

listener: IRegistryEvents[K]

497

): void {

498

this.addEventListener(type, listener as EventListener);

499

}

500

}

501

502

// Usage with events

503

const registry = new EventfulRegistry();

504

505

registry.on('widget-registered', (data) => {

506

console.log(`New widget module registered: ${data.name}@${data.version}`);

507

});

508

509

registry.on('widget-loaded', ({ name, version, exports }) => {

510

console.log(`Widget loaded: ${name}@${version}, exports:`, Object.keys(exports));

511

});

512

513

registry.on('widget-error', ({ name, version, error }) => {

514

console.error(`Failed to load widget ${name}@${version}:`, error);

515

});

516

```

517

518

## Registry Integration Examples

519

520

### JupyterLab Extension Pattern

521

522

```typescript

523

// JupyterLab extension using registry

524

const widgetExtension: JupyterFrontEndPlugin<void> = {

525

id: '@my-org/widget-extension',

526

autoStart: true,

527

requires: [IJupyterWidgetRegistry],

528

activate: (app: JupyterFrontEnd, registry: IJupyterWidgetRegistry) => {

529

// Register widgets when extension loads

530

registry.registerWidget({

531

name: '@my-org/widgets',

532

version: '1.0.0',

533

exports: async () => {

534

const module = await import('./widgets');

535

return {

536

MyModel: module.MyModel,

537

MyView: module.MyView

538

};

539

}

540

});

541

}

542

};

543

544

export default widgetExtension;

545

```

546

547

### Runtime Widget Discovery

548

549

```typescript

550

// Discover and register widgets at runtime

551

class RuntimeDiscovery {

552

constructor(private registry: IJupyterWidgetRegistry) {}

553

554

async discoverWidgets(searchPaths: string[]): Promise<void> {

555

for (const path of searchPaths) {

556

try {

557

const packageJson = await this.loadPackageJson(path);

558

559

if (packageJson.keywords?.includes('jupyter-widget')) {

560

await this.registerFromPackage(path, packageJson);

561

}

562

} catch (error) {

563

console.warn(`Failed to discover widgets in ${path}:`, error);

564

}

565

}

566

}

567

568

private async registerFromPackage(path: string, packageJson: any): Promise<void> {

569

this.registry.registerWidget({

570

name: packageJson.name,

571

version: packageJson.version,

572

exports: () => import(path)

573

});

574

}

575

}

576

```