pythonnative 0.23.0__py3-none-any.whl → 0.25.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 +44 -53
- pythonnative/animated.py +3 -3
- pythonnative/cli/pn.py +3 -3
- pythonnative/components.py +309 -513
- pythonnative/diagnostics.py +214 -0
- pythonnative/element.py +5 -2
- pythonnative/events.py +13 -8
- pythonnative/hooks.py +761 -114
- pythonnative/hot_reload.py +9 -1
- pythonnative/native_modules/notifications.py +36 -4
- pythonnative/native_modules/permissions.py +24 -1
- pythonnative/native_views/android.py +85 -3
- pythonnative/native_views/desktop.py +38 -1
- pythonnative/native_views/ios.py +252 -7
- pythonnative/navigation.py +41 -17
- pythonnative/preview.py +43 -7
- pythonnative/reconciler.py +1360 -457
- pythonnative/runtime.py +407 -215
- pythonnative/screen.py +377 -34
- pythonnative/storage.py +5 -5
- pythonnative/style.py +83 -0
- pythonnative/suspense.py +417 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +22 -0
- {pythonnative-0.23.0.dist-info → pythonnative-0.25.0.dist-info}/METADATA +6 -4
- {pythonnative-0.23.0.dist-info → pythonnative-0.25.0.dist-info}/RECORD +29 -27
- {pythonnative-0.23.0.dist-info → pythonnative-0.25.0.dist-info}/WHEEL +1 -1
- {pythonnative-0.23.0.dist-info → pythonnative-0.25.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.23.0.dist-info → pythonnative-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.23.0.dist-info → pythonnative-0.25.0.dist-info}/top_level.txt +0 -0
pythonnative/components.py
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
"""Built-in element factories
|
|
1
|
+
"""Built-in element factories.
|
|
2
2
|
|
|
3
|
-
Each
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
The same Props dataclasses are used by the `pythonnative.sdk` surface
|
|
12
|
-
for third-party components, so the built-in API and the extension API
|
|
13
|
-
speak the same shape.
|
|
3
|
+
Each factory function (``Text``, ``Button``, …) is a fully-typed thin
|
|
4
|
+
wrapper that builds an [`Element`][pythonnative.Element] through the
|
|
5
|
+
shared :func:`_make_element` helper, so style resolution, ``ref``
|
|
6
|
+
attachment, ``None``-default dropping, and forced overrides (e.g.
|
|
7
|
+
``Column``'s fixed ``flex_direction``) live in exactly one place. The
|
|
8
|
+
factory signatures themselves are the canonical prop schemas: editors
|
|
9
|
+
and type checkers validate calls directly against them.
|
|
14
10
|
|
|
15
11
|
Example:
|
|
16
12
|
```python
|
|
@@ -18,27 +14,27 @@ Example:
|
|
|
18
14
|
|
|
19
15
|
pn.Column(
|
|
20
16
|
pn.Text("Hello", style=pn.style(font_size=18)),
|
|
21
|
-
pn.Button("Tap",
|
|
17
|
+
pn.Button("Tap", on_press=lambda: print("tapped")),
|
|
22
18
|
style=pn.style(spacing=12, padding=16),
|
|
23
19
|
)
|
|
24
20
|
```
|
|
25
21
|
"""
|
|
26
22
|
|
|
27
23
|
import bisect
|
|
28
|
-
from dataclasses import dataclass, field
|
|
29
24
|
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union
|
|
30
25
|
|
|
31
26
|
from .element import Element
|
|
32
27
|
from .hooks import (
|
|
28
|
+
Ref,
|
|
33
29
|
component,
|
|
34
|
-
|
|
30
|
+
use_imperative_handle,
|
|
35
31
|
use_keyboard_height,
|
|
36
32
|
use_ref,
|
|
37
33
|
use_safe_area_insets,
|
|
38
34
|
use_state,
|
|
39
35
|
)
|
|
40
|
-
from .sdk import Props
|
|
41
36
|
from .style import (
|
|
37
|
+
AccessibilityState,
|
|
42
38
|
AutoCapitalize,
|
|
43
39
|
Color,
|
|
44
40
|
KeyboardType,
|
|
@@ -46,6 +42,7 @@ from .style import (
|
|
|
46
42
|
ScaleType,
|
|
47
43
|
StyleProp,
|
|
48
44
|
resolve_style,
|
|
45
|
+
validate_style_keys,
|
|
49
46
|
)
|
|
50
47
|
|
|
51
48
|
# ======================================================================
|
|
@@ -57,7 +54,7 @@ def _make_element(
|
|
|
57
54
|
name: str,
|
|
58
55
|
*children: Element,
|
|
59
56
|
style: StyleProp = None,
|
|
60
|
-
ref: Optional[
|
|
57
|
+
ref: Optional[Ref] = None,
|
|
61
58
|
key: Optional[str] = None,
|
|
62
59
|
_defaults: Optional[Dict[str, Any]] = None,
|
|
63
60
|
_forced: Optional[Dict[str, Any]] = None,
|
|
@@ -85,8 +82,9 @@ def _make_element(
|
|
|
85
82
|
name: Element type name (e.g. ``"Text"``).
|
|
86
83
|
*children: Child elements.
|
|
87
84
|
style: Style dict, list of dicts, or ``None``.
|
|
88
|
-
ref: Optional ``use_ref()
|
|
89
|
-
``ref
|
|
85
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``; the
|
|
86
|
+
reconciler populates ``ref.current`` with the underlying
|
|
87
|
+
native view.
|
|
90
88
|
key: Stable identity for keyed reconciliation.
|
|
91
89
|
_defaults: Internal: fill-only-if-missing prop defaults.
|
|
92
90
|
_forced: Internal: prop overrides applied last.
|
|
@@ -96,6 +94,8 @@ def _make_element(
|
|
|
96
94
|
A fresh [`Element`][pythonnative.Element].
|
|
97
95
|
"""
|
|
98
96
|
out: Dict[str, Any] = dict(resolve_style(style))
|
|
97
|
+
if out:
|
|
98
|
+
validate_style_keys(out, owner=name)
|
|
99
99
|
if _defaults:
|
|
100
100
|
for k, v in _defaults.items():
|
|
101
101
|
out.setdefault(k, v)
|
|
@@ -109,352 +109,6 @@ def _make_element(
|
|
|
109
109
|
return Element(name, out, list(children), key=key)
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
# ======================================================================
|
|
113
|
-
# Props dataclasses
|
|
114
|
-
# ======================================================================
|
|
115
|
-
#
|
|
116
|
-
# These are the canonical schemas for every built-in component. They
|
|
117
|
-
# subclass the SDK's ``Props`` base, so the same shape works for both
|
|
118
|
-
# the built-in factory functions and the third-party
|
|
119
|
-
# [`element_factory`][pythonnative.element_factory] API.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
@dataclass(frozen=True)
|
|
123
|
-
class TextProps(Props):
|
|
124
|
-
"""Props for [`Text`][pythonnative.Text]."""
|
|
125
|
-
|
|
126
|
-
text: str = ""
|
|
127
|
-
spans: Optional[List[Dict[str, Any]]] = None
|
|
128
|
-
accessibility_label: Optional[str] = None
|
|
129
|
-
accessibility_hint: Optional[str] = None
|
|
130
|
-
accessibility_role: Optional[str] = None
|
|
131
|
-
accessible: Optional[bool] = None
|
|
132
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
133
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
134
|
-
test_id: Optional[str] = None
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
@dataclass(frozen=True)
|
|
138
|
-
class ButtonProps(Props):
|
|
139
|
-
"""Props for [`Button`][pythonnative.Button]."""
|
|
140
|
-
|
|
141
|
-
title: str = ""
|
|
142
|
-
on_click: Optional[Callable[[], None]] = None
|
|
143
|
-
enabled: bool = True
|
|
144
|
-
accessibility_label: Optional[str] = None
|
|
145
|
-
accessibility_hint: Optional[str] = None
|
|
146
|
-
accessibility_role: Optional[str] = None
|
|
147
|
-
accessible: Optional[bool] = None
|
|
148
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
149
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
150
|
-
test_id: Optional[str] = None
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@dataclass(frozen=True)
|
|
154
|
-
class TextInputProps(Props):
|
|
155
|
-
"""Props for [`TextInput`][pythonnative.TextInput]."""
|
|
156
|
-
|
|
157
|
-
value: str = ""
|
|
158
|
-
placeholder: Optional[str] = None
|
|
159
|
-
on_change: Optional[Callable[[str], None]] = None
|
|
160
|
-
on_submit: Optional[Callable[[str], None]] = None
|
|
161
|
-
secure: bool = False
|
|
162
|
-
multiline: bool = False
|
|
163
|
-
keyboard_type: Optional[KeyboardType] = None
|
|
164
|
-
auto_capitalize: Optional[AutoCapitalize] = None
|
|
165
|
-
auto_correct: Optional[bool] = None
|
|
166
|
-
auto_focus: bool = False
|
|
167
|
-
return_key_type: Optional[ReturnKeyType] = None
|
|
168
|
-
max_length: Optional[int] = None
|
|
169
|
-
placeholder_color: Optional[Color] = None
|
|
170
|
-
editable: bool = True
|
|
171
|
-
clear_button: bool = False
|
|
172
|
-
on_focus: Optional[Callable[[], None]] = None
|
|
173
|
-
on_blur: Optional[Callable[[], None]] = None
|
|
174
|
-
selection_color: Optional[Color] = None
|
|
175
|
-
text_content_type: Optional[str] = None
|
|
176
|
-
accessibility_label: Optional[str] = None
|
|
177
|
-
accessibility_hint: Optional[str] = None
|
|
178
|
-
accessible: Optional[bool] = None
|
|
179
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
180
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
181
|
-
test_id: Optional[str] = None
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
@dataclass(frozen=True)
|
|
185
|
-
class ImageProps(Props):
|
|
186
|
-
"""Props for [`Image`][pythonnative.Image]."""
|
|
187
|
-
|
|
188
|
-
source: Optional[str] = None
|
|
189
|
-
scale_type: Optional[ScaleType] = None
|
|
190
|
-
tint_color: Optional[Color] = None
|
|
191
|
-
placeholder_color: Optional[Color] = None
|
|
192
|
-
on_load: Optional[Callable[[], None]] = None
|
|
193
|
-
on_error: Optional[Callable[[str], None]] = None
|
|
194
|
-
accessibility_label: Optional[str] = None
|
|
195
|
-
accessibility_role: Optional[str] = None
|
|
196
|
-
accessible: Optional[bool] = None
|
|
197
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
198
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
199
|
-
test_id: Optional[str] = None
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
@dataclass(frozen=True)
|
|
203
|
-
class SwitchProps(Props):
|
|
204
|
-
"""Props for [`Switch`][pythonnative.Switch]."""
|
|
205
|
-
|
|
206
|
-
value: bool = False
|
|
207
|
-
on_change: Optional[Callable[[bool], None]] = None
|
|
208
|
-
accessibility_label: Optional[str] = None
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
@dataclass(frozen=True)
|
|
212
|
-
class ProgressBarProps(Props):
|
|
213
|
-
"""Props for [`ProgressBar`][pythonnative.ProgressBar]."""
|
|
214
|
-
|
|
215
|
-
value: float = 0.0
|
|
216
|
-
color: Optional[Color] = None
|
|
217
|
-
track_color: Optional[Color] = None
|
|
218
|
-
indeterminate: bool = False
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
@dataclass(frozen=True)
|
|
222
|
-
class ActivityIndicatorProps(Props):
|
|
223
|
-
"""Props for [`ActivityIndicator`][pythonnative.ActivityIndicator]."""
|
|
224
|
-
|
|
225
|
-
animating: bool = True
|
|
226
|
-
color: Optional[Color] = None
|
|
227
|
-
size: Literal["small", "large"] = "small"
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
@dataclass(frozen=True)
|
|
231
|
-
class WebViewProps(Props):
|
|
232
|
-
"""Props for [`WebView`][pythonnative.WebView]."""
|
|
233
|
-
|
|
234
|
-
url: Optional[str] = None
|
|
235
|
-
html: Optional[str] = None
|
|
236
|
-
on_load: Optional[Callable[[str], None]] = None
|
|
237
|
-
on_message: Optional[Callable[[str], None]] = None
|
|
238
|
-
on_navigation_state_change: Optional[Callable[[str], None]] = None
|
|
239
|
-
inject_javascript: Optional[str] = None
|
|
240
|
-
scroll_enabled: bool = True
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
@dataclass(frozen=True)
|
|
244
|
-
class SpacerProps(Props):
|
|
245
|
-
"""Props for [`Spacer`][pythonnative.Spacer]."""
|
|
246
|
-
|
|
247
|
-
size: Optional[float] = None
|
|
248
|
-
flex: Optional[float] = None
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
@dataclass(frozen=True)
|
|
252
|
-
class SliderProps(Props):
|
|
253
|
-
"""Props for [`Slider`][pythonnative.Slider]."""
|
|
254
|
-
|
|
255
|
-
value: float = 0.0
|
|
256
|
-
min_value: float = 0.0
|
|
257
|
-
max_value: float = 1.0
|
|
258
|
-
on_change: Optional[Callable[[float], None]] = None
|
|
259
|
-
accessibility_label: Optional[str] = None
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
@dataclass(frozen=True)
|
|
263
|
-
class ViewProps(Props):
|
|
264
|
-
"""Props for [`View`][pythonnative.View], [`Column`][pythonnative.Column], and [`Row`][pythonnative.Row]."""
|
|
265
|
-
|
|
266
|
-
gestures: Optional[List[Any]] = None
|
|
267
|
-
accessibility_label: Optional[str] = None
|
|
268
|
-
accessibility_hint: Optional[str] = None
|
|
269
|
-
accessibility_role: Optional[str] = None
|
|
270
|
-
accessible: Optional[bool] = None
|
|
271
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
272
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
273
|
-
test_id: Optional[str] = None
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
@dataclass(frozen=True)
|
|
277
|
-
class ScrollViewProps(Props):
|
|
278
|
-
"""Props for [`ScrollView`][pythonnative.ScrollView].
|
|
279
|
-
|
|
280
|
-
``on_scroll`` receives a single payload dict with ``"x"`` and
|
|
281
|
-
``"y"`` content offsets in points.
|
|
282
|
-
"""
|
|
283
|
-
|
|
284
|
-
refresh_control: Optional[Dict[str, Any]] = None
|
|
285
|
-
scroll_axis: Optional[Literal["vertical", "horizontal"]] = None
|
|
286
|
-
on_scroll: Optional[Callable[[Dict[str, float]], None]] = None
|
|
287
|
-
shows_scroll_indicator: bool = True
|
|
288
|
-
paging_enabled: bool = False
|
|
289
|
-
bounces: bool = True
|
|
290
|
-
content_container_style: StyleProp = None
|
|
291
|
-
keyboard_dismiss_mode: Optional[Literal["none", "on_drag", "interactive"]] = None
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
@dataclass(frozen=True)
|
|
295
|
-
class SafeAreaViewProps(Props):
|
|
296
|
-
"""Props for [`SafeAreaView`][pythonnative.SafeAreaView]."""
|
|
297
|
-
|
|
298
|
-
edges: Optional[Tuple[Literal["top", "left", "bottom", "right"], ...]] = None
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
@dataclass(frozen=True)
|
|
302
|
-
class ModalProps(Props):
|
|
303
|
-
"""Props for [`Modal`][pythonnative.Modal]."""
|
|
304
|
-
|
|
305
|
-
visible: bool = False
|
|
306
|
-
on_dismiss: Optional[Callable[[], None]] = None
|
|
307
|
-
on_show: Optional[Callable[[], None]] = None
|
|
308
|
-
title: Optional[str] = None
|
|
309
|
-
animation_type: Literal["slide", "fade", "none"] = "slide"
|
|
310
|
-
transparent: bool = False
|
|
311
|
-
presentation_style: Literal["page_sheet", "form_sheet", "full_screen", "overlay"] = "page_sheet"
|
|
312
|
-
dismiss_on_backdrop: bool = True
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
@dataclass(frozen=True)
|
|
316
|
-
class PressableProps(Props):
|
|
317
|
-
"""Props for [`Pressable`][pythonnative.Pressable]."""
|
|
318
|
-
|
|
319
|
-
on_press: Optional[Callable[[], None]] = None
|
|
320
|
-
on_long_press: Optional[Callable[[], None]] = None
|
|
321
|
-
on_press_in: Optional[Callable[[], None]] = None
|
|
322
|
-
on_press_out: Optional[Callable[[], None]] = None
|
|
323
|
-
pressed_opacity: float = 0.6
|
|
324
|
-
gestures: Optional[List[Any]] = None
|
|
325
|
-
accessibility_label: Optional[str] = None
|
|
326
|
-
accessibility_hint: Optional[str] = None
|
|
327
|
-
accessibility_role: Optional[str] = None
|
|
328
|
-
accessible: Optional[bool] = None
|
|
329
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
330
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
331
|
-
test_id: Optional[str] = None
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
@dataclass(frozen=True)
|
|
335
|
-
class StatusBarProps(Props):
|
|
336
|
-
"""Props for [`StatusBar`][pythonnative.StatusBar]."""
|
|
337
|
-
|
|
338
|
-
bar_style: Optional[Literal["light", "dark", "default"]] = None
|
|
339
|
-
background_color: Optional[Color] = None
|
|
340
|
-
hidden: Optional[bool] = None
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
@dataclass(frozen=True)
|
|
344
|
-
class KeyboardAvoidingViewProps(Props):
|
|
345
|
-
"""Props for [`KeyboardAvoidingView`][pythonnative.KeyboardAvoidingView]."""
|
|
346
|
-
|
|
347
|
-
behavior: Literal["padding", "position"] = "padding"
|
|
348
|
-
keyboard_vertical_offset: float = 0.0
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
@dataclass(frozen=True)
|
|
352
|
-
class PickerProps(Props):
|
|
353
|
-
"""Props for [`Picker`][pythonnative.Picker].
|
|
354
|
-
|
|
355
|
-
``items`` is an ordered list of ``{"value": Any, "label": str}``
|
|
356
|
-
entries. ``value`` is matched against ``items[i]["value"]`` to
|
|
357
|
-
determine the currently selected row.
|
|
358
|
-
"""
|
|
359
|
-
|
|
360
|
-
value: Any = None
|
|
361
|
-
items: List[Dict[str, Any]] = field(default_factory=list)
|
|
362
|
-
on_change: Optional[Callable[[Any], None]] = None
|
|
363
|
-
placeholder: str = "Select…"
|
|
364
|
-
accessibility_label: Optional[str] = None
|
|
365
|
-
accessibility_hint: Optional[str] = None
|
|
366
|
-
accessible: Optional[bool] = None
|
|
367
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
368
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
369
|
-
test_id: Optional[str] = None
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
@dataclass(frozen=True)
|
|
373
|
-
class TouchableOpacityProps(Props):
|
|
374
|
-
"""Props for [`TouchableOpacity`][pythonnative.TouchableOpacity]."""
|
|
375
|
-
|
|
376
|
-
on_press: Optional[Callable[[], None]] = None
|
|
377
|
-
on_long_press: Optional[Callable[[], None]] = None
|
|
378
|
-
active_opacity: float = 0.2
|
|
379
|
-
disabled: bool = False
|
|
380
|
-
accessibility_label: Optional[str] = None
|
|
381
|
-
accessibility_hint: Optional[str] = None
|
|
382
|
-
accessibility_role: Optional[str] = None
|
|
383
|
-
accessible: Optional[bool] = None
|
|
384
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
385
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
386
|
-
test_id: Optional[str] = None
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
@dataclass(frozen=True)
|
|
390
|
-
class ImageBackgroundProps(Props):
|
|
391
|
-
"""Props for [`ImageBackground`][pythonnative.ImageBackground]."""
|
|
392
|
-
|
|
393
|
-
source: Optional[str] = None
|
|
394
|
-
scale_type: Optional[ScaleType] = None
|
|
395
|
-
accessibility_label: Optional[str] = None
|
|
396
|
-
accessible: Optional[bool] = None
|
|
397
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
398
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
399
|
-
test_id: Optional[str] = None
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
@dataclass(frozen=True)
|
|
403
|
-
class CheckboxProps(Props):
|
|
404
|
-
"""Props for [`Checkbox`][pythonnative.Checkbox]."""
|
|
405
|
-
|
|
406
|
-
value: bool = False
|
|
407
|
-
on_change: Optional[Callable[[bool], None]] = None
|
|
408
|
-
label: Optional[str] = None
|
|
409
|
-
disabled: bool = False
|
|
410
|
-
color: Optional[Color] = None
|
|
411
|
-
accessibility_label: Optional[str] = None
|
|
412
|
-
accessibility_hint: Optional[str] = None
|
|
413
|
-
accessible: Optional[bool] = None
|
|
414
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
415
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
416
|
-
test_id: Optional[str] = None
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
@dataclass(frozen=True)
|
|
420
|
-
class SegmentedControlProps(Props):
|
|
421
|
-
"""Props for [`SegmentedControl`][pythonnative.SegmentedControl]."""
|
|
422
|
-
|
|
423
|
-
segments: List[str] = field(default_factory=list)
|
|
424
|
-
selected_index: int = 0
|
|
425
|
-
on_change: Optional[Callable[[int], None]] = None
|
|
426
|
-
enabled: bool = True
|
|
427
|
-
tint_color: Optional[Color] = None
|
|
428
|
-
accessibility_label: Optional[str] = None
|
|
429
|
-
accessible: Optional[bool] = None
|
|
430
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
431
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
432
|
-
test_id: Optional[str] = None
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
@dataclass(frozen=True)
|
|
436
|
-
class DatePickerProps(Props):
|
|
437
|
-
"""Props for [`DatePicker`][pythonnative.DatePicker].
|
|
438
|
-
|
|
439
|
-
``value`` and the value passed to ``on_change`` are ISO-8601
|
|
440
|
-
strings (``"2026-05-31"`` for ``mode="date"``, ``"14:30"`` for
|
|
441
|
-
``mode="time"``, ``"2026-05-31T14:30"`` for ``mode="datetime"``),
|
|
442
|
-
so the schema stays JSON-serializable and platform-agnostic.
|
|
443
|
-
"""
|
|
444
|
-
|
|
445
|
-
value: Optional[str] = None
|
|
446
|
-
mode: Literal["date", "time", "datetime"] = "date"
|
|
447
|
-
on_change: Optional[Callable[[str], None]] = None
|
|
448
|
-
minimum: Optional[str] = None
|
|
449
|
-
maximum: Optional[str] = None
|
|
450
|
-
enabled: bool = True
|
|
451
|
-
accessibility_label: Optional[str] = None
|
|
452
|
-
accessible: Optional[bool] = None
|
|
453
|
-
accessibility_state: Optional[Dict[str, Any]] = None
|
|
454
|
-
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None
|
|
455
|
-
test_id: Optional[str] = None
|
|
456
|
-
|
|
457
|
-
|
|
458
112
|
# ======================================================================
|
|
459
113
|
# Leaf factories
|
|
460
114
|
# ======================================================================
|
|
@@ -518,10 +172,10 @@ def Text(
|
|
|
518
172
|
accessibility_hint: Optional[str] = None,
|
|
519
173
|
accessibility_role: Optional[str] = None,
|
|
520
174
|
accessible: Optional[bool] = None,
|
|
521
|
-
accessibility_state: Optional[
|
|
175
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
522
176
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
523
177
|
test_id: Optional[str] = None,
|
|
524
|
-
ref: Optional[
|
|
178
|
+
ref: Optional[Ref] = None,
|
|
525
179
|
key: Optional[str] = None,
|
|
526
180
|
) -> Element:
|
|
527
181
|
"""Display a string of text, optionally with styled nested spans.
|
|
@@ -571,7 +225,7 @@ def Text(
|
|
|
571
225
|
test_id: Stable identifier for UI tests; exposed as
|
|
572
226
|
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
573
227
|
on iOS.
|
|
574
|
-
ref: Optional ``use_ref()
|
|
228
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
575
229
|
key: Stable identity for keyed reconciliation.
|
|
576
230
|
|
|
577
231
|
Returns:
|
|
@@ -604,17 +258,17 @@ def Text(
|
|
|
604
258
|
def Button(
|
|
605
259
|
title: str = "",
|
|
606
260
|
*,
|
|
607
|
-
|
|
261
|
+
on_press: Optional[Callable[[], None]] = None,
|
|
608
262
|
enabled: bool = True,
|
|
609
263
|
style: StyleProp = None,
|
|
610
264
|
accessibility_label: Optional[str] = None,
|
|
611
265
|
accessibility_hint: Optional[str] = None,
|
|
612
266
|
accessibility_role: Optional[str] = None,
|
|
613
267
|
accessible: Optional[bool] = None,
|
|
614
|
-
accessibility_state: Optional[
|
|
268
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
615
269
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
616
270
|
test_id: Optional[str] = None,
|
|
617
|
-
ref: Optional[
|
|
271
|
+
ref: Optional[Ref] = None,
|
|
618
272
|
key: Optional[str] = None,
|
|
619
273
|
) -> Element:
|
|
620
274
|
"""Display a tappable button.
|
|
@@ -627,7 +281,7 @@ def Button(
|
|
|
627
281
|
|
|
628
282
|
Args:
|
|
629
283
|
title: Button label.
|
|
630
|
-
|
|
284
|
+
on_press: Callback invoked when the user taps the button.
|
|
631
285
|
enabled: When ``False``, the button is disabled and cannot be
|
|
632
286
|
tapped.
|
|
633
287
|
style: Style dict (or list of dicts).
|
|
@@ -645,7 +299,7 @@ def Button(
|
|
|
645
299
|
test_id: Stable identifier for UI tests; exposed as
|
|
646
300
|
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
647
301
|
on iOS.
|
|
648
|
-
ref: Optional ``use_ref()
|
|
302
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
649
303
|
key: Stable identity for keyed reconciliation.
|
|
650
304
|
|
|
651
305
|
Returns:
|
|
@@ -657,7 +311,7 @@ def Button(
|
|
|
657
311
|
ref=ref,
|
|
658
312
|
key=key,
|
|
659
313
|
title=title,
|
|
660
|
-
|
|
314
|
+
on_press=on_press,
|
|
661
315
|
enabled=enabled,
|
|
662
316
|
accessibility_label=accessibility_label,
|
|
663
317
|
accessibility_hint=accessibility_hint,
|
|
@@ -695,10 +349,10 @@ def TextInput(
|
|
|
695
349
|
accessibility_label: Optional[str] = None,
|
|
696
350
|
accessibility_hint: Optional[str] = None,
|
|
697
351
|
accessible: Optional[bool] = None,
|
|
698
|
-
accessibility_state: Optional[
|
|
352
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
699
353
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
700
354
|
test_id: Optional[str] = None,
|
|
701
|
-
ref: Optional[
|
|
355
|
+
ref: Optional[Ref] = None,
|
|
702
356
|
key: Optional[str] = None,
|
|
703
357
|
) -> Element:
|
|
704
358
|
"""Display a text-entry field (single-line by default, or ``multiline``).
|
|
@@ -748,7 +402,7 @@ def TextInput(
|
|
|
748
402
|
test_id: Stable identifier for UI tests; exposed as
|
|
749
403
|
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
750
404
|
on iOS.
|
|
751
|
-
ref: Optional ``use_ref()
|
|
405
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
752
406
|
key: Stable identity for keyed reconciliation.
|
|
753
407
|
|
|
754
408
|
Returns:
|
|
@@ -799,10 +453,10 @@ def Image(
|
|
|
799
453
|
accessibility_label: Optional[str] = None,
|
|
800
454
|
accessibility_role: Optional[str] = None,
|
|
801
455
|
accessible: Optional[bool] = None,
|
|
802
|
-
accessibility_state: Optional[
|
|
456
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
803
457
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
804
458
|
test_id: Optional[str] = None,
|
|
805
|
-
ref: Optional[
|
|
459
|
+
ref: Optional[Ref] = None,
|
|
806
460
|
key: Optional[str] = None,
|
|
807
461
|
) -> Element:
|
|
808
462
|
"""Display an image from a resource path or URL.
|
|
@@ -842,7 +496,7 @@ def Image(
|
|
|
842
496
|
test_id: Stable identifier for UI tests; exposed as
|
|
843
497
|
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
844
498
|
on iOS.
|
|
845
|
-
ref: Optional ``use_ref()
|
|
499
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
846
500
|
key: Stable identity for keyed reconciliation.
|
|
847
501
|
|
|
848
502
|
Returns:
|
|
@@ -1105,10 +759,10 @@ def View(
|
|
|
1105
759
|
accessibility_hint: Optional[str] = None,
|
|
1106
760
|
accessibility_role: Optional[str] = None,
|
|
1107
761
|
accessible: Optional[bool] = None,
|
|
1108
|
-
accessibility_state: Optional[
|
|
762
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
1109
763
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
1110
764
|
test_id: Optional[str] = None,
|
|
1111
|
-
ref: Optional[
|
|
765
|
+
ref: Optional[Ref] = None,
|
|
1112
766
|
key: Optional[str] = None,
|
|
1113
767
|
) -> Element:
|
|
1114
768
|
"""Universal flex container (like React Native's ``View``).
|
|
@@ -1157,7 +811,7 @@ def View(
|
|
|
1157
811
|
test_id: Stable identifier for UI tests; exposed as
|
|
1158
812
|
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
1159
813
|
on iOS.
|
|
1160
|
-
ref: Optional ``use_ref()
|
|
814
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
1161
815
|
key: Stable identity for keyed reconciliation.
|
|
1162
816
|
|
|
1163
817
|
Returns:
|
|
@@ -1184,7 +838,7 @@ def View(
|
|
|
1184
838
|
def Column(
|
|
1185
839
|
*children: Element,
|
|
1186
840
|
style: StyleProp = None,
|
|
1187
|
-
ref: Optional[
|
|
841
|
+
ref: Optional[Ref] = None,
|
|
1188
842
|
key: Optional[str] = None,
|
|
1189
843
|
) -> Element:
|
|
1190
844
|
"""Arrange children vertically.
|
|
@@ -1196,7 +850,7 @@ def Column(
|
|
|
1196
850
|
Args:
|
|
1197
851
|
*children: Child elements stacked top to bottom.
|
|
1198
852
|
style: Style dict (or list of dicts).
|
|
1199
|
-
ref: Optional
|
|
853
|
+
ref: Optional [`Ref`][pythonnative.Ref] for native-view access.
|
|
1200
854
|
key: Stable identity for keyed reconciliation.
|
|
1201
855
|
|
|
1202
856
|
Returns:
|
|
@@ -1215,7 +869,7 @@ def Column(
|
|
|
1215
869
|
def Row(
|
|
1216
870
|
*children: Element,
|
|
1217
871
|
style: StyleProp = None,
|
|
1218
|
-
ref: Optional[
|
|
872
|
+
ref: Optional[Ref] = None,
|
|
1219
873
|
key: Optional[str] = None,
|
|
1220
874
|
) -> Element:
|
|
1221
875
|
"""Arrange children horizontally.
|
|
@@ -1227,7 +881,7 @@ def Row(
|
|
|
1227
881
|
Args:
|
|
1228
882
|
*children: Child elements arranged left to right.
|
|
1229
883
|
style: Style dict (or list of dicts).
|
|
1230
|
-
ref: Optional
|
|
884
|
+
ref: Optional [`Ref`][pythonnative.Ref] for native-view access.
|
|
1231
885
|
key: Stable identity for keyed reconciliation.
|
|
1232
886
|
|
|
1233
887
|
Returns:
|
|
@@ -1254,7 +908,7 @@ def ScrollView(
|
|
|
1254
908
|
content_container_style: StyleProp = None,
|
|
1255
909
|
keyboard_dismiss_mode: Optional[Literal["none", "on_drag", "interactive"]] = None,
|
|
1256
910
|
style: StyleProp = None,
|
|
1257
|
-
ref: Optional[
|
|
911
|
+
ref: Optional[Ref] = None,
|
|
1258
912
|
key: Optional[str] = None,
|
|
1259
913
|
) -> Element:
|
|
1260
914
|
"""Wrap children in a scrollable container.
|
|
@@ -1285,7 +939,7 @@ def ScrollView(
|
|
|
1285
939
|
``"interactive"``. Controls whether scrolling dismisses
|
|
1286
940
|
the keyboard.
|
|
1287
941
|
style: Style dict (or list of dicts).
|
|
1288
|
-
ref: Optional ``use_ref()
|
|
942
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
1289
943
|
key: Stable identity for keyed reconciliation.
|
|
1290
944
|
|
|
1291
945
|
Returns:
|
|
@@ -1495,10 +1149,10 @@ def Pressable(
|
|
|
1495
1149
|
accessibility_hint: Optional[str] = None,
|
|
1496
1150
|
accessibility_role: Optional[str] = None,
|
|
1497
1151
|
accessible: Optional[bool] = None,
|
|
1498
|
-
accessibility_state: Optional[
|
|
1152
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
1499
1153
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
1500
1154
|
test_id: Optional[str] = None,
|
|
1501
|
-
ref: Optional[
|
|
1155
|
+
ref: Optional[Ref] = None,
|
|
1502
1156
|
key: Optional[str] = None,
|
|
1503
1157
|
) -> Element:
|
|
1504
1158
|
"""Wrap children with tap / long-press / gesture handlers.
|
|
@@ -1547,7 +1201,7 @@ def Pressable(
|
|
|
1547
1201
|
test_id: Stable identifier for UI tests; exposed as
|
|
1548
1202
|
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
1549
1203
|
on iOS.
|
|
1550
|
-
ref: Optional ``use_ref()
|
|
1204
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
1551
1205
|
key: Stable identity for keyed reconciliation.
|
|
1552
1206
|
|
|
1553
1207
|
Returns:
|
|
@@ -1613,7 +1267,7 @@ def TouchableOpacity(
|
|
|
1613
1267
|
accessibility_hint: Optional[str] = None,
|
|
1614
1268
|
accessibility_role: Optional[str] = None,
|
|
1615
1269
|
accessible: Optional[bool] = None,
|
|
1616
|
-
accessibility_state: Optional[
|
|
1270
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
1617
1271
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
1618
1272
|
test_id: Optional[str] = None,
|
|
1619
1273
|
key: Optional[str] = None,
|
|
@@ -1683,7 +1337,7 @@ def ImageBackground(
|
|
|
1683
1337
|
style: StyleProp = None,
|
|
1684
1338
|
accessibility_label: Optional[str] = None,
|
|
1685
1339
|
accessible: Optional[bool] = None,
|
|
1686
|
-
accessibility_state: Optional[
|
|
1340
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
1687
1341
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
1688
1342
|
test_id: Optional[str] = None,
|
|
1689
1343
|
key: Optional[str] = None,
|
|
@@ -1751,7 +1405,7 @@ def Checkbox(
|
|
|
1751
1405
|
accessibility_label: Optional[str] = None,
|
|
1752
1406
|
accessibility_hint: Optional[str] = None,
|
|
1753
1407
|
accessible: Optional[bool] = None,
|
|
1754
|
-
accessibility_state: Optional[
|
|
1408
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
1755
1409
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
1756
1410
|
test_id: Optional[str] = None,
|
|
1757
1411
|
key: Optional[str] = None,
|
|
@@ -1816,7 +1470,7 @@ def SegmentedControl(
|
|
|
1816
1470
|
style: StyleProp = None,
|
|
1817
1471
|
accessibility_label: Optional[str] = None,
|
|
1818
1472
|
accessible: Optional[bool] = None,
|
|
1819
|
-
accessibility_state: Optional[
|
|
1473
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
1820
1474
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
1821
1475
|
test_id: Optional[str] = None,
|
|
1822
1476
|
key: Optional[str] = None,
|
|
@@ -1879,7 +1533,7 @@ def DatePicker(
|
|
|
1879
1533
|
style: StyleProp = None,
|
|
1880
1534
|
accessibility_label: Optional[str] = None,
|
|
1881
1535
|
accessible: Optional[bool] = None,
|
|
1882
|
-
accessibility_state: Optional[
|
|
1536
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
1883
1537
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
1884
1538
|
test_id: Optional[str] = None,
|
|
1885
1539
|
key: Optional[str] = None,
|
|
@@ -1889,7 +1543,10 @@ def DatePicker(
|
|
|
1889
1543
|
Backed by ``UIDatePicker`` on iOS and a trigger button that opens
|
|
1890
1544
|
the platform ``DatePickerDialog`` / ``TimePickerDialog`` on
|
|
1891
1545
|
Android. ``value`` and the value reported to ``on_change`` are
|
|
1892
|
-
ISO-8601 strings (
|
|
1546
|
+
ISO-8601 strings (``"2026-05-31"`` for ``mode="date"``, ``"14:30"``
|
|
1547
|
+
for ``mode="time"``, ``"2026-05-31T14:30"`` for
|
|
1548
|
+
``mode="datetime"``), so values stay JSON-serializable and
|
|
1549
|
+
platform-agnostic.
|
|
1893
1550
|
|
|
1894
1551
|
Args:
|
|
1895
1552
|
value: Currently selected value as an ISO-8601 string.
|
|
@@ -1943,14 +1600,12 @@ def DatePicker(
|
|
|
1943
1600
|
def Fragment(*children: Optional[Element], key: Optional[str] = None) -> Element:
|
|
1944
1601
|
"""Group children without adding a wrapping native view.
|
|
1945
1602
|
|
|
1946
|
-
Like React's ``<></>``:
|
|
1947
|
-
|
|
1948
|
-
Fragment
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
[`memo`][pythonnative.memo] / conditional logic when grouping
|
|
1953
|
-
siblings inside another component's child list:
|
|
1603
|
+
Like React's ``<></>``: groups elements without introducing an
|
|
1604
|
+
extra container. Each child mounts as a direct sibling of the
|
|
1605
|
+
Fragment's position in the parent's child list. Components may
|
|
1606
|
+
also simply return a plain ``list`` of elements; ``Fragment``
|
|
1607
|
+
exists for when the group needs a ``key`` (e.g. rendering a list
|
|
1608
|
+
of pairs) or when a single expression reads better.
|
|
1954
1609
|
|
|
1955
1610
|
```python
|
|
1956
1611
|
pn.Column(
|
|
@@ -1964,24 +1619,54 @@ def Fragment(*children: Optional[Element], key: Optional[str] = None) -> Element
|
|
|
1964
1619
|
```
|
|
1965
1620
|
|
|
1966
1621
|
Args:
|
|
1967
|
-
*children: Child elements to expose at the parent level.
|
|
1968
|
-
children are dropped, which makes
|
|
1969
|
-
``cond and pn.Text(...)``
|
|
1970
|
-
|
|
1971
|
-
|
|
1622
|
+
*children: Child elements to expose at the parent level.
|
|
1623
|
+
``None`` and ``False`` children are dropped, which makes
|
|
1624
|
+
conditional rendering with ``cond and pn.Text(...)``
|
|
1625
|
+
ergonomic.
|
|
1626
|
+
key: Stable identity for keyed reconciliation. A keyed
|
|
1627
|
+
Fragment moves all of its children as one unit and
|
|
1628
|
+
preserves their state across reorders.
|
|
1972
1629
|
|
|
1973
1630
|
Returns:
|
|
1974
1631
|
An [`Element`][pythonnative.Element] of type ``"__Fragment__"``.
|
|
1632
|
+
"""
|
|
1633
|
+
kept = [c for c in children if c is not None and c is not False]
|
|
1634
|
+
return Element("__Fragment__", {}, kept, key=key)
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
def Portal(*children: Element, key: Optional[str] = None) -> Element:
|
|
1638
|
+
"""Render ``children`` into a full-screen overlay above everything else.
|
|
1975
1639
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1640
|
+
Like React DOM's ``createPortal``: the children stay part of this
|
|
1641
|
+
component's tree for state, context, and events, but their native
|
|
1642
|
+
views mount in a transparent overlay attached to the window (above
|
|
1643
|
+
the screen's content) instead of inside the surrounding parent.
|
|
1644
|
+
Use it for toasts, dropdowns, tooltips, and lightweight custom
|
|
1645
|
+
overlays that must escape ``overflow: "hidden"`` ancestors. For a
|
|
1646
|
+
system-styled dialog with its own presentation and dismissal
|
|
1647
|
+
gestures, use [`Modal`][pythonnative.Modal] instead.
|
|
1648
|
+
|
|
1649
|
+
The overlay itself does not intercept touches; only the children
|
|
1650
|
+
themselves are hit-testable. Children are laid out against the
|
|
1651
|
+
full viewport, so position them with absolute insets:
|
|
1652
|
+
|
|
1653
|
+
```python
|
|
1654
|
+
pn.Portal(
|
|
1655
|
+
pn.View(
|
|
1656
|
+
pn.Text("Saved!"),
|
|
1657
|
+
style=pn.style(position="absolute", bottom=40, left=40, right=40),
|
|
1658
|
+
),
|
|
1659
|
+
)
|
|
1660
|
+
```
|
|
1661
|
+
|
|
1662
|
+
Args:
|
|
1663
|
+
*children: Overlay content.
|
|
1664
|
+
key: Stable identity for keyed reconciliation.
|
|
1665
|
+
|
|
1666
|
+
Returns:
|
|
1667
|
+
An [`Element`][pythonnative.Element] of type ``"Portal"``.
|
|
1982
1668
|
"""
|
|
1983
|
-
|
|
1984
|
-
return Element("__Fragment__", {}, filtered, key=key)
|
|
1669
|
+
return Element("Portal", {}, list(children), key=key)
|
|
1985
1670
|
|
|
1986
1671
|
|
|
1987
1672
|
# ======================================================================
|
|
@@ -1992,23 +1677,33 @@ def Fragment(*children: Optional[Element], key: Optional[str] = None) -> Element
|
|
|
1992
1677
|
def ErrorBoundary(
|
|
1993
1678
|
*children: Element,
|
|
1994
1679
|
fallback: Optional[Any] = None,
|
|
1680
|
+
on_error: Optional[Callable[[BaseException], None]] = None,
|
|
1995
1681
|
key: Optional[str] = None,
|
|
1996
1682
|
) -> Element:
|
|
1997
1683
|
"""Catch render errors in the wrapped subtree and display ``fallback`` instead.
|
|
1998
1684
|
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
1685
|
+
When any descendant raises during render (initial mount, a parent
|
|
1686
|
+
re-render, or a local state-driven update), the failed subtree is
|
|
1687
|
+
torn down and ``fallback`` is mounted in its place. Without a
|
|
1688
|
+
boundary the error propagates to the screen host (which shows the
|
|
1689
|
+
dev error overlay in dev mode).
|
|
1690
|
+
|
|
1691
|
+
``fallback`` may be:
|
|
2003
1692
|
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
1693
|
+
- An [`Element`][pythonnative.Element], shown as-is.
|
|
1694
|
+
- ``fallback(error) -> Element``.
|
|
1695
|
+
- ``fallback(error, reset) -> Element``, where ``reset`` is a
|
|
1696
|
+
zero-arg callable that clears the error and remounts the
|
|
1697
|
+
original children (fresh state), for retry buttons.
|
|
2007
1698
|
|
|
2008
1699
|
Args:
|
|
2009
1700
|
*children: Subtree to wrap.
|
|
2010
|
-
fallback:
|
|
2011
|
-
|
|
1701
|
+
fallback: Fallback content (see above). Required for the
|
|
1702
|
+
boundary to actually catch; without it errors propagate
|
|
1703
|
+
to the next boundary up.
|
|
1704
|
+
on_error: Callback invoked with the exception when the
|
|
1705
|
+
boundary catches, before the fallback mounts. Use it for
|
|
1706
|
+
error reporting.
|
|
2012
1707
|
key: Stable identity for keyed reconciliation.
|
|
2013
1708
|
|
|
2014
1709
|
Returns:
|
|
@@ -2021,18 +1716,79 @@ def ErrorBoundary(
|
|
|
2021
1716
|
|
|
2022
1717
|
pn.ErrorBoundary(
|
|
2023
1718
|
MyRiskyComponent(),
|
|
2024
|
-
fallback=lambda err: pn.
|
|
1719
|
+
fallback=lambda err, reset: pn.Column(
|
|
1720
|
+
pn.Text(f"Error: {err}"),
|
|
1721
|
+
pn.Button("Retry", on_press=reset),
|
|
1722
|
+
),
|
|
1723
|
+
on_error=lambda err: log.exception(err),
|
|
2025
1724
|
)
|
|
2026
1725
|
```
|
|
2027
1726
|
"""
|
|
2028
1727
|
props: Dict[str, Any] = {}
|
|
2029
1728
|
if fallback is not None:
|
|
2030
1729
|
props["__fallback__"] = fallback
|
|
2031
|
-
if
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
1730
|
+
if on_error is not None:
|
|
1731
|
+
props["__on_error__"] = on_error
|
|
1732
|
+
return Element("__ErrorBoundary__", props, list(children), key=key)
|
|
1733
|
+
|
|
1734
|
+
|
|
1735
|
+
def Suspense(
|
|
1736
|
+
*children: Element,
|
|
1737
|
+
fallback: Optional[Any] = None,
|
|
1738
|
+
key: Optional[str] = None,
|
|
1739
|
+
) -> Element:
|
|
1740
|
+
"""Show ``fallback`` while descendants wait on async work, then swap in the content.
|
|
1741
|
+
|
|
1742
|
+
A Suspense boundary catches **suspensions** from the subtree it
|
|
1743
|
+
wraps: an ``async def`` component body blocking on a pending
|
|
1744
|
+
await, or a regular component calling
|
|
1745
|
+
[`Resource.read`][pythonnative.suspense.Resource.read] on data that hasn't
|
|
1746
|
+
arrived (see [`use_resource`][pythonnative.use_resource] and
|
|
1747
|
+
[`lazy`][pythonnative.lazy]). While anything is pending the
|
|
1748
|
+
boundary renders ``fallback``; when the awaited work completes it
|
|
1749
|
+
retries the content and swaps it in. Suspended components keep
|
|
1750
|
+
their hook state across retries, so cached resources aren't
|
|
1751
|
+
refetched.
|
|
1752
|
+
|
|
1753
|
+
Two timing behaviors, matching React:
|
|
1754
|
+
|
|
1755
|
+
- **Initial mount**: the fallback shows until the content is ready.
|
|
1756
|
+
- **Updates**: a component that's already on screen and suspends
|
|
1757
|
+
again (its dependencies changed) keeps its previous content
|
|
1758
|
+
visible and re-renders when ready; there's no fallback flash.
|
|
1759
|
+
|
|
1760
|
+
Args:
|
|
1761
|
+
*children: Subtree to wrap (the async content).
|
|
1762
|
+
fallback: Content shown while suspended: an
|
|
1763
|
+
[`Element`][pythonnative.Element] or a zero-arg callable
|
|
1764
|
+
returning one. Without it, suspensions propagate to the
|
|
1765
|
+
next Suspense boundary up.
|
|
1766
|
+
key: Stable identity for keyed reconciliation.
|
|
1767
|
+
|
|
1768
|
+
Returns:
|
|
1769
|
+
An [`Element`][pythonnative.Element] of type ``"__Suspense__"``.
|
|
1770
|
+
|
|
1771
|
+
Example:
|
|
1772
|
+
```python
|
|
1773
|
+
import pythonnative as pn
|
|
1774
|
+
|
|
1775
|
+
@pn.component
|
|
1776
|
+
async def Profile(user_id: str):
|
|
1777
|
+
user = await api.fetch_user(user_id)
|
|
1778
|
+
return pn.Text(user.name)
|
|
1779
|
+
|
|
1780
|
+
@pn.component
|
|
1781
|
+
def Screen():
|
|
1782
|
+
return pn.Suspense(
|
|
1783
|
+
Profile(user_id="42"),
|
|
1784
|
+
fallback=pn.ActivityIndicator(),
|
|
1785
|
+
)
|
|
1786
|
+
```
|
|
1787
|
+
"""
|
|
1788
|
+
props: Dict[str, Any] = {}
|
|
1789
|
+
if fallback is not None:
|
|
1790
|
+
props["__fallback__"] = fallback
|
|
1791
|
+
return Element("__Suspense__", props, list(children), key=key)
|
|
2036
1792
|
|
|
2037
1793
|
|
|
2038
1794
|
# ======================================================================
|
|
@@ -2082,7 +1838,7 @@ class _RowSpec:
|
|
|
2082
1838
|
|
|
2083
1839
|
def _dispatch_scroll_command(scroll_ref: Any, name: str, args: Dict[str, Any]) -> Any:
|
|
2084
1840
|
"""Send an imperative command to the ScrollView under ``scroll_ref``."""
|
|
2085
|
-
tag = scroll_ref
|
|
1841
|
+
tag = getattr(scroll_ref, "_pn_tag", None)
|
|
2086
1842
|
if tag is None:
|
|
2087
1843
|
return None
|
|
2088
1844
|
from .native_views import get_registry
|
|
@@ -2093,6 +1849,55 @@ def _dispatch_scroll_command(scroll_ref: Any, name: str, args: Dict[str, Any]) -
|
|
|
2093
1849
|
return None
|
|
2094
1850
|
|
|
2095
1851
|
|
|
1852
|
+
class ListController:
|
|
1853
|
+
"""Imperative scroll handle published on a list's ``ref``.
|
|
1854
|
+
|
|
1855
|
+
[`FlatList`][pythonnative.FlatList] and
|
|
1856
|
+
[`SectionList`][pythonnative.SectionList] install a
|
|
1857
|
+
``ListController`` on ``ref.current`` (via
|
|
1858
|
+
[`use_imperative_handle`][pythonnative.use_imperative_handle])
|
|
1859
|
+
after mount and clear it back to ``None`` on unmount.
|
|
1860
|
+
|
|
1861
|
+
Example:
|
|
1862
|
+
```python
|
|
1863
|
+
import pythonnative as pn
|
|
1864
|
+
|
|
1865
|
+
@pn.component
|
|
1866
|
+
def Chat(messages):
|
|
1867
|
+
list_ref = pn.use_ref()
|
|
1868
|
+
pn.use_layout_effect(
|
|
1869
|
+
lambda: list_ref.current and list_ref.current.scroll_to_end(animated=False),
|
|
1870
|
+
[len(messages)],
|
|
1871
|
+
)
|
|
1872
|
+
return pn.FlatList(data=messages, render_item=Bubble, ref=list_ref)
|
|
1873
|
+
```
|
|
1874
|
+
"""
|
|
1875
|
+
|
|
1876
|
+
__slots__ = ("_scroll_to_offset", "_scroll_to_index", "_scroll_to_end")
|
|
1877
|
+
|
|
1878
|
+
def __init__(
|
|
1879
|
+
self,
|
|
1880
|
+
scroll_to_offset: Callable[[float, bool], None],
|
|
1881
|
+
scroll_to_index: Callable[[int, bool], None],
|
|
1882
|
+
scroll_to_end: Callable[[bool], None],
|
|
1883
|
+
) -> None:
|
|
1884
|
+
self._scroll_to_offset = scroll_to_offset
|
|
1885
|
+
self._scroll_to_index = scroll_to_index
|
|
1886
|
+
self._scroll_to_end = scroll_to_end
|
|
1887
|
+
|
|
1888
|
+
def scroll_to_offset(self, offset: float, animated: bool = True) -> None:
|
|
1889
|
+
"""Scroll to an absolute content offset in points."""
|
|
1890
|
+
self._scroll_to_offset(offset, animated)
|
|
1891
|
+
|
|
1892
|
+
def scroll_to_index(self, index: int, animated: bool = True) -> None:
|
|
1893
|
+
"""Scroll so the row at ``index`` sits at the top of the viewport."""
|
|
1894
|
+
self._scroll_to_index(index, animated)
|
|
1895
|
+
|
|
1896
|
+
def scroll_to_end(self, animated: bool = True) -> None:
|
|
1897
|
+
"""Scroll to the end of the content."""
|
|
1898
|
+
self._scroll_to_end(animated)
|
|
1899
|
+
|
|
1900
|
+
|
|
2096
1901
|
@component
|
|
2097
1902
|
def _VirtualizedList(**p: Any) -> Element:
|
|
2098
1903
|
"""Shared windowing engine behind FlatList and SectionList."""
|
|
@@ -2104,18 +1909,18 @@ def _VirtualizedList(**p: Any) -> Element:
|
|
|
2104
1909
|
initial_extent: float = float(p.get("initial_window_extent") or 800.0)
|
|
2105
1910
|
|
|
2106
1911
|
window, set_window = use_state((0, -1))
|
|
2107
|
-
measured = use_ref({}) # row key -> measured extent (points)
|
|
2108
|
-
row_refs = use_ref({}) # row key ->
|
|
1912
|
+
measured: Ref[Dict[str, float]] = use_ref({}) # row key -> measured extent (points)
|
|
1913
|
+
row_refs: Ref[Dict[str, Ref]] = use_ref({}) # row key -> Ref for live rows
|
|
2109
1914
|
end_latch = use_ref({"fired_for": -1})
|
|
2110
|
-
viewable_ref = use_ref({"keys": ()})
|
|
1915
|
+
viewable_ref: Ref[Dict[str, Tuple[str, ...]]] = use_ref({"keys": ()})
|
|
2111
1916
|
scroll_pos = use_ref({"offset": 0.0})
|
|
2112
|
-
sv_ref = use_ref(None)
|
|
1917
|
+
sv_ref: Ref = use_ref(None)
|
|
2113
1918
|
|
|
2114
1919
|
# ------------------------------------------------------------------
|
|
2115
1920
|
# Extent model: measured > per-row hint > estimate. ``starts`` are
|
|
2116
1921
|
# prefix sums; ``starts[n]`` is the total content extent.
|
|
2117
1922
|
# ------------------------------------------------------------------
|
|
2118
|
-
measured_map: Dict[str, float] = measured
|
|
1923
|
+
measured_map: Dict[str, float] = measured.current
|
|
2119
1924
|
starts: List[float] = [0.0] * (n + 1)
|
|
2120
1925
|
acc = 0.0
|
|
2121
1926
|
for i, spec in enumerate(rows):
|
|
@@ -2128,7 +1933,7 @@ def _VirtualizedList(**p: Any) -> Element:
|
|
|
2128
1933
|
total_extent = acc
|
|
2129
1934
|
|
|
2130
1935
|
def _viewport_extent() -> float:
|
|
2131
|
-
frame = sv_ref.
|
|
1936
|
+
frame = sv_ref._pn_frame
|
|
2132
1937
|
if frame:
|
|
2133
1938
|
extent = frame[2] if horizontal else frame[3]
|
|
2134
1939
|
if extent and extent > 0:
|
|
@@ -2147,7 +1952,7 @@ def _VirtualizedList(**p: Any) -> Element:
|
|
|
2147
1952
|
|
|
2148
1953
|
first, last = window
|
|
2149
1954
|
if last < 0 or first >= n:
|
|
2150
|
-
first, last = _window_for(scroll_pos
|
|
1955
|
+
first, last = _window_for(scroll_pos.current["offset"], _viewport_extent())
|
|
2151
1956
|
last = min(last, n - 1)
|
|
2152
1957
|
first = max(0, min(first, max(0, n - 1)))
|
|
2153
1958
|
|
|
@@ -2163,8 +1968,8 @@ def _VirtualizedList(**p: Any) -> Element:
|
|
|
2163
1968
|
user_on_scroll = p.get("on_scroll")
|
|
2164
1969
|
|
|
2165
1970
|
def _sweep_measured() -> None:
|
|
2166
|
-
for row_key,
|
|
2167
|
-
frame =
|
|
1971
|
+
for row_key, row_ref in row_refs.current.items():
|
|
1972
|
+
frame = getattr(row_ref, "_pn_frame", None)
|
|
2168
1973
|
if frame:
|
|
2169
1974
|
extent = frame[2] if horizontal else frame[3]
|
|
2170
1975
|
if extent and extent > 0:
|
|
@@ -2175,7 +1980,7 @@ def _VirtualizedList(**p: Any) -> Element:
|
|
|
2175
1980
|
offset = float(payload.get("x" if horizontal else "y", 0.0) or 0.0)
|
|
2176
1981
|
else:
|
|
2177
1982
|
offset = float(payload or 0.0)
|
|
2178
|
-
scroll_pos
|
|
1983
|
+
scroll_pos.current["offset"] = offset
|
|
2179
1984
|
_sweep_measured()
|
|
2180
1985
|
viewport = _viewport_extent()
|
|
2181
1986
|
|
|
@@ -2186,18 +1991,18 @@ def _VirtualizedList(**p: Any) -> Element:
|
|
|
2186
1991
|
if on_end_reached is not None and total_extent > 0:
|
|
2187
1992
|
remaining = total_extent - (offset + viewport)
|
|
2188
1993
|
if remaining <= end_threshold * viewport:
|
|
2189
|
-
if end_latch
|
|
2190
|
-
end_latch
|
|
1994
|
+
if end_latch.current["fired_for"] != n:
|
|
1995
|
+
end_latch.current["fired_for"] = n
|
|
2191
1996
|
on_end_reached()
|
|
2192
1997
|
elif remaining > end_threshold * viewport + viewport:
|
|
2193
|
-
end_latch
|
|
1998
|
+
end_latch.current["fired_for"] = -1
|
|
2194
1999
|
|
|
2195
2000
|
if on_viewable is not None and n > 0:
|
|
2196
2001
|
v_first = max(0, bisect.bisect_right(starts, offset, 0, n) - 1)
|
|
2197
2002
|
v_last = min(n - 1, bisect.bisect_left(starts, offset + viewport, 0, n))
|
|
2198
2003
|
keys = tuple(rows[i].key for i in range(v_first, v_last + 1))
|
|
2199
|
-
if keys != viewable_ref
|
|
2200
|
-
viewable_ref
|
|
2004
|
+
if keys != viewable_ref.current["keys"]:
|
|
2005
|
+
viewable_ref.current["keys"] = keys
|
|
2201
2006
|
on_viewable(
|
|
2202
2007
|
[
|
|
2203
2008
|
{"index": rows[i].index, "key": rows[i].key, "item": rows[i].item}
|
|
@@ -2209,33 +2014,26 @@ def _VirtualizedList(**p: Any) -> Element:
|
|
|
2209
2014
|
user_on_scroll(payload)
|
|
2210
2015
|
|
|
2211
2016
|
# ------------------------------------------------------------------
|
|
2212
|
-
# Imperative controller (scroll_to_index / offset / end)
|
|
2213
|
-
# the user's ref
|
|
2214
|
-
# fresh extents
|
|
2215
|
-
# hook order stable.
|
|
2017
|
+
# Imperative controller (scroll_to_index / offset / end) published
|
|
2018
|
+
# on the user's ref. Rebuilt every render (deps=None) so the
|
|
2019
|
+
# closures see fresh extents.
|
|
2216
2020
|
# ------------------------------------------------------------------
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
if not isinstance(controller, dict):
|
|
2221
|
-
return
|
|
2222
|
-
|
|
2223
|
-
def scroll_to_offset(offset: float, animated: bool = True) -> None:
|
|
2224
|
-
axis = "x" if horizontal else "y"
|
|
2225
|
-
_dispatch_scroll_command(sv_ref, "scroll_to_offset", {axis: float(offset), "animated": animated})
|
|
2021
|
+
def _scroll_to_offset(offset: float, animated: bool = True) -> None:
|
|
2022
|
+
axis = "x" if horizontal else "y"
|
|
2023
|
+
_dispatch_scroll_command(sv_ref, "scroll_to_offset", {axis: float(offset), "animated": animated})
|
|
2226
2024
|
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2025
|
+
def _scroll_to_index(index: int, animated: bool = True) -> None:
|
|
2026
|
+
idx = max(0, min(int(index), n - 1)) if n else 0
|
|
2027
|
+
_scroll_to_offset(starts[idx], animated)
|
|
2230
2028
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2029
|
+
def _scroll_to_end(animated: bool = True) -> None:
|
|
2030
|
+
_scroll_to_offset(max(0.0, total_extent - _viewport_extent()), animated)
|
|
2233
2031
|
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2032
|
+
use_imperative_handle(
|
|
2033
|
+
p.get("controller_ref"),
|
|
2034
|
+
lambda: ListController(_scroll_to_offset, _scroll_to_index, _scroll_to_end),
|
|
2035
|
+
None,
|
|
2036
|
+
)
|
|
2239
2037
|
|
|
2240
2038
|
# ------------------------------------------------------------------
|
|
2241
2039
|
# Children: header, leading spacer, windowed rows, trailing spacer,
|
|
@@ -2254,17 +2052,17 @@ def _VirtualizedList(**p: Any) -> Element:
|
|
|
2254
2052
|
if empty is not None:
|
|
2255
2053
|
children.append(View(empty, key="__pn_empty__"))
|
|
2256
2054
|
else:
|
|
2257
|
-
live_refs: Dict[str,
|
|
2055
|
+
live_refs: Dict[str, Ref] = {}
|
|
2258
2056
|
lead = starts[first]
|
|
2259
2057
|
if lead > 0:
|
|
2260
2058
|
lead_style: Dict[str, Any] = {spacer_key: lead}
|
|
2261
2059
|
children.append(View(style=lead_style, key="__pn_lead__"))
|
|
2262
2060
|
for i in range(first, last + 1):
|
|
2263
2061
|
spec = rows[i]
|
|
2264
|
-
row_ref = row_refs
|
|
2062
|
+
row_ref = row_refs.current.get(spec.key) or Ref()
|
|
2265
2063
|
live_refs[spec.key] = row_ref
|
|
2266
2064
|
children.append(View(spec.make(), ref=row_ref, key=spec.key))
|
|
2267
|
-
row_refs
|
|
2065
|
+
row_refs.current = live_refs
|
|
2268
2066
|
trail = total_extent - starts[last + 1]
|
|
2269
2067
|
if trail > 0:
|
|
2270
2068
|
trail_style: Dict[str, Any] = {spacer_key: trail}
|
|
@@ -2318,9 +2116,9 @@ def _NativeList(**p: Any) -> Element:
|
|
|
2318
2116
|
heights: List[float] = [float(spec.extent or 0.0) for spec in rows]
|
|
2319
2117
|
uniform = len(set(heights)) <= 1
|
|
2320
2118
|
|
|
2321
|
-
internal_ref = use_ref(None)
|
|
2119
|
+
internal_ref: Ref = use_ref(None)
|
|
2322
2120
|
end_latch = use_ref({"fired_for": -1})
|
|
2323
|
-
viewable_ref = use_ref({"keys": ()})
|
|
2121
|
+
viewable_ref: Ref[Dict[str, Tuple[str, ...]]] = use_ref({"keys": ()})
|
|
2324
2122
|
|
|
2325
2123
|
starts: List[float] = [0.0] * (n + 1)
|
|
2326
2124
|
acc = 0.0
|
|
@@ -2349,18 +2147,18 @@ def _NativeList(**p: Any) -> Element:
|
|
|
2349
2147
|
if on_end_reached is not None and total_extent > 0:
|
|
2350
2148
|
remaining = total_extent - (offset + viewport)
|
|
2351
2149
|
if remaining <= end_threshold * viewport:
|
|
2352
|
-
if end_latch
|
|
2353
|
-
end_latch
|
|
2150
|
+
if end_latch.current["fired_for"] != n:
|
|
2151
|
+
end_latch.current["fired_for"] = n
|
|
2354
2152
|
on_end_reached()
|
|
2355
2153
|
elif remaining > end_threshold * viewport + viewport:
|
|
2356
|
-
end_latch
|
|
2154
|
+
end_latch.current["fired_for"] = -1
|
|
2357
2155
|
|
|
2358
2156
|
if on_viewable is not None and n > 0:
|
|
2359
2157
|
v_first = max(0, bisect.bisect_right(starts, offset, 0, n) - 1)
|
|
2360
2158
|
v_last = min(n - 1, bisect.bisect_left(starts, offset + viewport, 0, n))
|
|
2361
2159
|
keys = tuple(rows[i].key for i in range(v_first, v_last + 1))
|
|
2362
|
-
if keys != viewable_ref
|
|
2363
|
-
viewable_ref
|
|
2160
|
+
if keys != viewable_ref.current["keys"]:
|
|
2161
|
+
viewable_ref.current["keys"] = keys
|
|
2364
2162
|
on_viewable(
|
|
2365
2163
|
[
|
|
2366
2164
|
{"index": rows[i].index, "key": rows[i].key, "item": rows[i].item}
|
|
@@ -2371,26 +2169,20 @@ def _NativeList(**p: Any) -> Element:
|
|
|
2371
2169
|
if user_on_scroll is not None:
|
|
2372
2170
|
user_on_scroll({"x": 0.0, "y": offset})
|
|
2373
2171
|
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
def _attach_controller() -> None:
|
|
2377
|
-
if not isinstance(controller, dict):
|
|
2378
|
-
return
|
|
2172
|
+
def _scroll_to_offset(offset: float, animated: bool = True) -> None:
|
|
2173
|
+
_dispatch_scroll_command(internal_ref, "scroll_to_offset", {"y": float(offset), "animated": animated})
|
|
2379
2174
|
|
|
2380
|
-
|
|
2381
|
-
|
|
2175
|
+
def _scroll_to_index(index: int, animated: bool = True) -> None:
|
|
2176
|
+
_dispatch_scroll_command(internal_ref, "scroll_to_index", {"index": int(index), "animated": animated})
|
|
2382
2177
|
|
|
2383
|
-
|
|
2384
|
-
|
|
2178
|
+
def _scroll_to_end(animated: bool = True) -> None:
|
|
2179
|
+
_dispatch_scroll_command(internal_ref, "scroll_to_end", {"animated": animated})
|
|
2385
2180
|
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
controller["scroll_to_end"] = scroll_to_end
|
|
2392
|
-
|
|
2393
|
-
use_effect(_attach_controller, None)
|
|
2181
|
+
use_imperative_handle(
|
|
2182
|
+
p.get("controller_ref"),
|
|
2183
|
+
lambda: ListController(_scroll_to_offset, _scroll_to_index, _scroll_to_end),
|
|
2184
|
+
None,
|
|
2185
|
+
)
|
|
2394
2186
|
|
|
2395
2187
|
props: Dict[str, Any] = dict(p.get("list_style") or {})
|
|
2396
2188
|
props["count"] = n
|
|
@@ -2434,7 +2226,7 @@ def FlatList(
|
|
|
2434
2226
|
shows_scroll_indicator: bool = True,
|
|
2435
2227
|
content_container_style: StyleProp = None,
|
|
2436
2228
|
style: StyleProp = None,
|
|
2437
|
-
ref: Optional[
|
|
2229
|
+
ref: Optional[Ref] = None,
|
|
2438
2230
|
key: Optional[str] = None,
|
|
2439
2231
|
) -> Element:
|
|
2440
2232
|
"""Virtualized scrollable list that renders items from ``data`` lazily.
|
|
@@ -2447,10 +2239,12 @@ def FlatList(
|
|
|
2447
2239
|
unknown rows start at ``estimated_item_height`` and are corrected
|
|
2448
2240
|
with their measured extent once they've been on screen.
|
|
2449
2241
|
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2242
|
+
Pass a [`Ref`][pythonnative.Ref] (from
|
|
2243
|
+
[`use_ref`][pythonnative.use_ref]) to receive a
|
|
2244
|
+
[`ListController`][pythonnative.ListController] on ``ref.current``:
|
|
2245
|
+
``ref.current.scroll_to_index(i)``,
|
|
2246
|
+
``ref.current.scroll_to_offset(pts)``, and
|
|
2247
|
+
``ref.current.scroll_to_end()``.
|
|
2454
2248
|
|
|
2455
2249
|
Args:
|
|
2456
2250
|
data: List of arbitrary item values.
|
|
@@ -2485,8 +2279,9 @@ def FlatList(
|
|
|
2485
2279
|
content_container_style: Style applied to the inner content
|
|
2486
2280
|
wrapper.
|
|
2487
2281
|
style: Style for the outer scroll container.
|
|
2488
|
-
ref: Optional
|
|
2489
|
-
|
|
2282
|
+
ref: Optional [`Ref`][pythonnative.Ref]; receives a
|
|
2283
|
+
[`ListController`][pythonnative.ListController] on
|
|
2284
|
+
``ref.current`` after mount.
|
|
2490
2285
|
key: Stable identity for keyed reconciliation of the list.
|
|
2491
2286
|
|
|
2492
2287
|
Returns:
|
|
@@ -2630,7 +2425,7 @@ def SectionList(
|
|
|
2630
2425
|
on_end_reached_threshold: float = 0.5,
|
|
2631
2426
|
on_scroll: Optional[Callable[[Dict[str, float]], None]] = None,
|
|
2632
2427
|
style: StyleProp = None,
|
|
2633
|
-
ref: Optional[
|
|
2428
|
+
ref: Optional[Ref] = None,
|
|
2634
2429
|
key: Optional[str] = None,
|
|
2635
2430
|
) -> Element:
|
|
2636
2431
|
"""Virtualized list with section headers interleaved between row groups.
|
|
@@ -2664,8 +2459,9 @@ def SectionList(
|
|
|
2664
2459
|
multiples, at which ``on_end_reached`` fires.
|
|
2665
2460
|
on_scroll: Called with the raw scroll payload.
|
|
2666
2461
|
style: Style for the outer scroll container.
|
|
2667
|
-
ref: Optional
|
|
2668
|
-
|
|
2462
|
+
ref: Optional [`Ref`][pythonnative.Ref]; receives a
|
|
2463
|
+
[`ListController`][pythonnative.ListController] on
|
|
2464
|
+
``ref.current`` after mount.
|
|
2669
2465
|
key: Stable identity for keyed reconciliation of the list.
|
|
2670
2466
|
|
|
2671
2467
|
Returns:
|
|
@@ -2945,10 +2741,10 @@ def Picker(
|
|
|
2945
2741
|
accessibility_label: Optional[str] = None,
|
|
2946
2742
|
accessibility_hint: Optional[str] = None,
|
|
2947
2743
|
accessible: Optional[bool] = None,
|
|
2948
|
-
accessibility_state: Optional[
|
|
2744
|
+
accessibility_state: Optional[AccessibilityState] = None,
|
|
2949
2745
|
accessibility_live_region: Optional[Literal["none", "polite", "assertive"]] = None,
|
|
2950
2746
|
test_id: Optional[str] = None,
|
|
2951
|
-
ref: Optional[
|
|
2747
|
+
ref: Optional[Ref] = None,
|
|
2952
2748
|
key: Optional[str] = None,
|
|
2953
2749
|
) -> Element:
|
|
2954
2750
|
"""A real native dropdown / select widget.
|
|
@@ -2981,7 +2777,7 @@ def Picker(
|
|
|
2981
2777
|
test_id: Stable identifier for UI tests; exposed as
|
|
2982
2778
|
``resource-id`` on Android and ``accessibilityIdentifier``
|
|
2983
2779
|
on iOS.
|
|
2984
|
-
ref: Optional ``use_ref()
|
|
2780
|
+
ref: Optional [`Ref`][pythonnative.Ref] from ``use_ref()``.
|
|
2985
2781
|
key: Stable identity for keyed reconciliation.
|
|
2986
2782
|
|
|
2987
2783
|
Returns:
|