pythonnative 0.32.0__py3-none-any.whl → 0.34.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 +27 -15
- pythonnative/animated.py +7 -13
- pythonnative/cli/pn.py +1 -2
- pythonnative/component.py +255 -0
- pythonnative/components/__init__.py +106 -0
- pythonnative/components/_base.py +132 -0
- pythonnative/components/controls.py +515 -0
- pythonnative/components/layout.py +459 -0
- pythonnative/components/lists.py +809 -0
- pythonnative/components/media.py +204 -0
- pythonnative/components/overlays.py +107 -0
- pythonnative/components/pressable.py +252 -0
- pythonnative/components/structural.py +167 -0
- pythonnative/components/text.py +291 -0
- pythonnative/diagnostics.py +1 -1
- pythonnative/element.py +108 -29
- pythonnative/gestures.py +1 -1
- pythonnative/hooks.py +366 -671
- pythonnative/hosts/__init__.py +85 -0
- pythonnative/hosts/android.py +269 -0
- pythonnative/hosts/base.py +665 -0
- pythonnative/hosts/desktop.py +107 -0
- pythonnative/hosts/ios.py +404 -0
- pythonnative/hot_reload.py +16 -24
- pythonnative/layout.py +372 -66
- pythonnative/native_modules/__init__.py +23 -0
- pythonnative/native_modules/net_info.py +5 -1
- pythonnative/native_views/__init__.py +1 -1
- pythonnative/native_views/android.py +50 -6
- pythonnative/native_views/base.py +64 -1
- pythonnative/native_views/desktop.py +11 -3
- pythonnative/native_views/ios.py +72 -12
- pythonnative/navigation/__init__.py +102 -0
- pythonnative/navigation/container.py +154 -0
- pythonnative/navigation/handle.py +573 -0
- pythonnative/navigation/hooks.py +94 -0
- pythonnative/navigation/host.py +58 -0
- pythonnative/navigation/linking.py +200 -0
- pythonnative/navigation/navigators.py +637 -0
- pythonnative/navigation/screen.py +149 -0
- pythonnative/navigation/state.py +248 -0
- pythonnative/net.py +4 -0
- pythonnative/platform_metrics.py +1 -1
- pythonnative/preview.py +21 -19
- pythonnative/project/android.py +7 -1
- pythonnative/project/doctor.py +1 -1
- pythonnative/project/ios.py +1 -0
- pythonnative/project/runtime_assets.py +1 -1
- pythonnative/reconciler/__init__.py +29 -0
- pythonnative/reconciler/boundaries.py +365 -0
- pythonnative/reconciler/children.py +88 -0
- pythonnative/reconciler/core.py +1153 -0
- pythonnative/reconciler/layout_pass.py +359 -0
- pythonnative/reconciler/vnode.py +266 -0
- pythonnative/scheduler.py +159 -0
- pythonnative/sdk/_components.py +2 -4
- pythonnative/style.py +95 -13
- pythonnative/suspense.py +8 -12
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +85 -1
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +6 -7
- pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +1 -1
- pythonnative/templates/android_template/app/src/main/res/values/strings.xml +3 -1
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +33 -0
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +1 -1
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -10
- pythonnative/testing/__init__.py +53 -0
- pythonnative/testing/backend.py +276 -0
- pythonnative/testing/harness.py +391 -0
- {pythonnative-0.32.0.dist-info → pythonnative-0.34.0.dist-info}/METADATA +2 -21
- {pythonnative-0.32.0.dist-info → pythonnative-0.34.0.dist-info}/RECORD +74 -43
- pythonnative/components.py +0 -2856
- pythonnative/navigation.py +0 -1031
- pythonnative/reconciler.py +0 -2298
- pythonnative/screen.py +0 -2068
- {pythonnative-0.32.0.dist-info → pythonnative-0.34.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.32.0.dist-info → pythonnative-0.34.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.32.0.dist-info → pythonnative-0.34.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.32.0.dist-info → pythonnative-0.34.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Structural factories: ``Fragment``, ``ErrorBoundary``, and ``Suspense``.
|
|
2
|
+
|
|
3
|
+
These produce elements whose ``type`` is one of the reconciler-owned
|
|
4
|
+
singletons from [`pythonnative.element`][pythonnative.element]
|
|
5
|
+
(``FRAGMENT``, ``ERROR_BOUNDARY``, ``SUSPENSE``) rather than a native
|
|
6
|
+
view name, so they never create a platform view of their own.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Callable, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from ..element import ERROR_BOUNDARY, FRAGMENT, SUSPENSE, Element
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def Fragment(*children: Optional[Element], key: Optional[str] = None) -> Element:
|
|
15
|
+
"""Group children without adding a wrapping native view.
|
|
16
|
+
|
|
17
|
+
Like React's ``<></>``: groups elements without introducing an
|
|
18
|
+
extra container. Each child mounts as a direct sibling of the
|
|
19
|
+
Fragment's position in the parent's child list. Components may
|
|
20
|
+
also simply return a plain ``list`` of elements; ``Fragment``
|
|
21
|
+
exists for when the group needs a ``key`` (e.g. rendering a list
|
|
22
|
+
of pairs) or when a single expression reads better.
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
pn.Column(
|
|
26
|
+
pn.Text("Top"),
|
|
27
|
+
pn.Fragment(
|
|
28
|
+
pn.Text("Middle A"),
|
|
29
|
+
pn.Text("Middle B"),
|
|
30
|
+
),
|
|
31
|
+
pn.Text("Bottom"),
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
*children: Child elements to expose at the parent level.
|
|
37
|
+
``None`` and ``False`` children are dropped, which makes
|
|
38
|
+
conditional rendering with ``cond and pn.Text(...)``
|
|
39
|
+
ergonomic.
|
|
40
|
+
key: Stable identity for keyed reconciliation. A keyed
|
|
41
|
+
Fragment moves all of its children as one unit and
|
|
42
|
+
preserves their state across reorders.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
An [`Element`][pythonnative.Element] whose type is
|
|
46
|
+
[`FRAGMENT`][pythonnative.element.FRAGMENT].
|
|
47
|
+
"""
|
|
48
|
+
kept = [c for c in children if c is not None and c is not False]
|
|
49
|
+
return Element(FRAGMENT, {}, kept, key=key)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def ErrorBoundary(
|
|
53
|
+
*children: Element,
|
|
54
|
+
fallback: Optional[Any] = None,
|
|
55
|
+
on_error: Optional[Callable[[BaseException], Any]] = None,
|
|
56
|
+
key: Optional[str] = None,
|
|
57
|
+
) -> Element:
|
|
58
|
+
"""Catch render errors in the wrapped subtree and display ``fallback`` instead.
|
|
59
|
+
|
|
60
|
+
When any descendant raises during render (initial mount, a parent
|
|
61
|
+
re-render, or a local state-driven update), the failed subtree is
|
|
62
|
+
torn down and ``fallback`` is mounted in its place. Without a
|
|
63
|
+
boundary the error propagates to the screen host (which shows the
|
|
64
|
+
dev error overlay in dev mode).
|
|
65
|
+
|
|
66
|
+
``fallback`` may be:
|
|
67
|
+
|
|
68
|
+
- An [`Element`][pythonnative.Element], shown as-is.
|
|
69
|
+
- ``fallback(error) -> Element``.
|
|
70
|
+
- ``fallback(error, reset) -> Element``, where ``reset`` is a
|
|
71
|
+
zero-arg callable that clears the error and remounts the
|
|
72
|
+
original children (fresh state), for retry buttons.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
*children: Subtree to wrap.
|
|
76
|
+
fallback: Fallback content (see above). Required for the
|
|
77
|
+
boundary to actually catch; without it errors propagate
|
|
78
|
+
to the next boundary up.
|
|
79
|
+
on_error: Callback invoked with the exception when the
|
|
80
|
+
boundary catches, before the fallback mounts. Use it for
|
|
81
|
+
error reporting.
|
|
82
|
+
key: Stable identity for keyed reconciliation.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
An [`Element`][pythonnative.Element] whose type is
|
|
86
|
+
[`ERROR_BOUNDARY`][pythonnative.element.ERROR_BOUNDARY], with
|
|
87
|
+
``fallback`` and ``on_error`` in its props (omitted when
|
|
88
|
+
``None``).
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
```python
|
|
92
|
+
import pythonnative as pn
|
|
93
|
+
|
|
94
|
+
pn.ErrorBoundary(
|
|
95
|
+
MyRiskyComponent(),
|
|
96
|
+
fallback=lambda err, reset: pn.Column(
|
|
97
|
+
pn.Text(f"Error: {err}"),
|
|
98
|
+
pn.Button("Retry", on_press=reset),
|
|
99
|
+
),
|
|
100
|
+
on_error=lambda err: log.exception(err),
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
"""
|
|
104
|
+
props: Dict[str, Any] = {}
|
|
105
|
+
if fallback is not None:
|
|
106
|
+
props["fallback"] = fallback
|
|
107
|
+
if on_error is not None:
|
|
108
|
+
props["on_error"] = on_error
|
|
109
|
+
return Element(ERROR_BOUNDARY, props, list(children), key=key)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def Suspense(
|
|
113
|
+
*children: Element,
|
|
114
|
+
fallback: Optional[Any] = None,
|
|
115
|
+
key: Optional[str] = None,
|
|
116
|
+
) -> Element:
|
|
117
|
+
"""Show ``fallback`` while descendants wait on async work, then swap in the content.
|
|
118
|
+
|
|
119
|
+
A Suspense boundary catches **suspensions** from the subtree it
|
|
120
|
+
wraps: an ``async def`` component body blocking on a pending
|
|
121
|
+
await, or a regular component calling
|
|
122
|
+
[`Resource.read`][pythonnative.suspense.Resource.read] on data that hasn't
|
|
123
|
+
arrived (see [`use_resource`][pythonnative.use_resource] and
|
|
124
|
+
[`lazy`][pythonnative.lazy]). While anything is pending the
|
|
125
|
+
boundary renders ``fallback``; when the awaited work completes it
|
|
126
|
+
retries the content and swaps it in. Suspended components keep
|
|
127
|
+
their hook state across retries, so cached resources aren't
|
|
128
|
+
refetched.
|
|
129
|
+
|
|
130
|
+
Two timing behaviors, matching React:
|
|
131
|
+
|
|
132
|
+
- **Initial mount**: the fallback shows until the content is ready.
|
|
133
|
+
- **Updates**: a component that's already on screen and suspends
|
|
134
|
+
again (its dependencies changed) keeps its previous content
|
|
135
|
+
visible and re-renders when ready; there's no fallback flash.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
*children: Subtree to wrap (the async content).
|
|
139
|
+
fallback: Content shown while suspended: an
|
|
140
|
+
[`Element`][pythonnative.Element] or a zero-arg callable
|
|
141
|
+
returning one. Without it, suspensions propagate to the
|
|
142
|
+
next Suspense boundary up.
|
|
143
|
+
key: Stable identity for keyed reconciliation.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
An [`Element`][pythonnative.Element] whose type is
|
|
147
|
+
[`SUSPENSE`][pythonnative.element.SUSPENSE], with ``fallback``
|
|
148
|
+
in its props.
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
```python
|
|
152
|
+
import pythonnative as pn
|
|
153
|
+
|
|
154
|
+
@pn.component
|
|
155
|
+
async def Profile(user_id: str):
|
|
156
|
+
user = await api.fetch_user(user_id)
|
|
157
|
+
return pn.Text(user.name)
|
|
158
|
+
|
|
159
|
+
@pn.component
|
|
160
|
+
def Screen():
|
|
161
|
+
return pn.Suspense(
|
|
162
|
+
Profile(user_id="42"),
|
|
163
|
+
fallback=pn.ActivityIndicator(),
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
"""
|
|
167
|
+
return Element(SUSPENSE, {"fallback": fallback}, list(children), key=key)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Text-centric leaf factories: ``Text``, ``Button``, and ``TextInput``."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from ..element import Element
|
|
6
|
+
from ..hooks import Ref
|
|
7
|
+
from ..style import (
|
|
8
|
+
AccessibilityState,
|
|
9
|
+
AutoCapitalize,
|
|
10
|
+
Color,
|
|
11
|
+
KeyboardType,
|
|
12
|
+
ReturnKeyType,
|
|
13
|
+
StyleProp,
|
|
14
|
+
)
|
|
15
|
+
from ._base import _flatten_text_spans, _make_element
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def Text(
|
|
19
|
+
*parts: Any,
|
|
20
|
+
style: StyleProp = None,
|
|
21
|
+
accessibility_label: Optional[str] = None,
|
|
22
|
+
accessibility_hint: Optional[str] = None,
|
|
23
|
+
accessibility_role: Optional[str] = None,
|
|
24
|
+
accessible: Optional[bool] = None,
|
|
25
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
26
|
+
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
27
|
+
test_id: Optional[str] = None,
|
|
28
|
+
ref: Optional[Ref] = None,
|
|
29
|
+
key: Optional[str] = None,
|
|
30
|
+
) -> Element:
|
|
31
|
+
"""Display a string of text, optionally with styled nested spans.
|
|
32
|
+
|
|
33
|
+
Style properties: ``font_size``, ``color``, ``bold``,
|
|
34
|
+
``font_weight``, ``font_family``, ``italic``, ``text_align``,
|
|
35
|
+
``background_color``, ``max_lines``, ``letter_spacing``,
|
|
36
|
+
``line_height``, ``text_decoration`` (``"underline"`` /
|
|
37
|
+
``"line_through"``), ``border_radius``, ``border_width``,
|
|
38
|
+
``border_color``, ``shadow_*``, ``opacity``, ``transform``, plus
|
|
39
|
+
the common layout props.
|
|
40
|
+
|
|
41
|
+
**Rich text**: pass multiple parts, mixing plain strings and
|
|
42
|
+
nested ``Text`` elements, to render one paragraph with per-span
|
|
43
|
+
styling (a single ``TextView`` / ``UILabel`` natively, so line
|
|
44
|
+
wrapping flows across spans):
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
pn.Text(
|
|
48
|
+
"Hello, ",
|
|
49
|
+
pn.Text("world", style=pn.style(bold=True, color="#0A84FF")),
|
|
50
|
+
"!",
|
|
51
|
+
style=pn.style(font_size=18),
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Nested spans inherit the outer element's text styling and may
|
|
56
|
+
override ``color``, ``background_color``, ``font_size``,
|
|
57
|
+
``font_family``, ``font_weight``, ``bold``, ``italic``,
|
|
58
|
+
``text_decoration``, and ``letter_spacing``.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
*parts: Text content: a single string, or any mix of strings
|
|
62
|
+
and nested ``Text`` elements for rich text.
|
|
63
|
+
style: Style dict (or list of dicts).
|
|
64
|
+
accessibility_label: Spoken description for screen readers.
|
|
65
|
+
accessibility_hint: Spoken extra detail (iOS only).
|
|
66
|
+
accessibility_role: Semantic role for assistive tech.
|
|
67
|
+
accessible: Override whether the element is exposed to AT.
|
|
68
|
+
accessibility_state: Current widget state for assistive tech,
|
|
69
|
+
e.g. ``{"disabled": True, "selected": False}``. Recognized
|
|
70
|
+
keys: ``disabled``, ``selected``, ``checked``, ``busy``,
|
|
71
|
+
``expanded``.
|
|
72
|
+
accessibility_live_region: How AT announces dynamic changes to
|
|
73
|
+
this view: ``"none"``, ``"polite"``, or ``"assertive"``
|
|
74
|
+
(Android only).
|
|
75
|
+
test_id: Stable identifier for UI tests; exposed as
|
|
76
|
+
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
77
|
+
on iOS.
|
|
78
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
79
|
+
key: Stable identity for keyed reconciliation.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
An [`Element`][pythonnative.Element] of type ``"Text"``.
|
|
83
|
+
"""
|
|
84
|
+
rich = any(isinstance(p, Element) for p in parts) or len(parts) > 1
|
|
85
|
+
if rich:
|
|
86
|
+
spans = _flatten_text_spans(parts, {})
|
|
87
|
+
text = "".join(s["text"] for s in spans)
|
|
88
|
+
else:
|
|
89
|
+
spans = None
|
|
90
|
+
text = str(parts[0]) if parts else ""
|
|
91
|
+
return _make_element(
|
|
92
|
+
"Text",
|
|
93
|
+
style=style,
|
|
94
|
+
ref=ref,
|
|
95
|
+
key=key,
|
|
96
|
+
text=text,
|
|
97
|
+
spans=spans,
|
|
98
|
+
accessibility_label=accessibility_label,
|
|
99
|
+
accessibility_hint=accessibility_hint,
|
|
100
|
+
accessibility_role=accessibility_role,
|
|
101
|
+
accessible=accessible,
|
|
102
|
+
accessibility_state=accessibility_state,
|
|
103
|
+
accessibility_live_region=accessibility_live_region,
|
|
104
|
+
test_id=test_id,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def Button(
|
|
109
|
+
title: str = "",
|
|
110
|
+
*,
|
|
111
|
+
on_press: Optional[Callable[[], Any]] = None,
|
|
112
|
+
enabled: bool = True,
|
|
113
|
+
style: StyleProp = None,
|
|
114
|
+
accessibility_label: Optional[str] = None,
|
|
115
|
+
accessibility_hint: Optional[str] = None,
|
|
116
|
+
accessibility_role: Optional[str] = None,
|
|
117
|
+
accessible: Optional[bool] = None,
|
|
118
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
119
|
+
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
120
|
+
test_id: Optional[str] = None,
|
|
121
|
+
ref: Optional[Ref] = None,
|
|
122
|
+
key: Optional[str] = None,
|
|
123
|
+
) -> Element:
|
|
124
|
+
"""Display a tappable button.
|
|
125
|
+
|
|
126
|
+
Style properties: ``color``, ``background_color``, ``font_size``,
|
|
127
|
+
``border_radius``, ``border_width``, ``border_color``, ``shadow_*``,
|
|
128
|
+
``opacity``, ``transform``, plus the common layout props.
|
|
129
|
+
|
|
130
|
+
Buttons get ``accessibility_role="button"`` by default.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
title: Button label.
|
|
134
|
+
on_press: Callback invoked when the user taps the button.
|
|
135
|
+
enabled: When ``False``, the button is disabled and cannot be
|
|
136
|
+
tapped.
|
|
137
|
+
style: Style dict (or list of dicts).
|
|
138
|
+
accessibility_label: Spoken description for screen readers.
|
|
139
|
+
accessibility_hint: Spoken extra detail (iOS only).
|
|
140
|
+
accessibility_role: Override the default ``"button"`` role.
|
|
141
|
+
accessible: Override whether the element is exposed to AT.
|
|
142
|
+
accessibility_state: Current widget state for assistive tech,
|
|
143
|
+
e.g. ``{"disabled": True, "selected": False}``. Recognized
|
|
144
|
+
keys: ``disabled``, ``selected``, ``checked``, ``busy``,
|
|
145
|
+
``expanded``.
|
|
146
|
+
accessibility_live_region: How AT announces dynamic changes to
|
|
147
|
+
this view: ``"none"``, ``"polite"``, or ``"assertive"``
|
|
148
|
+
(Android only).
|
|
149
|
+
test_id: Stable identifier for UI tests; exposed as
|
|
150
|
+
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
151
|
+
on iOS.
|
|
152
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
153
|
+
key: Stable identity for keyed reconciliation.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
An [`Element`][pythonnative.Element] of type ``"Button"``.
|
|
157
|
+
"""
|
|
158
|
+
return _make_element(
|
|
159
|
+
"Button",
|
|
160
|
+
style=style,
|
|
161
|
+
ref=ref,
|
|
162
|
+
key=key,
|
|
163
|
+
title=title,
|
|
164
|
+
on_press=on_press,
|
|
165
|
+
enabled=enabled,
|
|
166
|
+
accessibility_label=accessibility_label,
|
|
167
|
+
accessibility_hint=accessibility_hint,
|
|
168
|
+
accessibility_role=accessibility_role,
|
|
169
|
+
accessible=accessible,
|
|
170
|
+
accessibility_state=accessibility_state,
|
|
171
|
+
accessibility_live_region=accessibility_live_region,
|
|
172
|
+
test_id=test_id,
|
|
173
|
+
_defaults={"accessibility_role": "button"},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def TextInput(
|
|
178
|
+
*,
|
|
179
|
+
value: str = "",
|
|
180
|
+
placeholder: Optional[str] = None,
|
|
181
|
+
on_change: Optional[Callable[[str], Any]] = None,
|
|
182
|
+
on_submit: Optional[Callable[[str], Any]] = None,
|
|
183
|
+
secure: bool = False,
|
|
184
|
+
multiline: bool = False,
|
|
185
|
+
keyboard_type: Optional[KeyboardType] = None,
|
|
186
|
+
auto_capitalize: Optional[AutoCapitalize] = None,
|
|
187
|
+
auto_correct: Optional[bool] = None,
|
|
188
|
+
auto_focus: bool = False,
|
|
189
|
+
return_key_type: Optional[ReturnKeyType] = None,
|
|
190
|
+
max_length: Optional[int] = None,
|
|
191
|
+
placeholder_color: Optional[Color] = None,
|
|
192
|
+
editable: bool = True,
|
|
193
|
+
clear_button: bool = False,
|
|
194
|
+
on_focus: Optional[Callable[[], Any]] = None,
|
|
195
|
+
on_blur: Optional[Callable[[], Any]] = None,
|
|
196
|
+
selection_color: Optional[Color] = None,
|
|
197
|
+
text_content_type: Optional[str] = None,
|
|
198
|
+
style: StyleProp = None,
|
|
199
|
+
accessibility_label: Optional[str] = None,
|
|
200
|
+
accessibility_hint: Optional[str] = None,
|
|
201
|
+
accessible: Optional[bool] = None,
|
|
202
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
203
|
+
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
204
|
+
test_id: Optional[str] = None,
|
|
205
|
+
ref: Optional[Ref] = None,
|
|
206
|
+
key: Optional[str] = None,
|
|
207
|
+
) -> Element:
|
|
208
|
+
"""Display a text-entry field (single-line by default, or ``multiline``).
|
|
209
|
+
|
|
210
|
+
Style properties: ``font_size``, ``color``, ``background_color``,
|
|
211
|
+
``border_*``, plus the common layout props.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
value: Current text content (controlled-input pattern).
|
|
215
|
+
placeholder: Hint shown when ``value`` is empty.
|
|
216
|
+
on_change: Callback invoked with the new string each keystroke.
|
|
217
|
+
on_submit: Callback invoked when the user submits (Return /
|
|
218
|
+
Done / etc.). Receives the final text.
|
|
219
|
+
secure: When ``True``, characters are masked (use for passwords).
|
|
220
|
+
multiline: When ``True``, allows multiple lines of input.
|
|
221
|
+
keyboard_type: One of ``"default"``, ``"email_address"``,
|
|
222
|
+
``"number_pad"``, ``"decimal_pad"``, ``"phone_pad"``, ``"url"``.
|
|
223
|
+
auto_capitalize: One of ``"none"``, ``"sentences"``, ``"words"``,
|
|
224
|
+
``"characters"``.
|
|
225
|
+
auto_correct: Enable/disable autocorrection.
|
|
226
|
+
auto_focus: Request focus on mount.
|
|
227
|
+
return_key_type: One of ``"default"``, ``"done"``, ``"go"``,
|
|
228
|
+
``"next"``, ``"send"``, ``"search"``.
|
|
229
|
+
max_length: Maximum number of characters allowed.
|
|
230
|
+
placeholder_color: Color used for the placeholder string.
|
|
231
|
+
editable: When ``False``, the field is read-only (still
|
|
232
|
+
selectable).
|
|
233
|
+
clear_button: When ``True``, shows a clear ("x") button while
|
|
234
|
+
editing (iOS ``clearButtonMode``; an inline button on
|
|
235
|
+
Android).
|
|
236
|
+
on_focus: Callback invoked when the field gains focus.
|
|
237
|
+
on_blur: Callback invoked when the field loses focus.
|
|
238
|
+
selection_color: Cursor / selection highlight color.
|
|
239
|
+
text_content_type: Semantic content hint for autofill (e.g.
|
|
240
|
+
``"username"``, ``"password"``, ``"one_time_code"``).
|
|
241
|
+
style: Style dict (or list of dicts).
|
|
242
|
+
accessibility_label: Spoken description for screen readers.
|
|
243
|
+
accessibility_hint: Spoken extra detail (iOS only).
|
|
244
|
+
accessible: Override whether the element is exposed to AT.
|
|
245
|
+
accessibility_state: Current widget state for assistive tech,
|
|
246
|
+
e.g. ``{"disabled": True, "selected": False}``. Recognized
|
|
247
|
+
keys: ``disabled``, ``selected``, ``checked``, ``busy``,
|
|
248
|
+
``expanded``.
|
|
249
|
+
accessibility_live_region: How AT announces dynamic changes to
|
|
250
|
+
this view: ``"none"``, ``"polite"``, or ``"assertive"``
|
|
251
|
+
(Android only).
|
|
252
|
+
test_id: Stable identifier for UI tests; exposed as
|
|
253
|
+
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
254
|
+
on iOS.
|
|
255
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
256
|
+
key: Stable identity for keyed reconciliation.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
An [`Element`][pythonnative.Element] of type ``"TextInput"``.
|
|
260
|
+
"""
|
|
261
|
+
return _make_element(
|
|
262
|
+
"TextInput",
|
|
263
|
+
style=style,
|
|
264
|
+
ref=ref,
|
|
265
|
+
key=key,
|
|
266
|
+
value=value,
|
|
267
|
+
placeholder=placeholder,
|
|
268
|
+
on_change=on_change,
|
|
269
|
+
on_submit=on_submit,
|
|
270
|
+
secure=secure or None,
|
|
271
|
+
multiline=multiline or None,
|
|
272
|
+
keyboard_type=keyboard_type,
|
|
273
|
+
auto_capitalize=auto_capitalize,
|
|
274
|
+
auto_correct=auto_correct,
|
|
275
|
+
auto_focus=auto_focus or None,
|
|
276
|
+
return_key_type=return_key_type,
|
|
277
|
+
max_length=max_length,
|
|
278
|
+
placeholder_color=placeholder_color,
|
|
279
|
+
editable=False if editable is False else None,
|
|
280
|
+
clear_button=clear_button or None,
|
|
281
|
+
on_focus=on_focus,
|
|
282
|
+
on_blur=on_blur,
|
|
283
|
+
selection_color=selection_color,
|
|
284
|
+
text_content_type=text_content_type,
|
|
285
|
+
accessibility_label=accessibility_label,
|
|
286
|
+
accessibility_hint=accessibility_hint,
|
|
287
|
+
accessible=accessible,
|
|
288
|
+
accessibility_state=accessibility_state,
|
|
289
|
+
accessibility_live_region=accessibility_live_region,
|
|
290
|
+
test_id=test_id,
|
|
291
|
+
)
|
pythonnative/diagnostics.py
CHANGED
|
@@ -14,7 +14,7 @@ Dev mode turns on:
|
|
|
14
14
|
immediately instead of cross-wiring state.
|
|
15
15
|
- **The RedBox**: uncaught errors from render, effects, and event
|
|
16
16
|
handlers are routed to the screen host, which presents a full-screen
|
|
17
|
-
error overlay (see `pythonnative.
|
|
17
|
+
error overlay (see `pythonnative.hosts`) instead of crashing or
|
|
18
18
|
swallowing the traceback.
|
|
19
19
|
|
|
20
20
|
In production none of this runs: validation is skipped, hook-order
|
pythonnative/element.py
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
"""Lightweight element descriptors for the virtual view tree.
|
|
2
2
|
|
|
3
3
|
An [`Element`][pythonnative.Element] is an immutable description of a UI
|
|
4
|
-
node, analogous to a React element. It captures a type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
node, analogous to a React element. It captures a type, a props dict,
|
|
5
|
+
and an ordered list of children without creating any native platform
|
|
6
|
+
objects. The reconciler consumes these trees to determine what native
|
|
7
|
+
views must be created, updated, or removed.
|
|
8
|
+
|
|
9
|
+
An element's ``type`` is one of three things:
|
|
10
|
+
|
|
11
|
+
- a ``str`` naming a **native view** (``"Text"``, ``"View"``, ...),
|
|
12
|
+
- a [`Component`][pythonnative.Component] produced by
|
|
13
|
+
[`@component`][pythonnative.component.component], or
|
|
14
|
+
- a **structural type**: one of the singletons defined here
|
|
15
|
+
([`FRAGMENT`][pythonnative.element.FRAGMENT],
|
|
16
|
+
[`ERROR_BOUNDARY`][pythonnative.element.ERROR_BOUNDARY],
|
|
17
|
+
[`SUSPENSE`][pythonnative.element.SUSPENSE]) or a
|
|
18
|
+
[`Context`][pythonnative.Context] (whose elements are providers).
|
|
19
|
+
|
|
20
|
+
Structural types are real objects rather than magic strings so the
|
|
21
|
+
reconciler can dispatch on them with identity checks and no user
|
|
22
|
+
element can collide with them.
|
|
9
23
|
|
|
10
24
|
Elements are produced by built-in factories such as
|
|
11
25
|
[`Text`][pythonnative.Text], [`Button`][pythonnative.Button], and
|
|
12
|
-
[`Column`][pythonnative.Column], or by calling
|
|
13
|
-
[`component`][pythonnative.component].
|
|
26
|
+
[`Column`][pythonnative.Column], or by calling components.
|
|
14
27
|
|
|
15
28
|
Example:
|
|
16
29
|
```python
|
|
@@ -20,48 +33,86 @@ Example:
|
|
|
20
33
|
```
|
|
21
34
|
"""
|
|
22
35
|
|
|
23
|
-
from
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from typing import Any, Dict, Iterable, List, Optional, Union
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"ERROR_BOUNDARY",
|
|
42
|
+
"FRAGMENT",
|
|
43
|
+
"SUSPENSE",
|
|
44
|
+
"Element",
|
|
45
|
+
"Node",
|
|
46
|
+
"StructuralType",
|
|
47
|
+
"type_label",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class StructuralType:
|
|
52
|
+
"""Identity object naming a reconciler-owned element kind.
|
|
53
|
+
|
|
54
|
+
Instances are singletons compared by identity; ``repr`` shows the
|
|
55
|
+
kind for debugging (``<Fragment>``).
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
__slots__ = ("name",)
|
|
59
|
+
|
|
60
|
+
def __init__(self, name: str) -> None:
|
|
61
|
+
self.name = name
|
|
62
|
+
|
|
63
|
+
def __repr__(self) -> str:
|
|
64
|
+
return f"<{self.name}>"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
FRAGMENT = StructuralType("Fragment")
|
|
68
|
+
"""Type of [`Fragment`][pythonnative.Fragment] elements: a transparent group."""
|
|
69
|
+
|
|
70
|
+
ERROR_BOUNDARY = StructuralType("ErrorBoundary")
|
|
71
|
+
"""Type of [`ErrorBoundary`][pythonnative.ErrorBoundary] elements."""
|
|
72
|
+
|
|
73
|
+
SUSPENSE = StructuralType("Suspense")
|
|
74
|
+
"""Type of [`Suspense`][pythonnative.Suspense] elements."""
|
|
24
75
|
|
|
25
76
|
|
|
26
77
|
class Element:
|
|
27
78
|
"""Immutable description of a single UI node.
|
|
28
79
|
|
|
29
|
-
Built-in elements use a string
|
|
30
|
-
|
|
31
|
-
`
|
|
32
|
-
|
|
80
|
+
Built-in elements use a string ``type`` (``"Text"``, ``"Button"``,
|
|
81
|
+
``"Column"``, etc.); components use the
|
|
82
|
+
[`Component`][pythonnative.Component] object itself as ``type``;
|
|
83
|
+
structural elements use a [`StructuralType`][pythonnative.element.StructuralType]
|
|
84
|
+
or a [`Context`][pythonnative.Context]. The reconciler dispatches on
|
|
85
|
+
this distinction when mounting the tree.
|
|
33
86
|
|
|
34
87
|
Attributes:
|
|
35
|
-
type:
|
|
36
|
-
|
|
37
|
-
props: Dict of properties passed to the native handler or
|
|
88
|
+
type: The element kind (see the module docstring).
|
|
89
|
+
props: Dict of properties passed to the native handler or the
|
|
38
90
|
component function.
|
|
39
|
-
children: Ordered list of child
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
91
|
+
children: Ordered list of child nodes. ``None`` and ``False``
|
|
92
|
+
entries are permitted and dropped during reconciliation, so
|
|
93
|
+
conditional children (``cond and Text(...)``) need no special
|
|
94
|
+
casing. Components receive these as their ``*children``.
|
|
43
95
|
key: Optional stable identity used by the reconciler when
|
|
44
|
-
diffing keyed lists. Two elements with the same
|
|
45
|
-
|
|
96
|
+
diffing keyed lists. Two elements with the same ``type`` and
|
|
97
|
+
``key`` are treated as the same logical node across renders.
|
|
46
98
|
"""
|
|
47
99
|
|
|
48
100
|
__slots__ = ("type", "props", "children", "key")
|
|
49
101
|
|
|
50
102
|
def __init__(
|
|
51
103
|
self,
|
|
52
|
-
|
|
53
|
-
props: Dict[str, Any],
|
|
54
|
-
children:
|
|
104
|
+
type_: Any,
|
|
105
|
+
props: Optional[Dict[str, Any]] = None,
|
|
106
|
+
children: Optional[Iterable[Any]] = None,
|
|
55
107
|
key: Optional[str] = None,
|
|
56
108
|
) -> None:
|
|
57
|
-
self.type =
|
|
58
|
-
self.props = props
|
|
59
|
-
self.children = children
|
|
109
|
+
self.type = type_
|
|
110
|
+
self.props: Dict[str, Any] = props if props is not None else {}
|
|
111
|
+
self.children: List[Any] = list(children) if children is not None else []
|
|
60
112
|
self.key = key
|
|
61
113
|
|
|
62
114
|
def __repr__(self) -> str:
|
|
63
|
-
|
|
64
|
-
return f"Element({t!r}, props={set(self.props)}, children={len(self.children)})"
|
|
115
|
+
return f"Element({type_label(self.type)!r}, props={sorted(self.props)}, children={len(self.children)})"
|
|
65
116
|
|
|
66
117
|
def __eq__(self, other: object) -> bool:
|
|
67
118
|
if not isinstance(other, Element):
|
|
@@ -78,3 +129,31 @@ class Element:
|
|
|
78
129
|
if result is NotImplemented:
|
|
79
130
|
return result
|
|
80
131
|
return not result
|
|
132
|
+
|
|
133
|
+
__hash__ = None # type: ignore[assignment,unused-ignore]
|
|
134
|
+
|
|
135
|
+
def with_key(self, key: Optional[str]) -> "Element":
|
|
136
|
+
"""Return a copy of this element carrying ``key``.
|
|
137
|
+
|
|
138
|
+
Handy when a factory result needs a key after the fact, for
|
|
139
|
+
example while building a list comprehension over elements
|
|
140
|
+
produced by a helper that doesn't take ``key``.
|
|
141
|
+
"""
|
|
142
|
+
return Element(self.type, self.props, self.children, key=key)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
Node = Union[Element, None, bool, Iterable[Any]]
|
|
146
|
+
"""Anything a component may render: an element, ``None`` / ``False``
|
|
147
|
+
for "nothing", or a (possibly nested) iterable of nodes."""
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def type_label(type_obj: Any) -> str:
|
|
151
|
+
"""Return a human-readable name for an element type (for messages)."""
|
|
152
|
+
if isinstance(type_obj, str):
|
|
153
|
+
return type_obj
|
|
154
|
+
if isinstance(type_obj, StructuralType):
|
|
155
|
+
return type_obj.name
|
|
156
|
+
name = getattr(type_obj, "display_name", None) or getattr(type_obj, "__name__", None)
|
|
157
|
+
if name:
|
|
158
|
+
return str(name)
|
|
159
|
+
return repr(type_obj)
|
pythonnative/gestures.py
CHANGED
|
@@ -107,7 +107,7 @@ class GestureState:
|
|
|
107
107
|
|
|
108
108
|
GestureStateName = Literal["began", "changed", "ended", "cancelled"]
|
|
109
109
|
|
|
110
|
-
GestureCallback = Callable[["GestureEvent"],
|
|
110
|
+
GestureCallback = Callable[["GestureEvent"], Any]
|
|
111
111
|
|
|
112
112
|
SwipeDirection = Literal["any", "left", "right", "up", "down"]
|
|
113
113
|
|