@@ -180,43 +180,70 @@ Shiny.addCustomMessageHandler("shinywidgets_comm_open", (msg_txt) => {
180
180
181
181
// Handle any mutation of the model (e.g., add a marker to a map, without a full redraw)
182
182
// Basically out version of https://github.com/jupyterlab/jupyterlab/blob/d33de15/packages/services/src/kernel/default.ts#L1200-L1215
183
- Shiny . addCustomMessageHandler ( "shinywidgets_comm_msg" , ( msg_txt ) => {
183
+ Shiny . addCustomMessageHandler ( "shinywidgets_comm_msg" , async ( msg_txt ) => {
184
184
const msg = jsonParse ( msg_txt ) ;
185
185
const id = msg . content . comm_id ;
186
186
const model = manager . get_model ( id ) ;
187
187
if ( ! model ) {
188
188
console . error ( `Couldn't handle message for model ${ id } because it doesn't exist.` ) ;
189
189
return ;
190
190
}
191
- model
192
- . then ( m => {
193
- // @ts -ignore for some reason IClassicComm doesn't have this method, but we do
194
- m . comm . handle_msg ( msg ) ;
195
- } )
196
- . catch ( console . error ) ;
191
+ try {
192
+ const m = await model ;
193
+ // @ts -ignore for some reason IClassicComm doesn't have this method, but we do
194
+ m . comm . handle_msg ( msg ) ;
195
+ } catch ( err ) {
196
+ console . error ( "Error handling message:" , err ) ;
197
+ }
197
198
} ) ;
198
199
199
200
200
201
// Handle the closing of a widget/comm/model
201
- Shiny . addCustomMessageHandler ( "shinywidgets_comm_close" , ( msg_txt ) => {
202
+ Shiny . addCustomMessageHandler ( "shinywidgets_comm_close" , async ( msg_txt ) => {
202
203
const msg = jsonParse ( msg_txt ) ;
203
204
const id = msg . content . comm_id ;
204
205
const model = manager . get_model ( id ) ;
205
206
if ( ! model ) {
206
207
console . error ( `Couldn't close model ${ id } because it doesn't exist.` ) ;
207
208
return ;
208
209
}
209
- model
210
- . then ( m => {
211
- // Closing the model removes the corresponding view from the DOM
212
- m . close ( ) ;
213
- // .close() isn't enough to remove manager's reference to it,
214
- // and apparently the only way to remove it is through the `comm:close` event
215
- // https://github.com/jupyter-widgets/ipywidgets/blob/303cae4/packages/base-manager/src/manager-base.ts#L330-L337
216
- // https://github.com/jupyter-widgets/ipywidgets/blob/303cae4/packages/base/src/widget.ts#L251-L253
217
- m . trigger ( "comm:close" ) ;
218
- } )
219
- . catch ( console . error ) ;
210
+
211
+ try {
212
+ const m = await model ;
213
+
214
+ // Before .close()ing the model (which will .remove() each view), do some
215
+ // additional cleanup that .remove() might miss
216
+ await Promise . all (
217
+ Object . values ( m . views ) . map ( async ( viewPromise ) => {
218
+ try {
219
+ const v = await viewPromise ;
220
+
221
+ // Old versions of plotly need a .destroy() to properly clean up
222
+ // https://github.com/plotly/plotly.py/pull/3805/files#diff-259c92d
223
+ if ( hasMethod < DestroyMethod > ( v , 'destroy' ) ) {
224
+ v . destroy ( ) ;
225
+ // Also, empirically, when this destroy() is relevant, it also helps to
226
+ // delete the view's reference to the model, I think this is the only
227
+ // way to drop the resize event listener (see the diff in the link above)
228
+ // https://github.com/posit-dev/py-shinywidgets/issues/166
229
+ delete v . model ;
230
+ }
231
+
232
+
233
+ } catch ( err ) {
234
+ console . error ( "Error cleaning up view:" , err ) ;
235
+ }
236
+ } )
237
+ ) ;
238
+
239
+ // Close model after all views are cleaned up
240
+ await m . close ( ) ;
241
+
242
+ // Trigger comm:close event to remove manager's reference
243
+ m . trigger ( "comm:close" ) ;
244
+ } catch ( err ) {
245
+ console . error ( "Error during model cleanup:" , err ) ;
246
+ }
220
247
} ) ;
221
248
222
249
$ ( document ) . on ( "shiny:disconnected" , ( ) => {
@@ -230,3 +257,12 @@ function setBaseURL(x: string = '') {
230
257
document . querySelector ( 'body' ) . setAttribute ( 'data-base-url' , x ) ;
231
258
}
232
259
}
260
+
261
+ // TypeGuard to safely check if an object has a method
262
+ function hasMethod < T > ( obj : any , methodName : keyof T ) : obj is T {
263
+ return typeof obj [ methodName ] === 'function' ;
264
+ }
265
+
266
+ interface DestroyMethod {
267
+ destroy ( ) : void ;
268
+ }
0 commit comments