1
- import {
2
- createEffect ,
3
- createResource ,
4
- createRoot ,
5
- createSignal ,
6
- } from "solid-js" ;
1
+ import * as uuid from "@lukeed/uuid" ;
2
+ import * as solid from "solid-js" ;
7
3
8
4
/**
9
5
* @typedef AnyWidget
@@ -242,6 +238,50 @@ function throw_anywidget_error(source) {
242
238
throw source ;
243
239
}
244
240
241
+ /**
242
+ * @template T
243
+ * @param {import("@anywidget/types").AnyModel } model
244
+ * @param {string } name
245
+ * @param {any } [msg]
246
+ * @param {DataView[] } [buffers]
247
+ * @param {{ timeout?: number } } [options]
248
+ * @return {Promise<[T, DataView[]]> }
249
+ */
250
+ export function invoke (
251
+ model ,
252
+ name ,
253
+ msg ,
254
+ buffers = [ ] ,
255
+ { timeout = 3000 } = { } ,
256
+ ) {
257
+ // crypto.randomUUID() is not available in non-secure contexts (i.e., http://)
258
+ // so we use simple (non-secure) polyfill.
259
+ let id = uuid . v4 ( ) ;
260
+ return new Promise ( ( resolve , reject ) => {
261
+ let timer = setTimeout ( ( ) => {
262
+ reject ( new Error ( `Promise timed out after ${ timeout } ms` ) ) ;
263
+ model . off ( "msg:custom" , handler ) ;
264
+ } , timeout ) ;
265
+
266
+ /**
267
+ * @param {{ id: string, kind: "anywidget-command-response", response: T } } msg
268
+ * @param {DataView[] } buffers
269
+ */
270
+ function handler ( msg , buffers ) {
271
+ if ( ! ( msg . id === id ) ) return ;
272
+ clearTimeout ( timer ) ;
273
+ resolve ( [ msg . response , buffers ] ) ;
274
+ model . off ( "msg:custom" , handler ) ;
275
+ }
276
+ model . on ( "msg:custom" , handler ) ;
277
+ model . send (
278
+ { id, kind : "anywidget-command" , name, msg } ,
279
+ undefined ,
280
+ buffers ,
281
+ ) ;
282
+ } ) ;
283
+ }
284
+
245
285
class Runtime {
246
286
/** @type {() => void } */
247
287
#disposer = ( ) => { } ;
@@ -253,34 +293,38 @@ class Runtime {
253
293
254
294
/** @param {import("@jupyter-widgets/base").DOMWidgetModel } model */
255
295
constructor ( model ) {
256
- this . #disposer = createRoot ( ( dispose ) => {
257
- let [ css , set_css ] = createSignal ( model . get ( "_css" ) ) ;
296
+ this . #disposer = solid . createRoot ( ( dispose ) => {
297
+ let [ css , set_css ] = solid . createSignal ( model . get ( "_css" ) ) ;
258
298
model . on ( "change:_css" , ( ) => {
259
299
let id = model . get ( "_anywidget_id" ) ;
260
300
console . debug ( `[anywidget] css hot updated: ${ id } ` ) ;
261
301
set_css ( model . get ( "_css" ) ) ;
262
302
} ) ;
263
- createEffect ( ( ) => {
303
+ solid . createEffect ( ( ) => {
264
304
let id = model . get ( "_anywidget_id" ) ;
265
305
load_css ( css ( ) , id ) ;
266
306
} ) ;
267
307
268
308
/** @type {import("solid-js").Signal<string> } */
269
- let [ esm , setEsm ] = createSignal ( model . get ( "_esm" ) ) ;
309
+ let [ esm , setEsm ] = solid . createSignal ( model . get ( "_esm" ) ) ;
270
310
model . on ( "change:_esm" , async ( ) => {
271
311
let id = model . get ( "_anywidget_id" ) ;
272
312
console . debug ( `[anywidget] esm hot updated: ${ id } ` ) ;
273
313
setEsm ( model . get ( "_esm" ) ) ;
274
314
} ) ;
275
315
/** @type {void | (() => import("vitest").Awaitable<void>) } */
276
316
let cleanup ;
277
- this . #widget_result = createResource ( esm , async ( update ) => {
317
+ this . #widget_result = solid . createResource ( esm , async ( update ) => {
278
318
await safe_cleanup ( cleanup , "initialize" ) ;
279
319
try {
280
320
model . off ( null , null , INITIALIZE_MARKER ) ;
281
321
let widget = await load_widget ( update ) ;
282
322
cleanup = await widget . initialize ?. ( {
283
323
model : model_proxy ( model , INITIALIZE_MARKER ) ,
324
+ experimental : {
325
+ // @ts -expect-error - bind isn't working
326
+ invoke : invoke . bind ( null , model ) ,
327
+ } ,
284
328
} ) ;
285
329
return ok ( widget ) ;
286
330
} catch ( e ) {
@@ -302,11 +346,11 @@ class Runtime {
302
346
*/
303
347
async create_view ( view ) {
304
348
let model = view . model ;
305
- let disposer = createRoot ( ( dispose ) => {
349
+ let disposer = solid . createRoot ( ( dispose ) => {
306
350
/** @type {void | (() => import("vitest").Awaitable<void>) } */
307
351
let cleanup ;
308
352
let resource =
309
- createResource ( this . #widget_result, async ( widget_result ) => {
353
+ solid . createResource ( this . #widget_result, async ( widget_result ) => {
310
354
cleanup ?. ( ) ;
311
355
// Clear all previous event listeners from this hook.
312
356
model . off ( null , null , view ) ;
@@ -319,12 +363,16 @@ class Runtime {
319
363
cleanup = await widget . render ?. ( {
320
364
model : model_proxy ( model , view ) ,
321
365
el : view . el ,
366
+ experimental : {
367
+ // @ts -expect-error - bind isn't working
368
+ invoke : invoke . bind ( null , model ) ,
369
+ } ,
322
370
} ) ;
323
371
} catch ( e ) {
324
372
throw_anywidget_error ( e ) ;
325
373
}
326
374
} ) [ 0 ] ;
327
- createEffect ( ( ) => {
375
+ solid . createEffect ( ( ) => {
328
376
if ( resource . error ) {
329
377
// TODO: Show error in the view?
330
378
}
0 commit comments