@@ -18,6 +18,7 @@ def event(
1818 * ,
1919 stop_propagation : bool = ...,
2020 prevent_default : bool = ...,
21+ debounce : int | None = ...,
2122) -> EventHandler : ...
2223
2324
@@ -27,6 +28,7 @@ def event(
2728 * ,
2829 stop_propagation : bool = ...,
2930 prevent_default : bool = ...,
31+ debounce : int | None = ...,
3032) -> Callable [[Callable [..., Any ]], EventHandler ]: ...
3133
3234
@@ -35,6 +37,7 @@ def event(
3537 * ,
3638 stop_propagation : bool = False ,
3739 prevent_default : bool = False ,
40+ debounce : int | None = None ,
3841) -> EventHandler | Callable [[Callable [..., Any ]], EventHandler ]:
3942 """A decorator for constructing an :class:`EventHandler`.
4043
@@ -63,13 +66,17 @@ def my_callback(*data): ...
6366 Block the event from propagating further up the DOM.
6467 prevent_default:
6568 Stops the default actional associate with the event from taking place.
69+ debounce:
70+ Preserve client-side user input state for the given number of milliseconds
71+ before applying conflicting server updates.
6672 """
6773
6874 def setup (function : Callable [..., Any ]) -> EventHandler :
6975 return EventHandler (
7076 to_event_handler_function (function , positional_args = True ),
7177 stop_propagation ,
7278 prevent_default ,
79+ debounce = debounce ,
7380 )
7481
7582 return setup (function ) if function is not None else setup
@@ -95,10 +102,12 @@ def __init__(
95102 stop_propagation : bool = False ,
96103 prevent_default : bool = False ,
97104 target : str | None = None ,
105+ debounce : int | None = None ,
98106 ) -> None :
99107 self .function = to_event_handler_function (function , positional_args = False )
100108 self .prevent_default = prevent_default
101109 self .stop_propagation = stop_propagation
110+ self .debounce = debounce
102111 self .target = target
103112
104113 # Check if our `preventDefault` or `stopPropagation` methods were called
@@ -110,14 +119,16 @@ def __init__(
110119 if isinstance (func_to_inspect , partial ):
111120 func_to_inspect = func_to_inspect .func
112121
113- found_prevent_default , found_stop_propagation = _inspect_event_handler_code (
114- func_to_inspect .__code__
122+ found_prevent_default , found_stop_propagation , found_debounce = (
123+ _inspect_event_handler_code ( func_to_inspect .__code__ )
115124 )
116125
117126 if found_prevent_default :
118127 self .prevent_default = True
119128 if found_stop_propagation :
120129 self .stop_propagation = True
130+ if found_debounce is not None :
131+ self .debounce = found_debounce
121132
122133 __hash__ = None # type: ignore
123134
@@ -130,6 +141,7 @@ def __eq__(self, other: object) -> bool:
130141 "function" ,
131142 "prevent_default" ,
132143 "stop_propagation" ,
144+ "debounce" ,
133145 "target" ,
134146 )
135147 )
@@ -184,8 +196,9 @@ def merge_event_handlers(
184196 """Merge multiple event handlers into one
185197
186198 Raises a ValueError if any handlers have conflicting
187- :attr:`~reactpy.core.proto.EventHandlerType.stop_propagation` or
188- :attr:`~reactpy.core.proto.EventHandlerType.prevent_default` attributes.
199+ :attr:`~reactpy.core.proto.EventHandlerType.stop_propagation`,
200+ :attr:`~reactpy.core.proto.EventHandlerType.prevent_default`, or
201+ :attr:`~reactpy.core.proto.EventHandlerType.debounce` attributes.
189202 """
190203 if not event_handlers :
191204 msg = "No event handlers to merge"
@@ -197,22 +210,28 @@ def merge_event_handlers(
197210
198211 stop_propagation = first_handler .stop_propagation
199212 prevent_default = first_handler .prevent_default
213+ debounce = first_handler .debounce
200214 target = first_handler .target
201215
202216 for handler in event_handlers :
203217 if (
204218 handler .stop_propagation != stop_propagation
205219 or handler .prevent_default != prevent_default
220+ or handler .debounce != debounce
206221 or handler .target != target
207222 ):
208- msg = "Cannot merge handlers - 'stop_propagation', 'prevent_default' or 'target' mismatch."
223+ msg = (
224+ "Cannot merge handlers - 'stop_propagation', 'prevent_default', "
225+ "'debounce' or 'target' mismatch."
226+ )
209227 raise ValueError (msg )
210228
211229 return EventHandler (
212230 merge_event_handler_funcs ([h .function for h in event_handlers ]),
213231 stop_propagation ,
214232 prevent_default ,
215233 target ,
234+ debounce ,
216235 )
217236
218237
@@ -235,43 +254,54 @@ async def await_all_event_handlers(data: Sequence[Any]) -> None:
235254
236255
237256@lru_cache (maxsize = 4096 )
238- def _inspect_event_handler_code (code : CodeType ) -> tuple [bool , bool ]:
257+ def _inspect_event_handler_code (code : CodeType ) -> tuple [bool , bool , int | None ]:
239258 prevent_default = False
240259 stop_propagation = False
260+ debounce = None
241261
242262 if code .co_argcount > 0 :
243263 names = code .co_names
244264 check_prevent_default = "preventDefault" in names
245265 check_stop_propagation = "stopPropagation" in names
266+ check_debounce = "debounce" in names
246267
247- if not (check_prevent_default or check_stop_propagation ):
248- return False , False
268+ if not (check_prevent_default or check_stop_propagation or check_debounce ):
269+ return False , False , None
249270
250271 event_arg_name = code .co_varnames [0 ]
251272 last_was_event = False
273+ instructions = list (dis .get_instructions (code ))
252274
253- for instr in dis . get_instructions ( code ):
275+ for index , instr in enumerate ( instructions ):
254276 if (
255277 instr .opname in ("LOAD_FAST" , "LOAD_FAST_BORROW" )
256278 and instr .argval == event_arg_name
257279 ):
258280 last_was_event = True
259281 continue
260282
261- if last_was_event and instr .opname in (
262- "LOAD_METHOD" ,
263- "LOAD_ATTR" ,
264- ):
265- if check_prevent_default and instr .argval == "preventDefault" :
266- prevent_default = True
267- check_prevent_default = False
268- elif check_stop_propagation and instr .argval == "stopPropagation" :
269- stop_propagation = True
270- check_stop_propagation = False
271-
272- if not (check_prevent_default or check_stop_propagation ):
283+ if last_was_event :
284+ if instr .opname in ("LOAD_METHOD" , "LOAD_ATTR" ):
285+ if check_prevent_default and instr .argval == "preventDefault" :
286+ prevent_default = True
287+ check_prevent_default = False
288+ elif check_stop_propagation and instr .argval == "stopPropagation" :
289+ stop_propagation = True
290+ check_stop_propagation = False
291+ elif check_debounce and instr .opname == "STORE_ATTR" :
292+ if instr .argval == "debounce" and index > 1 :
293+ candidate = instructions [index - 2 ].argval
294+ if isinstance (candidate , int ) and not isinstance (
295+ candidate , bool
296+ ):
297+ debounce = candidate
298+ check_debounce = False
299+
300+ if not (
301+ check_prevent_default or check_stop_propagation or check_debounce
302+ ):
273303 break
274304
275305 last_was_event = False
276306
277- return prevent_default , stop_propagation
307+ return prevent_default , stop_propagation , debounce
0 commit comments