|
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