2626from ._cdn import SHINYWIDGETS_CDN_ONLY , SHINYWIDGETS_EXTENSION_WARNING
2727from ._comm import BufferType , ShinyComm , ShinyCommManager
2828from ._dependencies import require_dependency
29- from ._render_widget_base import WIDGET_INSTANCE_MAP
29+ from ._render_widget_base import has_current_context
3030from ._utils import is_instance_of_class , package_dir
3131
3232__all__ = (
@@ -79,12 +79,16 @@ def init_shiny_widget(w: Widget):
7979
8080 # By the time we get here, the user has already had an opportunity to specify a model_id,
8181 # so it isn't yet populated, generate a random one so we can assign the same id to the comm
82- if getattr (w , "_model_id" , None ) is None :
83- w ._model_id = uuid4 ().hex
82+ w ._model_id = getattr (w , "_model_id" , uuid4 ().hex )
83+
84+ if not isinstance (w ._model_id , str ):
85+ raise ValueError (
86+ f"Expected widget id to be a string, but got { type (w ._model_id )} "
87+ )
8488
8589 # Initialize the comm...this will also send the initial state of the widget
8690 w .comm = ShinyComm (
87- comm_id = w ._model_id , # pyright: ignore
91+ comm_id = w ._model_id ,
8892 comm_manager = COMM_MANAGER ,
8993 target_name = "jupyter.widgets" ,
9094 data = {"state" : state , "buffer_paths" : buffer_paths },
@@ -94,6 +98,14 @@ def init_shiny_widget(w: Widget):
9498 html_deps = session ._process_ui (TagList (widget_dep ))["deps" ],
9599 )
96100
101+ # If we're in a reactive context, close this widget when the context is invalidated
102+ if has_current_context ():
103+ ctx = get_current_context ()
104+ ctx .on_invalidate (lambda : w .close ())
105+
106+ # Remember to close the widget when the session ends
107+ session .on_ended (lambda : w .close ())
108+
97109 # Some widget's JS make external requests for static files (e.g.,
98110 # ipyleaflet markers) under this resource path. Note that this assumes that
99111 # we're setting the data-base-url attribute on the <body> (which we should
@@ -111,7 +123,7 @@ def init_shiny_widget(w: Widget):
111123 # everything after this point should be done once per session
112124 if session in SESSIONS :
113125 return
114- SESSIONS .add (session ) # type: ignore
126+ SESSIONS .add (session )
115127
116128 # Somewhere inside ipywidgets, it makes requests for static files
117129 # under the publicPath set by the webpack.config.js file.
@@ -133,21 +145,10 @@ def _():
133145 comm : ShinyComm = COMM_MANAGER .comms [comm_id ]
134146 comm .handle_msg (msg )
135147
136- # Handle a close message from the client.
137- @reactive .effect
138- @reactive .event (session .input .shinywidgets_comm_close )
139- def _ ():
140- comm_id = session .input .shinywidgets_comm_close ()
141- # Close the widget, which unregisters/deletes the comm, and also drops
142- # ipywidget's reference to the instance, allowing it to be garbage collected.
143- w_obj = WIDGET_INSTANCE_MAP .get (comm_id )
144- if w_obj :
145- w_obj .close ()
146-
147148 def _restore_state ():
148149 if old_comm_klass is not None :
149150 Widget .comm .klass = old_comm_klass # type: ignore
150- SESSIONS .remove (session ) # type: ignore
151+ SESSIONS .remove (session )
151152
152153 session .on_ended (_restore_state )
153154
@@ -156,7 +157,7 @@ def _restore_state():
156157Widget .on_widget_constructed (init_shiny_widget ) # type: ignore
157158
158159# Use WeakSet() over Set() so that the session can be garbage collected
159- SESSIONS = WeakSet () # type: ignore
160+ SESSIONS : WeakSet [ Session ] = WeakSet ()
160161COMM_MANAGER = ShinyCommManager ()
161162
162163
0 commit comments