pythonnative 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonnative/__init__.py +53 -15
- pythonnative/cli/pn.py +150 -30
- pythonnative/components.py +217 -107
- pythonnative/element.py +14 -8
- pythonnative/hooks.py +334 -0
- pythonnative/hot_reload.py +143 -0
- pythonnative/native_modules/__init__.py +19 -0
- pythonnative/native_modules/camera.py +105 -0
- pythonnative/native_modules/file_system.py +131 -0
- pythonnative/native_modules/location.py +61 -0
- pythonnative/native_modules/notifications.py +151 -0
- pythonnative/native_views.py +638 -34
- pythonnative/page.py +138 -171
- pythonnative/reconciler.py +153 -20
- pythonnative/style.py +135 -0
- pythonnative/templates/android_template/app/build.gradle +2 -7
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt +2 -9
- pythonnative/templates/android_template/build.gradle +1 -1
- pythonnative/templates/ios_template/ios_template/ViewController.swift +7 -20
- {pythonnative-0.5.0.dist-info → pythonnative-0.7.0.dist-info}/METADATA +18 -38
- {pythonnative-0.5.0.dist-info → pythonnative-0.7.0.dist-info}/RECORD +25 -20
- pythonnative/collection_view.py +0 -0
- pythonnative/material_bottom_navigation_view.py +0 -0
- pythonnative/material_toolbar.py +0 -0
- {pythonnative-0.5.0.dist-info → pythonnative-0.7.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.5.0.dist-info → pythonnative-0.7.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.5.0.dist-info → pythonnative-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.5.0.dist-info → pythonnative-0.7.0.dist-info}/top_level.txt +0 -0
pythonnative/components.py
CHANGED
|
@@ -4,57 +4,42 @@ Each function returns an :class:`Element` describing a native UI widget.
|
|
|
4
4
|
These are pure data — no native views are created until the reconciler
|
|
5
5
|
mounts the element tree.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- ``Text`` (was *Label*)
|
|
10
|
-
- ``Button``
|
|
11
|
-
- ``Column`` / ``Row`` (was *StackView* vertical/horizontal)
|
|
12
|
-
- ``ScrollView``
|
|
13
|
-
- ``TextInput`` (was *TextField*)
|
|
14
|
-
- ``Image`` (was *ImageView*)
|
|
15
|
-
- ``Switch``
|
|
16
|
-
- ``ProgressBar`` (was *ProgressView*)
|
|
17
|
-
- ``ActivityIndicator`` (was *ActivityIndicatorView*)
|
|
18
|
-
- ``WebView``
|
|
19
|
-
- ``Spacer`` (new)
|
|
20
|
-
"""
|
|
7
|
+
All visual and layout properties are passed via the ``style`` parameter,
|
|
8
|
+
which accepts a dict or a list of dicts (later entries override earlier).
|
|
21
9
|
|
|
22
|
-
|
|
10
|
+
Layout properties supported by all components::
|
|
23
11
|
|
|
24
|
-
|
|
12
|
+
width, height, flex, margin, min_width, max_width, min_height,
|
|
13
|
+
max_height, align_self
|
|
25
14
|
|
|
15
|
+
Container-specific layout properties (Column / Row)::
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return {k: v for k, v in kwargs.items() if v is not None}
|
|
17
|
+
spacing, padding, align_items, justify_content
|
|
18
|
+
"""
|
|
30
19
|
|
|
20
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
from .element import Element
|
|
23
|
+
from .style import StyleValue, resolve_style
|
|
31
24
|
|
|
32
|
-
#
|
|
25
|
+
# ======================================================================
|
|
33
26
|
# Leaf components
|
|
34
|
-
#
|
|
27
|
+
# ======================================================================
|
|
35
28
|
|
|
36
29
|
|
|
37
30
|
def Text(
|
|
38
31
|
text: str = "",
|
|
39
32
|
*,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
""
|
|
49
|
-
props
|
|
50
|
-
text=text,
|
|
51
|
-
font_size=font_size,
|
|
52
|
-
color=color,
|
|
53
|
-
bold=bold or None,
|
|
54
|
-
text_align=text_align,
|
|
55
|
-
background_color=background_color,
|
|
56
|
-
max_lines=max_lines,
|
|
57
|
-
)
|
|
33
|
+
style: StyleValue = None,
|
|
34
|
+
key: Optional[str] = None,
|
|
35
|
+
) -> Element:
|
|
36
|
+
"""Display text.
|
|
37
|
+
|
|
38
|
+
Style properties: ``font_size``, ``color``, ``bold``, ``text_align``,
|
|
39
|
+
``background_color``, ``max_lines``, plus common layout props.
|
|
40
|
+
"""
|
|
41
|
+
props: Dict[str, Any] = {"text": text}
|
|
42
|
+
props.update(resolve_style(style))
|
|
58
43
|
return Element("Text", props, [], key=key)
|
|
59
44
|
|
|
60
45
|
|
|
@@ -62,24 +47,21 @@ def Button(
|
|
|
62
47
|
title: str = "",
|
|
63
48
|
*,
|
|
64
49
|
on_click: Optional[Callable[[], None]] = None,
|
|
65
|
-
color: Optional[str] = None,
|
|
66
|
-
background_color: Optional[str] = None,
|
|
67
|
-
font_size: Optional[float] = None,
|
|
68
50
|
enabled: bool = True,
|
|
51
|
+
style: StyleValue = None,
|
|
69
52
|
key: Optional[str] = None,
|
|
70
53
|
) -> Element:
|
|
71
|
-
"""Create a tappable button.
|
|
54
|
+
"""Create a tappable button.
|
|
55
|
+
|
|
56
|
+
Style properties: ``color``, ``background_color``, ``font_size``,
|
|
57
|
+
plus common layout props.
|
|
58
|
+
"""
|
|
72
59
|
props: Dict[str, Any] = {"title": title}
|
|
73
60
|
if on_click is not None:
|
|
74
61
|
props["on_click"] = on_click
|
|
75
|
-
if color is not None:
|
|
76
|
-
props["color"] = color
|
|
77
|
-
if background_color is not None:
|
|
78
|
-
props["background_color"] = background_color
|
|
79
|
-
if font_size is not None:
|
|
80
|
-
props["font_size"] = font_size
|
|
81
62
|
if not enabled:
|
|
82
63
|
props["enabled"] = False
|
|
64
|
+
props.update(resolve_style(style))
|
|
83
65
|
return Element("Button", props, [], key=key)
|
|
84
66
|
|
|
85
67
|
|
|
@@ -89,12 +71,14 @@ def TextInput(
|
|
|
89
71
|
placeholder: str = "",
|
|
90
72
|
on_change: Optional[Callable[[str], None]] = None,
|
|
91
73
|
secure: bool = False,
|
|
92
|
-
|
|
93
|
-
color: Optional[str] = None,
|
|
94
|
-
background_color: Optional[str] = None,
|
|
74
|
+
style: StyleValue = None,
|
|
95
75
|
key: Optional[str] = None,
|
|
96
76
|
) -> Element:
|
|
97
|
-
"""Create a single-line text entry field.
|
|
77
|
+
"""Create a single-line text entry field.
|
|
78
|
+
|
|
79
|
+
Style properties: ``font_size``, ``color``, ``background_color``,
|
|
80
|
+
plus common layout props.
|
|
81
|
+
"""
|
|
98
82
|
props: Dict[str, Any] = {"value": value}
|
|
99
83
|
if placeholder:
|
|
100
84
|
props["placeholder"] = placeholder
|
|
@@ -102,32 +86,27 @@ def TextInput(
|
|
|
102
86
|
props["on_change"] = on_change
|
|
103
87
|
if secure:
|
|
104
88
|
props["secure"] = True
|
|
105
|
-
|
|
106
|
-
props["font_size"] = font_size
|
|
107
|
-
if color is not None:
|
|
108
|
-
props["color"] = color
|
|
109
|
-
if background_color is not None:
|
|
110
|
-
props["background_color"] = background_color
|
|
89
|
+
props.update(resolve_style(style))
|
|
111
90
|
return Element("TextInput", props, [], key=key)
|
|
112
91
|
|
|
113
92
|
|
|
114
93
|
def Image(
|
|
115
94
|
source: str = "",
|
|
116
95
|
*,
|
|
117
|
-
width: Optional[float] = None,
|
|
118
|
-
height: Optional[float] = None,
|
|
119
96
|
scale_type: Optional[str] = None,
|
|
120
|
-
|
|
97
|
+
style: StyleValue = None,
|
|
121
98
|
key: Optional[str] = None,
|
|
122
99
|
) -> Element:
|
|
123
|
-
"""Display an image from a resource path or URL.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
100
|
+
"""Display an image from a resource path or URL.
|
|
101
|
+
|
|
102
|
+
Style properties: ``background_color``, plus common layout props.
|
|
103
|
+
"""
|
|
104
|
+
props: Dict[str, Any] = {}
|
|
105
|
+
if source:
|
|
106
|
+
props["source"] = source
|
|
107
|
+
if scale_type is not None:
|
|
108
|
+
props["scale_type"] = scale_type
|
|
109
|
+
props.update(resolve_style(style))
|
|
131
110
|
return Element("Image", props, [], key=key)
|
|
132
111
|
|
|
133
112
|
|
|
@@ -135,107 +114,238 @@ def Switch(
|
|
|
135
114
|
*,
|
|
136
115
|
value: bool = False,
|
|
137
116
|
on_change: Optional[Callable[[bool], None]] = None,
|
|
117
|
+
style: StyleValue = None,
|
|
138
118
|
key: Optional[str] = None,
|
|
139
119
|
) -> Element:
|
|
140
120
|
"""Create a toggle switch."""
|
|
141
121
|
props: Dict[str, Any] = {"value": value}
|
|
142
122
|
if on_change is not None:
|
|
143
123
|
props["on_change"] = on_change
|
|
124
|
+
props.update(resolve_style(style))
|
|
144
125
|
return Element("Switch", props, [], key=key)
|
|
145
126
|
|
|
146
127
|
|
|
147
128
|
def ProgressBar(
|
|
148
129
|
*,
|
|
149
130
|
value: float = 0.0,
|
|
150
|
-
|
|
131
|
+
style: StyleValue = None,
|
|
151
132
|
key: Optional[str] = None,
|
|
152
133
|
) -> Element:
|
|
153
134
|
"""Show determinate progress (0.0 – 1.0)."""
|
|
154
|
-
props =
|
|
135
|
+
props: Dict[str, Any] = {"value": value}
|
|
136
|
+
props.update(resolve_style(style))
|
|
155
137
|
return Element("ProgressBar", props, [], key=key)
|
|
156
138
|
|
|
157
139
|
|
|
158
140
|
def ActivityIndicator(
|
|
159
141
|
*,
|
|
160
142
|
animating: bool = True,
|
|
143
|
+
style: StyleValue = None,
|
|
161
144
|
key: Optional[str] = None,
|
|
162
145
|
) -> Element:
|
|
163
146
|
"""Show an indeterminate loading spinner."""
|
|
164
|
-
|
|
147
|
+
props: Dict[str, Any] = {"animating": animating}
|
|
148
|
+
props.update(resolve_style(style))
|
|
149
|
+
return Element("ActivityIndicator", props, [], key=key)
|
|
165
150
|
|
|
166
151
|
|
|
167
152
|
def WebView(
|
|
168
153
|
*,
|
|
169
154
|
url: str = "",
|
|
155
|
+
style: StyleValue = None,
|
|
170
156
|
key: Optional[str] = None,
|
|
171
157
|
) -> Element:
|
|
172
158
|
"""Embed web content."""
|
|
173
159
|
props: Dict[str, Any] = {}
|
|
174
160
|
if url:
|
|
175
161
|
props["url"] = url
|
|
162
|
+
props.update(resolve_style(style))
|
|
176
163
|
return Element("WebView", props, [], key=key)
|
|
177
164
|
|
|
178
165
|
|
|
179
166
|
def Spacer(
|
|
180
167
|
*,
|
|
181
168
|
size: Optional[float] = None,
|
|
169
|
+
flex: Optional[float] = None,
|
|
182
170
|
key: Optional[str] = None,
|
|
183
171
|
) -> Element:
|
|
184
|
-
"""Insert empty space with an optional fixed size."""
|
|
185
|
-
props =
|
|
172
|
+
"""Insert empty space with an optional fixed size or flex weight."""
|
|
173
|
+
props: Dict[str, Any] = {}
|
|
174
|
+
if size is not None:
|
|
175
|
+
props["size"] = size
|
|
176
|
+
if flex is not None:
|
|
177
|
+
props["flex"] = flex
|
|
186
178
|
return Element("Spacer", props, [], key=key)
|
|
187
179
|
|
|
188
180
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
181
|
+
def Slider(
|
|
182
|
+
*,
|
|
183
|
+
value: float = 0.0,
|
|
184
|
+
min_value: float = 0.0,
|
|
185
|
+
max_value: float = 1.0,
|
|
186
|
+
on_change: Optional[Callable[[float], None]] = None,
|
|
187
|
+
style: StyleValue = None,
|
|
188
|
+
key: Optional[str] = None,
|
|
189
|
+
) -> Element:
|
|
190
|
+
"""Continuous value slider."""
|
|
191
|
+
props: Dict[str, Any] = {
|
|
192
|
+
"value": value,
|
|
193
|
+
"min_value": min_value,
|
|
194
|
+
"max_value": max_value,
|
|
195
|
+
}
|
|
196
|
+
if on_change is not None:
|
|
197
|
+
props["on_change"] = on_change
|
|
198
|
+
props.update(resolve_style(style))
|
|
199
|
+
return Element("Slider", props, [], key=key)
|
|
192
200
|
|
|
193
|
-
|
|
201
|
+
|
|
202
|
+
# ======================================================================
|
|
203
|
+
# Container components
|
|
204
|
+
# ======================================================================
|
|
194
205
|
|
|
195
206
|
|
|
196
207
|
def Column(
|
|
197
208
|
*children: Element,
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
)
|
|
209
|
+
style: StyleValue = None,
|
|
210
|
+
key: Optional[str] = None,
|
|
211
|
+
) -> Element:
|
|
212
|
+
"""Arrange children vertically.
|
|
213
|
+
|
|
214
|
+
Style properties: ``spacing``, ``padding``, ``align_items``,
|
|
215
|
+
``justify_content``, ``background_color``, plus common layout props.
|
|
216
|
+
|
|
217
|
+
``align_items`` controls cross-axis (horizontal) alignment:
|
|
218
|
+
``"stretch"`` (default), ``"flex_start"``/``"leading"``,
|
|
219
|
+
``"center"``, ``"flex_end"``/``"trailing"``.
|
|
220
|
+
|
|
221
|
+
``justify_content`` controls main-axis (vertical) distribution:
|
|
222
|
+
``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
|
|
223
|
+
``"space_between"``, ``"space_around"``, ``"space_evenly"``.
|
|
224
|
+
"""
|
|
225
|
+
props: Dict[str, Any] = {}
|
|
226
|
+
props.update(resolve_style(style))
|
|
211
227
|
return Element("Column", props, list(children), key=key)
|
|
212
228
|
|
|
213
229
|
|
|
214
230
|
def Row(
|
|
215
231
|
*children: Element,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
)
|
|
232
|
+
style: StyleValue = None,
|
|
233
|
+
key: Optional[str] = None,
|
|
234
|
+
) -> Element:
|
|
235
|
+
"""Arrange children horizontally.
|
|
236
|
+
|
|
237
|
+
Style properties: ``spacing``, ``padding``, ``align_items``,
|
|
238
|
+
``justify_content``, ``background_color``, plus common layout props.
|
|
239
|
+
|
|
240
|
+
``align_items`` controls cross-axis (vertical) alignment:
|
|
241
|
+
``"stretch"`` (default), ``"flex_start"``/``"top"``,
|
|
242
|
+
``"center"``, ``"flex_end"``/``"bottom"``.
|
|
243
|
+
|
|
244
|
+
``justify_content`` controls main-axis (horizontal) distribution:
|
|
245
|
+
``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
|
|
246
|
+
``"space_between"``, ``"space_around"``, ``"space_evenly"``.
|
|
247
|
+
"""
|
|
248
|
+
props: Dict[str, Any] = {}
|
|
249
|
+
props.update(resolve_style(style))
|
|
229
250
|
return Element("Row", props, list(children), key=key)
|
|
230
251
|
|
|
231
252
|
|
|
232
253
|
def ScrollView(
|
|
233
254
|
child: Optional[Element] = None,
|
|
234
255
|
*,
|
|
235
|
-
|
|
256
|
+
style: StyleValue = None,
|
|
236
257
|
key: Optional[str] = None,
|
|
237
258
|
) -> Element:
|
|
238
259
|
"""Wrap a single child in a scrollable container."""
|
|
239
260
|
children = [child] if child is not None else []
|
|
240
|
-
props =
|
|
261
|
+
props: Dict[str, Any] = {}
|
|
262
|
+
props.update(resolve_style(style))
|
|
241
263
|
return Element("ScrollView", props, children, key=key)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def View(
|
|
267
|
+
*children: Element,
|
|
268
|
+
style: StyleValue = None,
|
|
269
|
+
key: Optional[str] = None,
|
|
270
|
+
) -> Element:
|
|
271
|
+
"""Generic container view (``UIView`` / ``android.view.View``)."""
|
|
272
|
+
props: Dict[str, Any] = {}
|
|
273
|
+
props.update(resolve_style(style))
|
|
274
|
+
return Element("View", props, list(children), key=key)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def SafeAreaView(
|
|
278
|
+
*children: Element,
|
|
279
|
+
style: StyleValue = None,
|
|
280
|
+
key: Optional[str] = None,
|
|
281
|
+
) -> Element:
|
|
282
|
+
"""Container that respects safe area insets (notch, status bar)."""
|
|
283
|
+
props: Dict[str, Any] = {}
|
|
284
|
+
props.update(resolve_style(style))
|
|
285
|
+
return Element("SafeAreaView", props, list(children), key=key)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def Modal(
|
|
289
|
+
*children: Element,
|
|
290
|
+
visible: bool = False,
|
|
291
|
+
on_dismiss: Optional[Callable[[], None]] = None,
|
|
292
|
+
title: Optional[str] = None,
|
|
293
|
+
style: StyleValue = None,
|
|
294
|
+
key: Optional[str] = None,
|
|
295
|
+
) -> Element:
|
|
296
|
+
"""Overlay modal dialog.
|
|
297
|
+
|
|
298
|
+
The modal is shown when ``visible=True`` and hidden when ``False``.
|
|
299
|
+
"""
|
|
300
|
+
props: Dict[str, Any] = {"visible": visible}
|
|
301
|
+
if on_dismiss is not None:
|
|
302
|
+
props["on_dismiss"] = on_dismiss
|
|
303
|
+
if title is not None:
|
|
304
|
+
props["title"] = title
|
|
305
|
+
props.update(resolve_style(style))
|
|
306
|
+
return Element("Modal", props, list(children), key=key)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def Pressable(
|
|
310
|
+
child: Optional[Element] = None,
|
|
311
|
+
*,
|
|
312
|
+
on_press: Optional[Callable[[], None]] = None,
|
|
313
|
+
on_long_press: Optional[Callable[[], None]] = None,
|
|
314
|
+
key: Optional[str] = None,
|
|
315
|
+
) -> Element:
|
|
316
|
+
"""Wrapper that adds press handling to any child element."""
|
|
317
|
+
props: Dict[str, Any] = {}
|
|
318
|
+
if on_press is not None:
|
|
319
|
+
props["on_press"] = on_press
|
|
320
|
+
if on_long_press is not None:
|
|
321
|
+
props["on_long_press"] = on_long_press
|
|
322
|
+
children = [child] if child is not None else []
|
|
323
|
+
return Element("Pressable", props, children, key=key)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def FlatList(
|
|
327
|
+
*,
|
|
328
|
+
data: Optional[List[Any]] = None,
|
|
329
|
+
render_item: Optional[Callable[[Any, int], Element]] = None,
|
|
330
|
+
key_extractor: Optional[Callable[[Any, int], str]] = None,
|
|
331
|
+
separator_height: float = 0,
|
|
332
|
+
style: StyleValue = None,
|
|
333
|
+
key: Optional[str] = None,
|
|
334
|
+
) -> Element:
|
|
335
|
+
"""Scrollable list that renders items from *data* using *render_item*.
|
|
336
|
+
|
|
337
|
+
Each item is rendered by calling ``render_item(item, index)``. If
|
|
338
|
+
``key_extractor`` is provided, it is called as ``key_extractor(item, index)``
|
|
339
|
+
to produce a stable key for each child element.
|
|
340
|
+
"""
|
|
341
|
+
items: List[Element] = []
|
|
342
|
+
for i, item in enumerate(data or []):
|
|
343
|
+
el = render_item(item, i) if render_item else Text(str(item))
|
|
344
|
+
if key_extractor is not None:
|
|
345
|
+
el = Element(el.type, el.props, el.children, key=key_extractor(item, i))
|
|
346
|
+
items.append(el)
|
|
347
|
+
|
|
348
|
+
inner = Column(*items, style={"spacing": separator_height} if separator_height else None)
|
|
349
|
+
sv_props: Dict[str, Any] = {}
|
|
350
|
+
sv_props.update(resolve_style(style))
|
|
351
|
+
return Element("ScrollView", sv_props, [inner], key=key)
|
pythonnative/element.py
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
"""Lightweight element descriptors for the virtual view tree.
|
|
2
2
|
|
|
3
3
|
An Element is an immutable description of a UI node — analogous to a React
|
|
4
|
-
element. It captures a type name
|
|
5
|
-
of children without creating any native
|
|
6
|
-
consumes these trees to determine what
|
|
7
|
-
updated, or removed.
|
|
4
|
+
element. It captures a type (name string **or** component function), a props
|
|
5
|
+
dictionary, and an ordered list of children without creating any native
|
|
6
|
+
platform objects. The reconciler consumes these trees to determine what
|
|
7
|
+
native views must be created, updated, or removed.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from typing import Any, Dict, List, Optional
|
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Element:
|
|
14
|
-
"""Immutable description of a single UI node.
|
|
14
|
+
"""Immutable description of a single UI node.
|
|
15
|
+
|
|
16
|
+
``type_name`` may be a *string* (e.g. ``"Text"``) for built-in native
|
|
17
|
+
elements or a *callable* for function components decorated with
|
|
18
|
+
:func:`~pythonnative.hooks.component`.
|
|
19
|
+
"""
|
|
15
20
|
|
|
16
21
|
__slots__ = ("type", "props", "children", "key")
|
|
17
22
|
|
|
18
23
|
def __init__(
|
|
19
24
|
self,
|
|
20
|
-
type_name: str,
|
|
25
|
+
type_name: Union[str, Any],
|
|
21
26
|
props: Dict[str, Any],
|
|
22
27
|
children: List["Element"],
|
|
23
28
|
key: Optional[str] = None,
|
|
@@ -28,7 +33,8 @@ class Element:
|
|
|
28
33
|
self.key = key
|
|
29
34
|
|
|
30
35
|
def __repr__(self) -> str:
|
|
31
|
-
|
|
36
|
+
t = self.type if isinstance(self.type, str) else getattr(self.type, "__name__", repr(self.type))
|
|
37
|
+
return f"Element({t!r}, props={set(self.props)}, children={len(self.children)})"
|
|
32
38
|
|
|
33
39
|
def __eq__(self, other: object) -> bool:
|
|
34
40
|
if not isinstance(other, Element):
|