|
5 | 5 | from __future__ import annotations
|
6 | 6 |
|
7 | 7 | import asyncio
|
8 |
| -from typing import Any, Callable, Iterator, List, Mapping, Optional, Sequence |
| 8 | +from typing import Any, Callable, List, Optional, Sequence |
9 | 9 |
|
10 | 10 | from anyio import create_task_group
|
11 | 11 |
|
12 |
| -from idom.core.proto import EventHandlerDict, EventHandlerFunc, EventHandlerType |
| 12 | +from idom.core.proto import EventHandlerFunc, EventHandlerType |
13 | 13 |
|
14 | 14 |
|
15 | 15 | def event(
|
@@ -47,7 +47,7 @@ def my_callback(*data):
|
47 | 47 |
|
48 | 48 | def setup(function: Callable[..., Any]) -> EventHandler:
|
49 | 49 | return EventHandler(
|
50 |
| - to_event_handler_function(function), |
| 50 | + to_event_handler_function(function, positional_args=True), |
51 | 51 | stop_propagation,
|
52 | 52 | prevent_default,
|
53 | 53 | )
|
@@ -84,152 +84,63 @@ def __init__(
|
84 | 84 | prevent_default: bool = False,
|
85 | 85 | target: Optional[str] = None,
|
86 | 86 | ) -> None:
|
87 |
| - self.function = function |
| 87 | + self.function = to_event_handler_function(function, positional_args=False) |
88 | 88 | self.prevent_default = prevent_default
|
89 | 89 | self.stop_propagation = stop_propagation
|
90 | 90 | self.target = target
|
91 | 91 |
|
| 92 | + def __eq__(self, other: Any) -> bool: |
| 93 | + for slot in self.__slots__: |
| 94 | + if not slot.startswith("_"): |
| 95 | + if not hasattr(other, slot): |
| 96 | + return False |
| 97 | + elif not getattr(other, slot) == getattr(self, slot): |
| 98 | + return False |
| 99 | + return True |
| 100 | + |
92 | 101 | def __repr__(self) -> str:
|
93 | 102 | public_names = [name for name in self.__slots__ if not name.startswith("_")]
|
94 | 103 | items = ", ".join([f"{n}={getattr(self, n)!r}" for n in public_names])
|
95 | 104 | return f"{type(self).__name__}({items})"
|
96 | 105 |
|
97 | 106 |
|
98 |
| -async def _no_op(data: List[Any]) -> None: |
99 |
| - return None |
100 |
| - |
101 |
| - |
102 |
| -class Events(Mapping[str, EventHandler]): |
103 |
| - """A container for event handlers. |
104 |
| -
|
105 |
| - Assign this object to the ``"eventHandlers"`` field of an element model. |
106 |
| - """ |
107 |
| - |
108 |
| - __slots__ = "_handlers" |
109 |
| - |
110 |
| - def __init__(self) -> None: |
111 |
| - self._handlers: EventHandlerDict = {} |
112 |
| - |
113 |
| - def on( |
114 |
| - self, |
115 |
| - event: str, |
116 |
| - stop_propagation: Optional[bool] = None, |
117 |
| - prevent_default: Optional[bool] = None, |
118 |
| - ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: |
119 |
| - """A decorator for adding an event handler. |
120 |
| -
|
121 |
| - Parameters: |
122 |
| - event: |
123 |
| - The camel-case name of the event, the word "on" is automatically |
124 |
| - prepended. So passing "keyDown" would refer to the event "onKeyDown". |
125 |
| - stop_propagation: |
126 |
| - Block the event from propagating further up the DOM. |
127 |
| - prevent_default: |
128 |
| - Stops the default action associate with the event from taking place. |
129 |
| -
|
130 |
| - Returns: |
131 |
| - A decorator which accepts an event handler function as its first argument. |
132 |
| - The parameters of the event handler function may indicate event attributes |
133 |
| - which should be sent back from the frontend. See :class:`EventHandler` for |
134 |
| - more info. |
135 |
| -
|
136 |
| - Examples: |
137 |
| - Simple "onClick" event handler: |
138 |
| -
|
139 |
| - .. code-block:: python |
140 |
| -
|
141 |
| - def clickable_element(): |
142 |
| - events = Events() |
143 |
| -
|
144 |
| - @events.on("click") |
145 |
| - def handler(event): |
146 |
| - # do something on a click event |
147 |
| - ... |
148 |
| -
|
149 |
| - return idom.vdom("button", "hello!", eventHandlers=events) |
150 |
| - """ |
151 |
| - if not event.startswith("on"): |
152 |
| - event = "on" + event[:1].upper() + event[1:] |
153 |
| - |
154 |
| - if event not in self._handlers: |
155 |
| - # do this so it's possible to stop event propagation or default behavior |
156 |
| - # without making the user have to pass a no op event handler themselves |
157 |
| - self._handlers[event] = EventHandler( |
158 |
| - _no_op, |
159 |
| - stop_propagation, |
160 |
| - prevent_default, |
161 |
| - ) |
162 |
| - |
163 |
| - def setup(function: Callable[..., Any]) -> Callable[..., Any]: |
164 |
| - old_handler = self._handlers[event] |
165 |
| - |
166 |
| - if old_handler.function is _no_op: |
167 |
| - return EventHandler( |
168 |
| - to_event_handler_function(function), |
169 |
| - bool(stop_propagation), |
170 |
| - bool(prevent_default), |
171 |
| - ) |
172 |
| - |
173 |
| - new_stop_propagation = ( |
174 |
| - old_handler.stop_propagation |
175 |
| - if stop_propagation is None |
176 |
| - else stop_propagation |
177 |
| - ) |
178 |
| - new_prevent_default = ( |
179 |
| - old_handler.prevent_default |
180 |
| - if prevent_default is None |
181 |
| - else prevent_default |
182 |
| - ) |
183 |
| - |
184 |
| - self._handlers[event] = merge_event_handlers( |
185 |
| - [ |
186 |
| - old_handler, |
187 |
| - EventHandler( |
188 |
| - to_event_handler_function(function), |
189 |
| - new_stop_propagation, |
190 |
| - new_prevent_default, |
191 |
| - ), |
192 |
| - ] |
193 |
| - ) |
194 |
| - |
195 |
| - return function |
196 |
| - |
197 |
| - return setup |
198 |
| - |
199 |
| - def __contains__(self, key: Any) -> bool: |
200 |
| - return key in self._handlers |
201 |
| - |
202 |
| - def __len__(self) -> int: |
203 |
| - return len(self._handlers) |
204 |
| - |
205 |
| - def __iter__(self) -> Iterator[str]: |
206 |
| - return iter(self._handlers) |
207 |
| - |
208 |
| - def __getitem__(self, key: str) -> EventHandler: |
209 |
| - return self._handlers[key] |
210 |
| - |
211 |
| - def __repr__(self) -> str: # pragma: no cover |
212 |
| - return repr(self._handlers) |
213 |
| - |
214 |
| - |
215 |
| -def to_event_handler_function(function: Callable[..., Any]) -> EventHandlerFunc: |
| 107 | +def to_event_handler_function( |
| 108 | + function: Callable[..., Any], |
| 109 | + positional_args: bool = True, |
| 110 | +) -> EventHandlerFunc: |
216 | 111 | """Make a :data:`~idom.core.proto.EventHandlerFunc` from a function or coroutine
|
217 | 112 |
|
218 | 113 | Parameters:
|
219 | 114 | function:
|
220 | 115 | A function or coroutine accepting a number of positional arguments.
|
| 116 | + positional_args: |
| 117 | + Whether to pass the event parameters a positional args or as a list. |
221 | 118 | """
|
222 |
| - if asyncio.iscoroutinefunction(function): |
223 |
| - return lambda data: function(*data) |
224 |
| - else: |
| 119 | + if positional_args: |
| 120 | + if asyncio.iscoroutinefunction(function): |
| 121 | + |
| 122 | + async def wrapper(data: List[Any]) -> None: |
| 123 | + await function(*data) |
| 124 | + |
| 125 | + else: |
| 126 | + |
| 127 | + async def wrapper(data: List[Any]) -> None: |
| 128 | + function(*data) |
| 129 | + |
| 130 | + return wrapper |
| 131 | + elif not asyncio.iscoroutinefunction(function): |
225 | 132 |
|
226 | 133 | async def wrapper(data: List[Any]) -> None:
|
227 |
| - return function(*data) |
| 134 | + function(data) |
228 | 135 |
|
229 | 136 | return wrapper
|
| 137 | + else: |
| 138 | + return function |
230 | 139 |
|
231 | 140 |
|
232 |
| -def merge_event_handlers(event_handlers: Sequence[EventHandlerType]) -> EventHandler: |
| 141 | +def merge_event_handlers( |
| 142 | + event_handlers: Sequence[EventHandlerType], |
| 143 | +) -> EventHandlerType: |
233 | 144 | """Merge multiple event handlers into one
|
234 | 145 |
|
235 | 146 | Raises a ValueError if any handlers have conflicting
|
|
0 commit comments