diff --git a/panel/models/reactive_html.py b/panel/models/reactive_html.py
index 9406635f93..e48678838d 100644
--- a/panel/models/reactive_html.py
+++ b/panel/models/reactive_html.py
@@ -231,6 +231,8 @@ class ReactiveHTML(HTMLBox):
events = bp.Dict(bp.String, bp.Dict(bp.String, bp.Bool))
+ event_params = bp.List(bp.String)
+
html = bp.String()
looped = bp.List(bp.String)
diff --git a/panel/models/reactive_html.ts b/panel/models/reactive_html.ts
index c328f120ba..61b690f820 100644
--- a/panel/models/reactive_html.ts
+++ b/panel/models/reactive_html.ts
@@ -169,9 +169,13 @@ export class ReactiveHTMLView extends HTMLBoxView {
const property = data_model.properties[attr]
if (property == null)
continue
+ const is_event_param = this.model.event_params.includes(prop)
this.connect(property.change, () => {
- if (!this._changing)
+ if (!this._changing && !(is_event_param && !data_model[prop])) {
this.run_script(prop)
+ if (is_event_param)
+ data_model.setv({[prop]: false})
+ }
})
}
}
@@ -503,6 +507,7 @@ export namespace ReactiveHTML {
callbacks: p.Property
children: p.Property
data: p.Property
+ event_params: p.Property
events: p.Property
html: p.Property
looped: p.Property
@@ -529,6 +534,7 @@ export class ReactiveHTML extends HTMLBox {
callbacks: [ Any, {} ],
children: [ Any, {} ],
data: [ Any, ],
+ event_params: [ Array(String), [] ],
events: [ Any, {} ],
html: [ String, "" ],
looped: [ Array(String), [] ],
diff --git a/panel/reactive.py b/panel/reactive.py
index d71fe24eb5..173aaad8f9 100644
--- a/panel/reactive.py
+++ b/panel/reactive.py
@@ -1669,23 +1669,27 @@ def _init_params(self) -> Dict[str, Any]:
p : getattr(self, p) for p in list(Layoutable.param)
if getattr(self, p) is not None and p != 'name'
}
- data_params = {}
+ data_params, event_params = {}, []
for k, v in self.param.values().items():
+ pobj = self.param[k]
if (
(k in ignored and k != 'name') or
- ((self.param[k].precedence or 0) < 0) or
- (isinstance(v, Viewable) and not isinstance(self.param[k], param.ClassSelector))
+ ((pobj.precedence or 0) < 0) or
+ (isinstance(v, Viewable) and not isinstance(pobj, param.ClassSelector))
):
continue
if isinstance(v, str):
v = HTML_SANITIZER.clean(v)
data_params[k] = v
+ if isinstance(pobj, param.Event):
+ event_params.append(k)
html, nodes, self._attrs = self._get_template()
params.update({
'attrs': self._attrs,
'callbacks': self._node_callbacks,
'data': self._data_model(**self._process_param_change(data_params)),
'events': self._get_events(),
+ 'event_params': event_params,
'html': escape(textwrap.dedent(html)),
'nodes': nodes,
'looped': [node for node, _ in self._parser.looped],
@@ -1994,6 +1998,9 @@ def _update_model(
model_msg['children'] = children
self._set_on_model(model_msg, root, model)
self._set_on_model(data_msg, root, model.data)
+ reset = {p: False for p in data_msg if p in model.event_params}
+ if reset:
+ self._set_on_model(reset, root, model.data)
def on_event(self, node: str, event: str, callback: Callable) -> None:
"""
diff --git a/panel/tests/ui/test_reactive.py b/panel/tests/ui/test_reactive.py
index 6f1e6407c8..d64ad6906c 100644
--- a/panel/tests/ui/test_reactive.py
+++ b/panel/tests/ui/test_reactive.py
@@ -15,13 +15,16 @@ class ReactiveComponent(ReactiveHTML):
count = param.Integer(default=0)
+ event = param.Event()
+
_template = """
"""
_scripts = {
'render': 'data.count += 1; reactive.innerText = `${data.count}`;',
- 'click': 'data.count += 1; reactive.innerText = `${data.count}`;'
+ 'click': 'data.count += 1; reactive.innerText = `${data.count}`;',
+ 'event': 'data.count += 1; reactive.innerText = `${data.count}`;',
}
class ReactiveLiteral(ReactiveHTML):
@@ -46,6 +49,28 @@ def test_reactive_html_click_js_event(page):
wait_until(lambda: component.count == 2, page)
+def test_reactive_html_param_event(page):
+ component = ReactiveComponent()
+
+ serve_component(page, component)
+
+ expect(page.locator(".reactive")).to_have_text('1')
+
+ component.param.trigger('event')
+
+ expect(page.locator(".reactive")).to_have_text('2')
+
+ component.param.trigger('event')
+
+ expect(page.locator(".reactive")).to_have_text('3')
+
+ component.param.trigger('event')
+ component.param.trigger('event')
+
+ expect(page.locator(".reactive")).to_have_text('5')
+
+ wait_until(lambda: component.count == 5, page)
+
def test_reactive_html_set_loading_no_rerender(page):
component = ReactiveComponent()