From 0e43855459583fbb964d2e2bb0be3d852d582d89 Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 2 Nov 2026 16:44:26 -0600 Subject: [PATCH 1/2] Document event-accessor warm-up idiom in render_plotly/render_ipyleaflet; fix stale reactable module docstring --- shinywidgets/_reactable.py | 11 +++++++---- shinywidgets/_render_widget.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/shinywidgets/_reactable.py b/shinywidgets/_reactable.py index 01321dee..862da4f2 100644 --- a/shinywidgets/_reactable.py +++ b/shinywidgets/_reactable.py @@ -4,10 +4,13 @@ reactable-py's widget never calls `model.send()` itself, but its JavaScript bundle exports R reactable's state API (`getState`/`onStateChange`), keyed by the table's `elementId` prop (which `render_reactable` defaults to a unique -value when the user didn't set one). When an accessor attaches, the bridge in -`js/src/reactable.ts` subscribes to that API and forwards a pruned table state -(page, sort, filters, search, selection) as custom messages, so nothing is -subscribed and no traffic flows until an accessor is read. +value when the user didn't set one). Every accessor here is kind="state", so +they all attach at render: the bridge in `js/src/reactable.ts` binds once per +model, subscribing to that API and forwarding the pruned table state (page, +sort, filters, search, selection) as custom messages. The accessors share +that one subscription -- each emits only its `key` slice -- and binding is +idempotent on the JS side, so re-renders and several accessors don't stack +subscriptions. """ from __future__ import annotations diff --git a/shinywidgets/_render_widget.py b/shinywidgets/_render_widget.py index 644845da..500a034a 100644 --- a/shinywidgets/_render_widget.py +++ b/shinywidgets/_render_widget.py @@ -986,6 +986,17 @@ class render_ipyleaflet(_IpyleafletAccessors, render_widget_base[ValueT, Leaflet render function, which builds a new map each time it runs. For anything beyond them, use :func:`~shinywidgets.reactive_read` on ``map.widget``. + ``clicked()`` attaches to the map on its first read, so a click made + before that first read is lost. If the first reader runs late -- e.g. a + chat handler, or an effect gated on ``input.action_button()`` -- read it + once, unconditionally, at startup to attach it immediately: + + ```python + @reactive.effect + def _(): + map.clicked() # attach now, so early clicks aren't lost + ``` + Parameters ---------- width @@ -3452,6 +3463,17 @@ class render_plotly(render_widget_base[PlotlyValueT, FigureWidget]): a new plot each time it runs. To change the plot in response to a click, modify ``.widget`` from a ``@reactive.effect`` instead (see the example). + These accessors attach to the plot on their first read, so a gesture made + before that first read is lost. If the first reader runs late -- e.g. a + chat handler, or an effect gated on ``input.action_button()`` -- read the + accessor once, unconditionally, at startup to attach it immediately: + + ```python + @reactive.effect + def _(): + plot.selected() # attach now, so early selections aren't lost + ``` + **Updating in place**: re-rendering rebuilds the figure, resetting zoom and selection. To change a displayed figure instead, read inputs under `reactive.isolate()` in the render function and modify `.widget` in a From 008e9a3cfba1f714d3e768060e58afbca0759763 Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 2 Nov 2026 17:39:54 -0600 Subject: [PATCH 2/2] Scope the render_plotly warm-up note to still-lazy accessors selected()/brushed() became state accessors on the parent branch (they attach at render and seed the current selection), so the warm-up idiom now applies only to clicked()/selecting()/brushing()/relayout(). --- shinywidgets/_render_widget.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shinywidgets/_render_widget.py b/shinywidgets/_render_widget.py index 500a034a..dd4ebe7a 100644 --- a/shinywidgets/_render_widget.py +++ b/shinywidgets/_render_widget.py @@ -3463,15 +3463,17 @@ class render_plotly(render_widget_base[PlotlyValueT, FigureWidget]): a new plot each time it runs. To change the plot in response to a click, modify ``.widget`` from a ``@reactive.effect`` instead (see the example). - These accessors attach to the plot on their first read, so a gesture made - before that first read is lost. If the first reader runs late -- e.g. a - chat handler, or an effect gated on ``input.action_button()`` -- read the - accessor once, unconditionally, at startup to attach it immediately: + ``clicked()``, ``selecting()``, ``brushing()``, and ``relayout()`` attach + to the plot on their first read, so a gesture made before that first read + is lost (``selected()`` and ``brushed()`` attach at render, so they don't + need this). If the first reader runs late -- e.g. a chat handler, or an + effect gated on ``input.action_button()`` -- read the accessor once, + unconditionally, at startup to attach it immediately: ```python @reactive.effect def _(): - plot.selected() # attach now, so early selections aren't lost + plot.clicked() # attach now, so early clicks aren't lost ``` **Updating in place**: re-rendering rebuilds the figure, resetting zoom and