pythonnative 0.14.0__py3-none-any.whl → 0.16.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.
@@ -1,79 +1,310 @@
1
- """Built-in element factories for declarative UI composition.
1
+ """Built-in element factories and the typed prop schemas they share.
2
2
 
3
- Each function in this module returns an [`Element`][pythonnative.Element]
4
- describing a native UI widget. Element factories are pure data: no
5
- native views are created until the reconciler mounts the element tree.
3
+ Each ``@dataclass(frozen=True)`` class in this module ``TextProps``,
4
+ ``ButtonProps``, etc. is the canonical schema for one built-in
5
+ component. Each factory function (``Text``, ``Button``, …) is a thin
6
+ ergonomic wrapper that builds an [`Element`][pythonnative.Element]
7
+ through the shared :func:`_make_element` helper, so style resolution,
8
+ ``ref`` attachment, ``None``-default dropping, and forced overrides
9
+ (e.g. ``Column``'s fixed ``flex_direction``) live in exactly one place.
6
10
 
7
- All visual and layout properties are passed via the `style` parameter,
8
- which accepts a dict or a list of dicts (later entries override
9
- earlier ones; see [`resolve_style`][pythonnative.style.resolve_style]).
10
-
11
- Layout properties supported by every component:
12
-
13
- - `width`, `height`, `flex`, `flex_grow`, `flex_shrink`, `margin`,
14
- `min_width`, `max_width`, `min_height`, `max_height`, `align_self`.
15
-
16
- Flex container properties (`View` / `Column` / `Row`):
17
-
18
- - `flex_direction`, `justify_content`, `align_items`, `overflow`,
19
- `spacing`, `padding`.
20
-
21
- [`View`][pythonnative.View] is the universal flex container (like React
22
- Native's `View`). It defaults to `flex_direction: "column"`.
23
- [`Column`][pythonnative.Column] and [`Row`][pythonnative.Row] are
24
- convenience wrappers that fix the direction.
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.
25
14
 
26
15
  Example:
27
16
  ```python
28
17
  import pythonnative as pn
29
18
 
30
19
  pn.Column(
31
- pn.Text("Hello", style={"font_size": 18}),
20
+ pn.Text("Hello", style=pn.style(font_size=18)),
32
21
  pn.Button("Tap", on_click=lambda: print("tapped")),
33
- style={"spacing": 12, "padding": 16},
22
+ style=pn.style(spacing=12, padding=16),
34
23
  )
35
24
  ```
36
25
  """
37
26
 
38
- from typing import Any, Callable, Dict, List, Optional
27
+ from dataclasses import dataclass, field
28
+ from typing import Any, Callable, Dict, List, Literal, Optional
39
29
 
40
30
  from .element import Element
41
- from .style import StyleValue, resolve_style
31
+ from .sdk import Props
32
+ from .style import (
33
+ AutoCapitalize,
34
+ Color,
35
+ KeyboardType,
36
+ ReturnKeyType,
37
+ ScaleType,
38
+ StyleProp,
39
+ resolve_style,
40
+ )
42
41
 
43
42
  # ======================================================================
44
- # Leaf components
43
+ # Canonical element builder
45
44
  # ======================================================================
46
45
 
47
46
 
48
- def _accessibility_props(
49
- accessibility_label: Optional[str],
50
- accessibility_hint: Optional[str],
51
- accessibility_role: Optional[str],
52
- accessible: Optional[bool],
53
- ) -> Dict[str, Any]:
54
- """Collect the four accessibility prop keys into a dict.
47
+ def _make_element(
48
+ name: str,
49
+ *children: Element,
50
+ style: StyleProp = None,
51
+ ref: Optional[Dict[str, Any]] = None,
52
+ key: Optional[str] = None,
53
+ _defaults: Optional[Dict[str, Any]] = None,
54
+ _forced: Optional[Dict[str, Any]] = None,
55
+ **props: Any,
56
+ ) -> Element:
57
+ """Build an [`Element`][pythonnative.Element] of type ``name``.
58
+
59
+ This is the single helper every built-in factory routes through, so
60
+ the cross-cutting concerns that used to be duplicated per component
61
+ live in one place:
62
+
63
+ 1. ``style`` is flattened via
64
+ [`resolve_style`][pythonnative.style.resolve_style] (list-of-dicts
65
+ and ``None`` both handled).
66
+ 2. ``_defaults`` are filled in for keys not already present (used for
67
+ things like ``View``'s default ``flex_direction: "column"`` that
68
+ a user style may legitimately override).
69
+ 3. ``**props`` are merged on top, with ``None`` values *dropped* so
70
+ optional kwargs don't pollute the prop dict.
71
+ 4. ``ref`` is attached under the reserved ``"ref"`` key.
72
+ 5. ``_forced`` overrides everything (used by ``Column`` / ``Row`` to
73
+ lock their flex direction regardless of user style).
74
+
75
+ Args:
76
+ name: Element type name (e.g. ``"Text"``).
77
+ *children: Child elements.
78
+ style: Style dict, list of dicts, or ``None``.
79
+ ref: Optional ``use_ref()`` dict; the reconciler populates
80
+ ``ref["current"]`` with the underlying native view.
81
+ key: Stable identity for keyed reconciliation.
82
+ _defaults: Internal: fill-only-if-missing prop defaults.
83
+ _forced: Internal: prop overrides applied last.
84
+ **props: Per-component props. ``None`` values are dropped.
85
+
86
+ Returns:
87
+ A fresh [`Element`][pythonnative.Element].
88
+ """
89
+ out: Dict[str, Any] = dict(resolve_style(style))
90
+ if _defaults:
91
+ for k, v in _defaults.items():
92
+ out.setdefault(k, v)
93
+ for k, v in props.items():
94
+ if v is not None:
95
+ out[k] = v
96
+ if ref is not None:
97
+ out["ref"] = ref
98
+ if _forced:
99
+ out.update(_forced)
100
+ return Element(name, out, list(children), key=key)
101
+
102
+
103
+ # ======================================================================
104
+ # Props dataclasses
105
+ # ======================================================================
106
+ #
107
+ # These are the canonical schemas for every built-in component. They
108
+ # subclass the SDK's ``Props`` base, so the same shape works for both
109
+ # the built-in factory functions and the third-party
110
+ # [`element_factory`][pythonnative.element_factory] API.
111
+
112
+
113
+ @dataclass(frozen=True)
114
+ class TextProps(Props):
115
+ """Props for [`Text`][pythonnative.Text]."""
116
+
117
+ text: str = ""
118
+ accessibility_label: Optional[str] = None
119
+ accessibility_hint: Optional[str] = None
120
+ accessibility_role: Optional[str] = None
121
+ accessible: Optional[bool] = None
122
+
123
+
124
+ @dataclass(frozen=True)
125
+ class ButtonProps(Props):
126
+ """Props for [`Button`][pythonnative.Button]."""
127
+
128
+ title: str = ""
129
+ on_click: Optional[Callable[[], None]] = None
130
+ enabled: bool = True
131
+ accessibility_label: Optional[str] = None
132
+ accessibility_hint: Optional[str] = None
133
+ accessibility_role: Optional[str] = None
134
+ accessible: Optional[bool] = None
135
+
136
+
137
+ @dataclass(frozen=True)
138
+ class TextInputProps(Props):
139
+ """Props for [`TextInput`][pythonnative.TextInput]."""
140
+
141
+ value: str = ""
142
+ placeholder: Optional[str] = None
143
+ on_change: Optional[Callable[[str], None]] = None
144
+ on_submit: Optional[Callable[[str], None]] = None
145
+ secure: bool = False
146
+ multiline: bool = False
147
+ keyboard_type: Optional[KeyboardType] = None
148
+ auto_capitalize: Optional[AutoCapitalize] = None
149
+ auto_correct: Optional[bool] = None
150
+ auto_focus: bool = False
151
+ return_key_type: Optional[ReturnKeyType] = None
152
+ max_length: Optional[int] = None
153
+ placeholder_color: Optional[Color] = None
154
+ accessibility_label: Optional[str] = None
155
+ accessibility_hint: Optional[str] = None
156
+ accessible: Optional[bool] = None
157
+
158
+
159
+ @dataclass(frozen=True)
160
+ class ImageProps(Props):
161
+ """Props for [`Image`][pythonnative.Image]."""
162
+
163
+ source: Optional[str] = None
164
+ scale_type: Optional[ScaleType] = None
165
+ tint_color: Optional[Color] = None
166
+ accessibility_label: Optional[str] = None
167
+ accessibility_role: Optional[str] = None
168
+ accessible: Optional[bool] = None
169
+
170
+
171
+ @dataclass(frozen=True)
172
+ class SwitchProps(Props):
173
+ """Props for [`Switch`][pythonnative.Switch]."""
174
+
175
+ value: bool = False
176
+ on_change: Optional[Callable[[bool], None]] = None
177
+
178
+
179
+ @dataclass(frozen=True)
180
+ class ProgressBarProps(Props):
181
+ """Props for [`ProgressBar`][pythonnative.ProgressBar]."""
182
+
183
+ value: float = 0.0
184
+
185
+
186
+ @dataclass(frozen=True)
187
+ class ActivityIndicatorProps(Props):
188
+ """Props for [`ActivityIndicator`][pythonnative.ActivityIndicator]."""
189
+
190
+ animating: bool = True
191
+
192
+
193
+ @dataclass(frozen=True)
194
+ class WebViewProps(Props):
195
+ """Props for [`WebView`][pythonnative.WebView]."""
196
+
197
+ url: Optional[str] = None
198
+
199
+
200
+ @dataclass(frozen=True)
201
+ class SpacerProps(Props):
202
+ """Props for [`Spacer`][pythonnative.Spacer]."""
55
203
 
56
- Internal helper kept here so every component factory can expose
57
- the same four kwargs without repeating the ``if x is not None``
58
- plumbing. Returns an empty dict when no accessibility values are
59
- supplied so we don't bloat element props.
204
+ size: Optional[float] = None
205
+ flex: Optional[float] = None
206
+
207
+
208
+ @dataclass(frozen=True)
209
+ class SliderProps(Props):
210
+ """Props for [`Slider`][pythonnative.Slider]."""
211
+
212
+ value: float = 0.0
213
+ min_value: float = 0.0
214
+ max_value: float = 1.0
215
+ on_change: Optional[Callable[[float], None]] = None
216
+
217
+
218
+ @dataclass(frozen=True)
219
+ class ViewProps(Props):
220
+ """Props for [`View`][pythonnative.View], [`Column`][pythonnative.Column], and [`Row`][pythonnative.Row]."""
221
+
222
+ accessibility_label: Optional[str] = None
223
+ accessibility_hint: Optional[str] = None
224
+ accessibility_role: Optional[str] = None
225
+ accessible: Optional[bool] = None
226
+
227
+
228
+ @dataclass(frozen=True)
229
+ class ScrollViewProps(Props):
230
+ """Props for [`ScrollView`][pythonnative.ScrollView]."""
231
+
232
+ refresh_control: Optional[Dict[str, Any]] = None
233
+ scroll_axis: Optional[Literal["vertical", "horizontal"]] = None
234
+
235
+
236
+ @dataclass(frozen=True)
237
+ class SafeAreaViewProps(Props):
238
+ """Props for [`SafeAreaView`][pythonnative.SafeAreaView]."""
239
+
240
+
241
+ @dataclass(frozen=True)
242
+ class ModalProps(Props):
243
+ """Props for [`Modal`][pythonnative.Modal]."""
244
+
245
+ visible: bool = False
246
+ on_dismiss: Optional[Callable[[], None]] = None
247
+ title: Optional[str] = None
248
+ animation_type: Literal["slide", "fade", "none"] = "slide"
249
+ transparent: bool = False
250
+
251
+
252
+ @dataclass(frozen=True)
253
+ class PressableProps(Props):
254
+ """Props for [`Pressable`][pythonnative.Pressable]."""
255
+
256
+ on_press: Optional[Callable[[], None]] = None
257
+ on_long_press: Optional[Callable[[], None]] = None
258
+ pressed_opacity: float = 0.6
259
+ accessibility_label: Optional[str] = None
260
+ accessibility_hint: Optional[str] = None
261
+ accessibility_role: Optional[str] = None
262
+ accessible: Optional[bool] = None
263
+
264
+
265
+ @dataclass(frozen=True)
266
+ class StatusBarProps(Props):
267
+ """Props for [`StatusBar`][pythonnative.StatusBar]."""
268
+
269
+ bar_style: Optional[Literal["light", "dark", "default"]] = None
270
+ background_color: Optional[Color] = None
271
+ hidden: Optional[bool] = None
272
+
273
+
274
+ @dataclass(frozen=True)
275
+ class KeyboardAvoidingViewProps(Props):
276
+ """Props for [`KeyboardAvoidingView`][pythonnative.KeyboardAvoidingView]."""
277
+
278
+ behavior: Literal["padding", "position"] = "padding"
279
+
280
+
281
+ @dataclass(frozen=True)
282
+ class PickerProps(Props):
283
+ """Props for [`Picker`][pythonnative.Picker].
284
+
285
+ ``items`` is an ordered list of ``{"value": Any, "label": str}``
286
+ entries. ``value`` is matched against ``items[i]["value"]`` to
287
+ determine the currently selected row.
60
288
  """
61
- out: Dict[str, Any] = {}
62
- if accessibility_label is not None:
63
- out["accessibility_label"] = accessibility_label
64
- if accessibility_hint is not None:
65
- out["accessibility_hint"] = accessibility_hint
66
- if accessibility_role is not None:
67
- out["accessibility_role"] = accessibility_role
68
- if accessible is not None:
69
- out["accessible"] = accessible
70
- return out
289
+
290
+ value: Any = None
291
+ items: List[Dict[str, Any]] = field(default_factory=list)
292
+ on_change: Optional[Callable[[Any], None]] = None
293
+ placeholder: str = "Select…"
294
+ accessibility_label: Optional[str] = None
295
+ accessibility_hint: Optional[str] = None
296
+ accessible: Optional[bool] = None
297
+
298
+
299
+ # ======================================================================
300
+ # Leaf factories
301
+ # ======================================================================
71
302
 
72
303
 
73
304
  def Text(
74
305
  text: str = "",
75
306
  *,
76
- style: StyleValue = None,
307
+ style: StyleProp = None,
77
308
  accessibility_label: Optional[str] = None,
78
309
  accessibility_hint: Optional[str] = None,
79
310
  accessibility_role: Optional[str] = None,
@@ -83,34 +314,38 @@ def Text(
83
314
  ) -> Element:
84
315
  """Display a string of text.
85
316
 
86
- Style properties: `font_size`, `color`, `bold`, `font_weight`,
87
- `font_family`, `italic`, `text_align`, `background_color`,
88
- `max_lines`, `letter_spacing`, `line_height`, `text_decoration`
89
- (`"underline"` / `"line_through"`), `border_radius`,
90
- `border_width`, `border_color`, `shadow_*`, `opacity`,
91
- `transform`, plus the common layout props.
317
+ Style properties: ``font_size``, ``color``, ``bold``,
318
+ ``font_weight``, ``font_family``, ``italic``, ``text_align``,
319
+ ``background_color``, ``max_lines``, ``letter_spacing``,
320
+ ``line_height``, ``text_decoration`` (``"underline"`` /
321
+ ``"line_through"``), ``border_radius``, ``border_width``,
322
+ ``border_color``, ``shadow_*``, ``opacity``, ``transform``, plus
323
+ the common layout props.
92
324
 
93
325
  Args:
94
326
  text: Text content to display.
95
- style: Style dict (or list of dicts) controlling appearance and
96
- layout.
327
+ style: Style dict (or list of dicts).
97
328
  accessibility_label: Spoken description for screen readers.
98
329
  accessibility_hint: Spoken extra detail (iOS only).
99
330
  accessibility_role: Semantic role for assistive tech.
100
331
  accessible: Override whether the element is exposed to AT.
101
- ref: Optional ``use_ref()`` dict; the reconciler populates
102
- ``ref["current"]`` with the underlying native view.
103
- key: Stable identity for keyed reconciliation in lists.
332
+ ref: Optional ``use_ref()`` dict.
333
+ key: Stable identity for keyed reconciliation.
104
334
 
105
335
  Returns:
106
- An [`Element`][pythonnative.Element] of type `"Text"`.
336
+ An [`Element`][pythonnative.Element] of type ``"Text"``.
107
337
  """
108
- props: Dict[str, Any] = {"text": text}
109
- props.update(resolve_style(style))
110
- props.update(_accessibility_props(accessibility_label, accessibility_hint, accessibility_role, accessible))
111
- if ref is not None:
112
- props["ref"] = ref
113
- return Element("Text", props, [], key=key)
338
+ return _make_element(
339
+ "Text",
340
+ style=style,
341
+ ref=ref,
342
+ key=key,
343
+ text=text,
344
+ accessibility_label=accessibility_label,
345
+ accessibility_hint=accessibility_hint,
346
+ accessibility_role=accessibility_role,
347
+ accessible=accessible,
348
+ )
114
349
 
115
350
 
116
351
  def Button(
@@ -118,100 +353,99 @@ def Button(
118
353
  *,
119
354
  on_click: Optional[Callable[[], None]] = None,
120
355
  enabled: bool = True,
121
- style: StyleValue = None,
356
+ style: StyleProp = None,
122
357
  accessibility_label: Optional[str] = None,
123
358
  accessibility_hint: Optional[str] = None,
359
+ accessibility_role: Optional[str] = None,
124
360
  accessible: Optional[bool] = None,
125
361
  ref: Optional[Dict[str, Any]] = None,
126
362
  key: Optional[str] = None,
127
363
  ) -> Element:
128
364
  """Display a tappable button.
129
365
 
130
- Style properties: `color`, `background_color`, `font_size`,
131
- `border_radius`, `border_width`, `border_color`, `shadow_*`,
132
- `opacity`, `transform`, plus the common layout props.
366
+ Style properties: ``color``, ``background_color``, ``font_size``,
367
+ ``border_radius``, ``border_width``, ``border_color``, ``shadow_*``,
368
+ ``opacity``, ``transform``, plus the common layout props.
369
+
370
+ Buttons get ``accessibility_role="button"`` by default.
133
371
 
134
372
  Args:
135
373
  title: Button label.
136
374
  on_click: Callback invoked when the user taps the button.
137
- enabled: When `False`, the button is disabled and cannot be
375
+ enabled: When ``False``, the button is disabled and cannot be
138
376
  tapped.
139
377
  style: Style dict (or list of dicts).
140
378
  accessibility_label: Spoken description for screen readers.
141
379
  accessibility_hint: Spoken extra detail (iOS only).
380
+ accessibility_role: Override the default ``"button"`` role.
142
381
  accessible: Override whether the element is exposed to AT.
143
- ref: Optional ``use_ref()`` dict; the reconciler populates
144
- ``ref["current"]`` with the underlying native view.
382
+ ref: Optional ``use_ref()`` dict.
145
383
  key: Stable identity for keyed reconciliation.
146
384
 
147
385
  Returns:
148
- An [`Element`][pythonnative.Element] of type `"Button"`.
386
+ An [`Element`][pythonnative.Element] of type ``"Button"``.
149
387
  """
150
- props: Dict[str, Any] = {"title": title}
151
- if on_click is not None:
152
- props["on_click"] = on_click
153
- if not enabled:
154
- props["enabled"] = False
155
- props.update(resolve_style(style))
156
- # Buttons get accessibility_role="button" by default.
157
- if accessibility_label is not None:
158
- props["accessibility_label"] = accessibility_label
159
- if accessibility_hint is not None:
160
- props["accessibility_hint"] = accessibility_hint
161
- if accessible is not None:
162
- props["accessible"] = accessible
163
- props.setdefault("accessibility_role", "button")
164
- if ref is not None:
165
- props["ref"] = ref
166
- return Element("Button", props, [], key=key)
388
+ return _make_element(
389
+ "Button",
390
+ style=style,
391
+ ref=ref,
392
+ key=key,
393
+ title=title,
394
+ on_click=on_click,
395
+ enabled=enabled,
396
+ accessibility_label=accessibility_label,
397
+ accessibility_hint=accessibility_hint,
398
+ accessibility_role=accessibility_role,
399
+ accessible=accessible,
400
+ _defaults={"accessibility_role": "button"},
401
+ )
167
402
 
168
403
 
169
404
  def TextInput(
170
405
  *,
171
406
  value: str = "",
172
- placeholder: str = "",
407
+ placeholder: Optional[str] = None,
173
408
  on_change: Optional[Callable[[str], None]] = None,
174
409
  on_submit: Optional[Callable[[str], None]] = None,
175
410
  secure: bool = False,
176
411
  multiline: bool = False,
177
- keyboard_type: Optional[str] = None,
178
- auto_capitalize: Optional[str] = None,
412
+ keyboard_type: Optional[KeyboardType] = None,
413
+ auto_capitalize: Optional[AutoCapitalize] = None,
179
414
  auto_correct: Optional[bool] = None,
180
415
  auto_focus: bool = False,
181
- return_key_type: Optional[str] = None,
416
+ return_key_type: Optional[ReturnKeyType] = None,
182
417
  max_length: Optional[int] = None,
183
- placeholder_color: Optional[str] = None,
184
- style: StyleValue = None,
418
+ placeholder_color: Optional[Color] = None,
419
+ style: StyleProp = None,
185
420
  accessibility_label: Optional[str] = None,
186
421
  accessibility_hint: Optional[str] = None,
187
422
  accessible: Optional[bool] = None,
188
423
  ref: Optional[Dict[str, Any]] = None,
189
424
  key: Optional[str] = None,
190
425
  ) -> Element:
191
- """Display a text entry field (single-line by default, or `multiline`).
426
+ """Display a text-entry field (single-line by default, or ``multiline``).
192
427
 
193
- Style properties: `font_size`, `color`, `background_color`,
194
- `border_*`, plus the common layout props.
428
+ Style properties: ``font_size``, ``color``, ``background_color``,
429
+ ``border_*``, plus the common layout props.
195
430
 
196
431
  Args:
197
432
  value: Current text content (controlled-input pattern).
198
- placeholder: Hint shown when `value` is empty.
433
+ placeholder: Hint shown when ``value`` is empty.
199
434
  on_change: Callback invoked with the new string each keystroke.
200
435
  on_submit: Callback invoked when the user submits (Return /
201
436
  Done / etc.). Receives the final text.
202
- secure: When `True`, characters are masked (use for passwords).
203
- multiline: When `True`, allows multiple lines of input.
437
+ secure: When ``True``, characters are masked (use for passwords).
438
+ multiline: When ``True``, allows multiple lines of input.
204
439
  keyboard_type: One of ``"default"``, ``"email_address"``,
205
- ``"number_pad"``, ``"decimal_pad"``, ``"phone_pad"``,
206
- ``"url"``.
207
- auto_capitalize: One of ``"none"``, ``"sentences"``,
208
- ``"words"``, ``"characters"``.
440
+ ``"number_pad"``, ``"decimal_pad"``, ``"phone_pad"``, ``"url"``.
441
+ auto_capitalize: One of ``"none"``, ``"sentences"``, ``"words"``,
442
+ ``"characters"``.
209
443
  auto_correct: Enable/disable autocorrection.
210
444
  auto_focus: Request focus on mount.
211
445
  return_key_type: One of ``"default"``, ``"done"``, ``"go"``,
212
446
  ``"next"``, ``"send"``, ``"search"``.
213
447
  max_length: Maximum number of characters allowed.
214
- placeholder_color: Color to use for the placeholder string.
448
+ placeholder_color: Color used for the placeholder string.
215
449
  style: Style dict (or list of dicts).
216
450
  accessibility_label: Spoken description for screen readers.
217
451
  accessibility_hint: Spoken extra detail (iOS only).
@@ -220,94 +454,90 @@ def TextInput(
220
454
  key: Stable identity for keyed reconciliation.
221
455
 
222
456
  Returns:
223
- An [`Element`][pythonnative.Element] of type `"TextInput"`.
457
+ An [`Element`][pythonnative.Element] of type ``"TextInput"``.
224
458
  """
225
- props: Dict[str, Any] = {"value": value}
226
- if placeholder:
227
- props["placeholder"] = placeholder
228
- if on_change is not None:
229
- props["on_change"] = on_change
230
- if on_submit is not None:
231
- props["on_submit"] = on_submit
232
- if secure:
233
- props["secure"] = True
234
- if multiline:
235
- props["multiline"] = True
236
- if keyboard_type is not None:
237
- props["keyboard_type"] = keyboard_type
238
- if auto_capitalize is not None:
239
- props["auto_capitalize"] = auto_capitalize
240
- if auto_correct is not None:
241
- props["auto_correct"] = auto_correct
242
- if auto_focus:
243
- props["auto_focus"] = True
244
- if return_key_type is not None:
245
- props["return_key_type"] = return_key_type
246
- if max_length is not None:
247
- props["max_length"] = max_length
248
- if placeholder_color is not None:
249
- props["placeholder_color"] = placeholder_color
250
- props.update(resolve_style(style))
251
- props.update(_accessibility_props(accessibility_label, accessibility_hint, None, accessible))
252
- if ref is not None:
253
- props["ref"] = ref
254
- return Element("TextInput", props, [], key=key)
459
+ return _make_element(
460
+ "TextInput",
461
+ style=style,
462
+ ref=ref,
463
+ key=key,
464
+ value=value,
465
+ placeholder=placeholder,
466
+ on_change=on_change,
467
+ on_submit=on_submit,
468
+ secure=secure or None,
469
+ multiline=multiline or None,
470
+ keyboard_type=keyboard_type,
471
+ auto_capitalize=auto_capitalize,
472
+ auto_correct=auto_correct,
473
+ auto_focus=auto_focus or None,
474
+ return_key_type=return_key_type,
475
+ max_length=max_length,
476
+ placeholder_color=placeholder_color,
477
+ accessibility_label=accessibility_label,
478
+ accessibility_hint=accessibility_hint,
479
+ accessible=accessible,
480
+ )
255
481
 
256
482
 
257
483
  def Image(
258
484
  source: str = "",
259
485
  *,
260
- scale_type: Optional[str] = None,
261
- tint_color: Optional[str] = None,
262
- style: StyleValue = None,
486
+ scale_type: Optional[ScaleType] = None,
487
+ tint_color: Optional[Color] = None,
488
+ style: StyleProp = None,
263
489
  accessibility_label: Optional[str] = None,
490
+ accessibility_role: Optional[str] = None,
264
491
  accessible: Optional[bool] = None,
265
492
  ref: Optional[Dict[str, Any]] = None,
266
493
  key: Optional[str] = None,
267
494
  ) -> Element:
268
495
  """Display an image from a resource path or URL.
269
496
 
270
- Style properties: `background_color`, `border_*`, `opacity`,
271
- `transform`, plus the common layout props.
497
+ Style properties: ``background_color``, ``border_*``, ``opacity``,
498
+ ``transform``, plus the common layout props.
272
499
 
273
500
  Network images (``http://`` / ``https://``) are loaded
274
- asynchronously off the main thread on both iOS (via NSURLSession)
275
- and Android (via a worker thread + `BitmapFactory`).
501
+ asynchronously off the main thread on both iOS (via
502
+ ``NSURLSession``) and Android (via a worker thread plus
503
+ ``BitmapFactory``).
276
504
 
277
505
  Args:
278
506
  source: Image resource name or URL.
279
- scale_type: Fit mode: `"cover"`, `"contain"`, `"stretch"`,
280
- `"center"`.
507
+ scale_type: Fit mode: ``"cover"``, ``"contain"``, ``"stretch"``,
508
+ ``"center"``.
281
509
  tint_color: Color overlay applied to template images
282
510
  (monochrome icons).
283
511
  style: Style dict (or list of dicts).
284
512
  accessibility_label: Spoken description for screen readers.
513
+ accessibility_role: Override the default ``"image"`` role.
285
514
  accessible: Override whether the element is exposed to AT.
286
515
  ref: Optional ``use_ref()`` dict.
287
516
  key: Stable identity for keyed reconciliation.
288
517
 
289
518
  Returns:
290
- An [`Element`][pythonnative.Element] of type `"Image"`.
519
+ An [`Element`][pythonnative.Element] of type ``"Image"``.
291
520
  """
292
- props: Dict[str, Any] = {}
293
- if source:
294
- props["source"] = source
295
- if scale_type is not None:
296
- props["scale_type"] = scale_type
297
- if tint_color is not None:
298
- props["tint_color"] = tint_color
299
- props.update(resolve_style(style))
300
- props.update(_accessibility_props(accessibility_label, None, "image", accessible))
301
- if ref is not None:
302
- props["ref"] = ref
303
- return Element("Image", props, [], key=key)
521
+ return _make_element(
522
+ "Image",
523
+ style=style,
524
+ ref=ref,
525
+ key=key,
526
+ source=source or None,
527
+ scale_type=scale_type,
528
+ tint_color=tint_color,
529
+ accessibility_label=accessibility_label,
530
+ accessibility_role=accessibility_role,
531
+ accessible=accessible,
532
+ _defaults={"accessibility_role": "image"},
533
+ )
304
534
 
305
535
 
306
536
  def Switch(
307
537
  *,
308
538
  value: bool = False,
309
539
  on_change: Optional[Callable[[bool], None]] = None,
310
- style: StyleValue = None,
540
+ style: StyleProp = None,
311
541
  key: Optional[str] = None,
312
542
  ) -> Element:
313
543
  """Display a toggle switch.
@@ -319,66 +549,74 @@ def Switch(
319
549
  key: Stable identity for keyed reconciliation.
320
550
 
321
551
  Returns:
322
- An [`Element`][pythonnative.Element] of type `"Switch"`.
552
+ An [`Element`][pythonnative.Element] of type ``"Switch"``.
323
553
  """
324
- props: Dict[str, Any] = {"value": value}
325
- if on_change is not None:
326
- props["on_change"] = on_change
327
- props.update(resolve_style(style))
328
- return Element("Switch", props, [], key=key)
554
+ return _make_element(
555
+ "Switch",
556
+ style=style,
557
+ key=key,
558
+ value=value,
559
+ on_change=on_change,
560
+ )
329
561
 
330
562
 
331
563
  def ProgressBar(
332
564
  *,
333
565
  value: float = 0.0,
334
- style: StyleValue = None,
566
+ style: StyleProp = None,
335
567
  key: Optional[str] = None,
336
568
  ) -> Element:
337
- """Show determinate progress as a value between 0.0 and 1.0.
569
+ """Show determinate progress as a value between ``0.0`` and ``1.0``.
338
570
 
339
571
  For indeterminate progress, use
340
572
  [`ActivityIndicator`][pythonnative.ActivityIndicator] instead.
341
573
 
342
574
  Args:
343
- value: Fraction complete (clamped to `[0.0, 1.0]` by the
575
+ value: Fraction complete (clamped to ``[0.0, 1.0]`` by the
344
576
  platform handler).
345
577
  style: Style dict (or list of dicts).
346
578
  key: Stable identity for keyed reconciliation.
347
579
 
348
580
  Returns:
349
- An [`Element`][pythonnative.Element] of type `"ProgressBar"`.
581
+ An [`Element`][pythonnative.Element] of type ``"ProgressBar"``.
350
582
  """
351
- props: Dict[str, Any] = {"value": value}
352
- props.update(resolve_style(style))
353
- return Element("ProgressBar", props, [], key=key)
583
+ return _make_element(
584
+ "ProgressBar",
585
+ style=style,
586
+ key=key,
587
+ value=value,
588
+ )
354
589
 
355
590
 
356
591
  def ActivityIndicator(
357
592
  *,
358
593
  animating: bool = True,
359
- style: StyleValue = None,
594
+ style: StyleProp = None,
360
595
  key: Optional[str] = None,
361
596
  ) -> Element:
362
597
  """Show an indeterminate loading spinner.
363
598
 
364
599
  Args:
365
- animating: When `False`, the spinner is hidden.
600
+ animating: When ``False``, the spinner is hidden.
366
601
  style: Style dict (or list of dicts).
367
602
  key: Stable identity for keyed reconciliation.
368
603
 
369
604
  Returns:
370
605
  An [`Element`][pythonnative.Element] of type
371
- `"ActivityIndicator"`.
606
+ ``"ActivityIndicator"``.
372
607
  """
373
- props: Dict[str, Any] = {"animating": animating}
374
- props.update(resolve_style(style))
375
- return Element("ActivityIndicator", props, [], key=key)
608
+ return _make_element(
609
+ "ActivityIndicator",
610
+ style=style,
611
+ key=key,
612
+ animating=animating,
613
+ )
376
614
 
377
615
 
378
616
  def WebView(
379
617
  *,
380
618
  url: str = "",
381
- style: StyleValue = None,
619
+ style: StyleProp = None,
382
620
  key: Optional[str] = None,
383
621
  ) -> Element:
384
622
  """Embed web content from a URL.
@@ -389,13 +627,14 @@ def WebView(
389
627
  key: Stable identity for keyed reconciliation.
390
628
 
391
629
  Returns:
392
- An [`Element`][pythonnative.Element] of type `"WebView"`.
630
+ An [`Element`][pythonnative.Element] of type ``"WebView"``.
393
631
  """
394
- props: Dict[str, Any] = {}
395
- if url:
396
- props["url"] = url
397
- props.update(resolve_style(style))
398
- return Element("WebView", props, [], key=key)
632
+ return _make_element(
633
+ "WebView",
634
+ style=style,
635
+ key=key,
636
+ url=url or None,
637
+ )
399
638
 
400
639
 
401
640
  def Spacer(
@@ -406,32 +645,31 @@ def Spacer(
406
645
  ) -> Element:
407
646
  """Insert empty space inside a flex container.
408
647
 
409
- Pass `size` for a fixed gap, or `flex` to expand and absorb
648
+ Pass ``size`` for a fixed gap, or ``flex`` to expand and absorb
410
649
  remaining space.
411
650
 
412
651
  Args:
413
- size: Fixed gap in dp/pt along the parent's main axis.
652
+ size: Fixed gap in dp/pt along the parent's main axis. Mirrored
653
+ on both axes — whichever axis the parent's
654
+ ``flex_direction`` chooses as main becomes the actual gap.
414
655
  flex: Flex-grow weight; useful for pushing siblings to the
415
656
  opposite end of a [`Row`][pythonnative.Row] or
416
657
  [`Column`][pythonnative.Column].
417
658
  key: Stable identity for keyed reconciliation.
418
659
 
419
660
  Returns:
420
- An [`Element`][pythonnative.Element] of type `"Spacer"`.
661
+ An [`Element`][pythonnative.Element] of type ``"Spacer"``.
421
662
  """
422
- props: Dict[str, Any] = {}
423
- if size is not None:
424
- # The layout engine sees ``width`` / ``height`` only, so a fixed
425
- # ``size`` is mirrored on both axes. Whichever axis the parent
426
- # container's ``flex_direction`` chooses as main becomes the
427
- # actual gap; the cross axis is constrained by the parent's
428
- # ``align_items`` (typically ``stretch``) anyway.
429
- props["size"] = size
430
- props["width"] = size
431
- props["height"] = size
432
- if flex is not None:
433
- props["flex"] = flex
434
- return Element("Spacer", props, [], key=key)
663
+ width = size if size is not None else None
664
+ height = size if size is not None else None
665
+ return _make_element(
666
+ "Spacer",
667
+ key=key,
668
+ size=size,
669
+ width=width,
670
+ height=height,
671
+ flex=flex,
672
+ )
435
673
 
436
674
 
437
675
  def Slider(
@@ -440,10 +678,10 @@ def Slider(
440
678
  min_value: float = 0.0,
441
679
  max_value: float = 1.0,
442
680
  on_change: Optional[Callable[[float], None]] = None,
443
- style: StyleValue = None,
681
+ style: StyleProp = None,
444
682
  key: Optional[str] = None,
445
683
  ) -> Element:
446
- """Continuous-value slider between `min_value` and `max_value`.
684
+ """Continuous-value slider between ``min_value`` and ``max_value``.
447
685
 
448
686
  Args:
449
687
  value: Current slider value.
@@ -455,27 +693,27 @@ def Slider(
455
693
  key: Stable identity for keyed reconciliation.
456
694
 
457
695
  Returns:
458
- An [`Element`][pythonnative.Element] of type `"Slider"`.
696
+ An [`Element`][pythonnative.Element] of type ``"Slider"``.
459
697
  """
460
- props: Dict[str, Any] = {
461
- "value": value,
462
- "min_value": min_value,
463
- "max_value": max_value,
464
- }
465
- if on_change is not None:
466
- props["on_change"] = on_change
467
- props.update(resolve_style(style))
468
- return Element("Slider", props, [], key=key)
698
+ return _make_element(
699
+ "Slider",
700
+ style=style,
701
+ key=key,
702
+ value=value,
703
+ min_value=min_value,
704
+ max_value=max_value,
705
+ on_change=on_change,
706
+ )
469
707
 
470
708
 
471
709
  # ======================================================================
472
- # Container components
710
+ # Container factories
473
711
  # ======================================================================
474
712
 
475
713
 
476
714
  def View(
477
715
  *children: Element,
478
- style: StyleValue = None,
716
+ style: StyleProp = None,
479
717
  accessibility_label: Optional[str] = None,
480
718
  accessibility_hint: Optional[str] = None,
481
719
  accessibility_role: Optional[str] = None,
@@ -483,28 +721,24 @@ def View(
483
721
  ref: Optional[Dict[str, Any]] = None,
484
722
  key: Optional[str] = None,
485
723
  ) -> Element:
486
- """Universal flex container (like React Native's `View`).
724
+ """Universal flex container (like React Native's ``View``).
487
725
 
488
- Defaults to `flex_direction: "column"`. Override via `style`:
726
+ Defaults to ``flex_direction: "column"`` (override via ``style``).
489
727
 
490
- ```python
491
- pn.View(child_a, child_b, style={"flex_direction": "row"})
492
- ```
728
+ Flex container properties (passed via ``style``):
493
729
 
494
- Flex container properties (inside `style`):
495
-
496
- - `flex_direction`: `"column"` (default), `"row"`,
497
- `"column_reverse"`, `"row_reverse"`.
498
- - `justify_content`: main-axis distribution. Accepts `"flex_start"`
499
- (default), `"center"`, `"flex_end"`, `"space_between"`,
500
- `"space_around"`, `"space_evenly"`.
501
- - `align_items`: cross-axis alignment. Accepts `"stretch"` (default),
502
- `"flex_start"`, `"center"`, `"flex_end"`.
503
- - `overflow`: `"visible"` (default) or `"hidden"`.
504
- - `spacing`, `padding`, `background_color`, `border_radius`,
505
- `border_width`, `border_color`, `shadow_color`, `shadow_offset`,
506
- `shadow_opacity`, `shadow_radius`, `elevation`, `opacity`,
507
- `transform`.
730
+ - ``flex_direction``: ``"column"`` (default), ``"row"``,
731
+ ``"column_reverse"``, ``"row_reverse"``.
732
+ - ``justify_content``: main-axis distribution. Accepts
733
+ ``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
734
+ ``"space_between"``, ``"space_around"``, ``"space_evenly"``.
735
+ - ``align_items``: cross-axis alignment. Accepts ``"stretch"``
736
+ (default), ``"flex_start"``, ``"center"``, ``"flex_end"``.
737
+ - ``overflow``: ``"visible"`` (default) or ``"hidden"``.
738
+ - ``spacing``, ``padding``, ``background_color``, ``border_radius``,
739
+ ``border_width``, ``border_color``, ``shadow_color``,
740
+ ``shadow_offset``, ``shadow_opacity``, ``shadow_radius``,
741
+ ``elevation``, ``opacity``, ``transform``.
508
742
 
509
743
  Args:
510
744
  *children: Child elements rendered inside the container.
@@ -513,44 +747,37 @@ def View(
513
747
  accessibility_hint: Spoken extra detail (iOS only).
514
748
  accessibility_role: Semantic role for assistive tech.
515
749
  accessible: Override whether the element is exposed to AT.
516
- ref: Optional ``use_ref()`` dict; the reconciler populates
517
- ``ref["current"]`` with the underlying native view.
750
+ ref: Optional ``use_ref()`` dict.
518
751
  key: Stable identity for keyed reconciliation.
519
752
 
520
753
  Returns:
521
- An [`Element`][pythonnative.Element] of type `"View"`.
754
+ An [`Element`][pythonnative.Element] of type ``"View"``.
522
755
  """
523
- props: Dict[str, Any] = {"flex_direction": "column"}
524
- props.update(resolve_style(style))
525
- props.update(_accessibility_props(accessibility_label, accessibility_hint, accessibility_role, accessible))
526
- if ref is not None:
527
- props["ref"] = ref
528
- return Element("View", props, list(children), key=key)
756
+ return _make_element(
757
+ "View",
758
+ *children,
759
+ style=style,
760
+ ref=ref,
761
+ key=key,
762
+ accessibility_label=accessibility_label,
763
+ accessibility_hint=accessibility_hint,
764
+ accessibility_role=accessibility_role,
765
+ accessible=accessible,
766
+ _defaults={"flex_direction": "column"},
767
+ )
529
768
 
530
769
 
531
770
  def Column(
532
771
  *children: Element,
533
- style: StyleValue = None,
772
+ style: StyleProp = None,
534
773
  ref: Optional[Dict[str, Any]] = None,
535
774
  key: Optional[str] = None,
536
775
  ) -> Element:
537
776
  """Arrange children vertically.
538
777
 
539
778
  Convenience wrapper around [`View`][pythonnative.View] with
540
- `flex_direction` fixed to `"column"`. Use `View` directly if you
541
- need to switch between row and column at runtime.
542
-
543
- Style properties: `spacing`, `padding`, `align_items`,
544
- `justify_content`, `background_color`, `overflow`, plus the common
545
- layout props.
546
-
547
- `align_items` controls cross-axis (horizontal) alignment:
548
- `"stretch"` (default), `"flex_start"` / `"leading"`, `"center"`, or
549
- `"flex_end"` / `"trailing"`.
550
-
551
- `justify_content` controls main-axis (vertical) distribution:
552
- `"flex_start"` (default), `"center"`, `"flex_end"`,
553
- `"space_between"`, `"space_around"`, `"space_evenly"`.
779
+ ``flex_direction`` locked to ``"column"``. Use ``View`` directly if
780
+ you need to switch between row and column at runtime.
554
781
 
555
782
  Args:
556
783
  *children: Child elements stacked top to bottom.
@@ -559,39 +786,29 @@ def Column(
559
786
  key: Stable identity for keyed reconciliation.
560
787
 
561
788
  Returns:
562
- An [`Element`][pythonnative.Element] of type `"Column"`.
789
+ An [`Element`][pythonnative.Element] of type ``"Column"``.
563
790
  """
564
- props: Dict[str, Any] = {"flex_direction": "column"}
565
- props.update(resolve_style(style))
566
- props["flex_direction"] = "column"
567
- if ref is not None:
568
- props["ref"] = ref
569
- return Element("Column", props, list(children), key=key)
791
+ return _make_element(
792
+ "Column",
793
+ *children,
794
+ style=style,
795
+ ref=ref,
796
+ key=key,
797
+ _forced={"flex_direction": "column"},
798
+ )
570
799
 
571
800
 
572
801
  def Row(
573
802
  *children: Element,
574
- style: StyleValue = None,
803
+ style: StyleProp = None,
575
804
  ref: Optional[Dict[str, Any]] = None,
576
805
  key: Optional[str] = None,
577
806
  ) -> Element:
578
807
  """Arrange children horizontally.
579
808
 
580
809
  Convenience wrapper around [`View`][pythonnative.View] with
581
- `flex_direction` fixed to `"row"`. Use `View` directly if you need
582
- to switch between row and column at runtime.
583
-
584
- Style properties: `spacing`, `padding`, `align_items`,
585
- `justify_content`, `background_color`, `overflow`, plus the common
586
- layout props.
587
-
588
- `align_items` controls cross-axis (vertical) alignment:
589
- `"stretch"` (default), `"flex_start"` / `"top"`, `"center"`, or
590
- `"flex_end"` / `"bottom"`.
591
-
592
- `justify_content` controls main-axis (horizontal) distribution:
593
- `"flex_start"` (default), `"center"`, `"flex_end"`,
594
- `"space_between"`, `"space_around"`, `"space_evenly"`.
810
+ ``flex_direction`` locked to ``"row"``. Use ``View`` directly if you
811
+ need to switch between row and column at runtime.
595
812
 
596
813
  Args:
597
814
  *children: Child elements arranged left to right.
@@ -600,54 +817,62 @@ def Row(
600
817
  key: Stable identity for keyed reconciliation.
601
818
 
602
819
  Returns:
603
- An [`Element`][pythonnative.Element] of type `"Row"`.
820
+ An [`Element`][pythonnative.Element] of type ``"Row"``.
604
821
  """
605
- props: Dict[str, Any] = {"flex_direction": "row"}
606
- props.update(resolve_style(style))
607
- props["flex_direction"] = "row"
608
- if ref is not None:
609
- props["ref"] = ref
610
- return Element("Row", props, list(children), key=key)
822
+ return _make_element(
823
+ "Row",
824
+ *children,
825
+ style=style,
826
+ ref=ref,
827
+ key=key,
828
+ _forced={"flex_direction": "row"},
829
+ )
611
830
 
612
831
 
613
832
  def ScrollView(
614
- child: Optional[Element] = None,
615
- *,
833
+ *children: Element,
616
834
  refresh_control: Optional[Dict[str, Any]] = None,
617
- style: StyleValue = None,
835
+ scroll_axis: Optional[Literal["vertical", "horizontal"]] = None,
836
+ style: StyleProp = None,
618
837
  ref: Optional[Dict[str, Any]] = None,
619
838
  key: Optional[str] = None,
620
839
  ) -> Element:
621
- """Wrap a single child in a scrollable container.
840
+ """Wrap children in a scrollable container.
841
+
842
+ ``ScrollView`` typically takes a single child (a ``Column`` or
843
+ ``Row`` aggregating the scrollable content). It accepts ``*children``
844
+ for ergonomic call sites; the underlying native scroll view stacks
845
+ them on its content axis.
622
846
 
623
847
  Args:
624
- child: The single child to scroll. Wrap multiple elements in a
625
- [`Column`][pythonnative.Column] or
626
- [`Row`][pythonnative.Row] first.
848
+ *children: Child elements to scroll.
627
849
  refresh_control: Optional pull-to-refresh spec, typically
628
850
  constructed via
629
851
  [`RefreshControl`][pythonnative.RefreshControl]. The dict
630
- must have ``refreshing`` (bool) and ``on_refresh`` (callable).
852
+ must have ``refreshing`` (bool) and ``on_refresh``
853
+ (callable).
854
+ scroll_axis: ``"vertical"`` (default) or ``"horizontal"``.
631
855
  style: Style dict (or list of dicts).
632
856
  ref: Optional ``use_ref()`` dict.
633
857
  key: Stable identity for keyed reconciliation.
634
858
 
635
859
  Returns:
636
- An [`Element`][pythonnative.Element] of type `"ScrollView"`.
860
+ An [`Element`][pythonnative.Element] of type ``"ScrollView"``.
637
861
  """
638
- children = [child] if child is not None else []
639
- props: Dict[str, Any] = {}
640
- if refresh_control is not None:
641
- props["refresh_control"] = refresh_control
642
- props.update(resolve_style(style))
643
- if ref is not None:
644
- props["ref"] = ref
645
- return Element("ScrollView", props, children, key=key)
862
+ return _make_element(
863
+ "ScrollView",
864
+ *children,
865
+ style=style,
866
+ ref=ref,
867
+ key=key,
868
+ refresh_control=refresh_control,
869
+ scroll_axis=scroll_axis,
870
+ )
646
871
 
647
872
 
648
873
  def SafeAreaView(
649
874
  *children: Element,
650
- style: StyleValue = None,
875
+ style: StyleProp = None,
651
876
  key: Optional[str] = None,
652
877
  ) -> Element:
653
878
  """Container that respects safe-area insets (notch, status bar, home indicator).
@@ -658,11 +883,14 @@ def SafeAreaView(
658
883
  key: Stable identity for keyed reconciliation.
659
884
 
660
885
  Returns:
661
- An [`Element`][pythonnative.Element] of type `"SafeAreaView"`.
886
+ An [`Element`][pythonnative.Element] of type ``"SafeAreaView"``.
662
887
  """
663
- props: Dict[str, Any] = {}
664
- props.update(resolve_style(style))
665
- return Element("SafeAreaView", props, list(children), key=key)
888
+ return _make_element(
889
+ "SafeAreaView",
890
+ *children,
891
+ style=style,
892
+ key=key,
893
+ )
666
894
 
667
895
 
668
896
  def Modal(
@@ -670,28 +898,28 @@ def Modal(
670
898
  visible: bool = False,
671
899
  on_dismiss: Optional[Callable[[], None]] = None,
672
900
  title: Optional[str] = None,
673
- animation_type: str = "slide",
901
+ animation_type: Literal["slide", "fade", "none"] = "slide",
674
902
  transparent: bool = False,
675
- style: StyleValue = None,
903
+ style: StyleProp = None,
676
904
  key: Optional[str] = None,
677
905
  ) -> Element:
678
906
  """Overlay modal dialog backed by a real native presentation.
679
907
 
680
- The modal is shown when `visible=True` and hidden when `False`.
681
- Drive `visible` from a hook so the parent component can dismiss
908
+ The modal is shown when ``visible=True`` and hidden when ``False``.
909
+ Drive ``visible`` from a hook so the parent component can dismiss
682
910
  the modal in response to user actions. On iOS this presents a
683
- `UIViewController`; on Android it shows an `android.app.Dialog`.
911
+ ``UIViewController``; on Android it shows an ``android.app.Dialog``.
684
912
 
685
913
  Children are mounted as the modal's content view, not into the
686
- on-tree placeholder, so they appear above all other native
687
- content and don't influence the underlying layout.
914
+ on-tree placeholder, so they appear above all other native content
915
+ and don't influence the underlying layout.
688
916
 
689
917
  Args:
690
918
  *children: Modal content.
691
919
  visible: Controls whether the modal is presented.
692
920
  on_dismiss: Callback invoked when the user dismisses the modal
693
- via system gesture (e.g., backdrop tap or back button).
694
- title: Optional title bar text.
921
+ via system gesture.
922
+ title: Optional title-bar text.
695
923
  animation_type: ``"slide"`` (default), ``"fade"``, or ``"none"``.
696
924
  transparent: When ``True``, the underlying view is dimmed
697
925
  instead of fully covered.
@@ -699,91 +927,152 @@ def Modal(
699
927
  key: Stable identity for keyed reconciliation.
700
928
 
701
929
  Returns:
702
- An [`Element`][pythonnative.Element] of type `"Modal"`.
930
+ An [`Element`][pythonnative.Element] of type ``"Modal"``.
703
931
  """
704
- props: Dict[str, Any] = {
705
- "visible": visible,
706
- "animation_type": animation_type,
707
- "transparent": transparent,
708
- }
709
- if on_dismiss is not None:
710
- props["on_dismiss"] = on_dismiss
711
- if title is not None:
712
- props["title"] = title
713
- props.update(resolve_style(style))
714
- return Element("Modal", props, list(children), key=key)
932
+ return _make_element(
933
+ "Modal",
934
+ *children,
935
+ style=style,
936
+ key=key,
937
+ visible=visible,
938
+ animation_type=animation_type,
939
+ transparent=transparent,
940
+ on_dismiss=on_dismiss,
941
+ title=title,
942
+ )
715
943
 
716
944
 
717
945
  def Pressable(
718
- child: Optional[Element] = None,
719
- *,
946
+ *children: Element,
720
947
  on_press: Optional[Callable[[], None]] = None,
721
948
  on_long_press: Optional[Callable[[], None]] = None,
722
949
  pressed_opacity: float = 0.6,
723
- style: StyleValue = None,
950
+ style: StyleProp = None,
724
951
  accessibility_label: Optional[str] = None,
725
952
  accessibility_hint: Optional[str] = None,
953
+ accessibility_role: Optional[str] = None,
726
954
  accessible: Optional[bool] = None,
727
955
  key: Optional[str] = None,
728
956
  ) -> Element:
729
- """Wrap any child element with tap and long-press handlers.
957
+ """Wrap children with tap and long-press handlers.
730
958
 
731
959
  Useful for making non-button elements (text, images, custom views)
732
960
  respond to user taps. The wrapper view fades to ``pressed_opacity``
733
- on touch-down and back to full opacity on touch-up, providing
734
- subtle visual feedback (matches React Native's `Pressable` default).
961
+ on touch-down and back to full opacity on touch-up.
962
+
963
+ Pressable gets ``accessibility_role="button"`` by default.
735
964
 
736
965
  Args:
737
- child: The single element to make pressable.
966
+ *children: Elements to make pressable.
738
967
  on_press: Callback invoked on a normal tap.
739
968
  on_long_press: Callback invoked on a sustained press.
740
- pressed_opacity: Opacity (0-1) applied to the wrapper while
741
- the user's finger is down. Set to ``1.0`` for no visual
742
- feedback.
969
+ pressed_opacity: Opacity (01) applied while the user's finger
970
+ is down. Set to ``1.0`` for no visual feedback.
743
971
  style: Style dict applied to the wrapper.
744
972
  accessibility_label: Spoken description for screen readers.
745
973
  accessibility_hint: Spoken extra detail (iOS only).
974
+ accessibility_role: Override the default ``"button"`` role.
746
975
  accessible: Override whether the element is exposed to AT.
747
976
  key: Stable identity for keyed reconciliation.
748
977
 
749
978
  Returns:
750
- An [`Element`][pythonnative.Element] of type `"Pressable"`.
979
+ An [`Element`][pythonnative.Element] of type ``"Pressable"``.
751
980
  """
752
- props: Dict[str, Any] = {}
753
- if on_press is not None:
754
- props["on_press"] = on_press
755
- if on_long_press is not None:
756
- props["on_long_press"] = on_long_press
757
- if pressed_opacity != 0.6:
758
- props["pressed_opacity"] = pressed_opacity
759
- else:
760
- props.setdefault("pressed_opacity", 0.6)
761
- props.update(resolve_style(style))
762
- props.update(_accessibility_props(accessibility_label, accessibility_hint, "button", accessible))
763
- children = [child] if child is not None else []
764
- return Element("Pressable", props, children, key=key)
981
+ return _make_element(
982
+ "Pressable",
983
+ *children,
984
+ style=style,
985
+ key=key,
986
+ on_press=on_press,
987
+ on_long_press=on_long_press,
988
+ pressed_opacity=pressed_opacity,
989
+ accessibility_label=accessibility_label,
990
+ accessibility_hint=accessibility_hint,
991
+ accessibility_role=accessibility_role,
992
+ accessible=accessible,
993
+ _defaults={"accessibility_role": "button"},
994
+ )
995
+
996
+
997
+ # ======================================================================
998
+ # Fragment
999
+ # ======================================================================
1000
+
1001
+
1002
+ def Fragment(*children: Optional[Element], key: Optional[str] = None) -> Element:
1003
+ """Group children without adding a wrapping native view.
1004
+
1005
+ Like React's ``<></>``: returns multiple elements from a component
1006
+ without introducing an extra container. The reconciler flattens
1007
+ Fragment elements at the children-list level, so each child appears
1008
+ as a direct sibling of the Fragment's parent in the native tree.
1009
+
1010
+ Useful inside [`Provider`][pythonnative.Provider] /
1011
+ [`memo`][pythonnative.memo] / conditional logic when grouping
1012
+ siblings inside another component's child list:
1013
+
1014
+ ```python
1015
+ pn.Column(
1016
+ pn.Text("Top"),
1017
+ pn.Fragment(
1018
+ pn.Text("Middle A"),
1019
+ pn.Text("Middle B"),
1020
+ ),
1021
+ pn.Text("Bottom"),
1022
+ )
1023
+ ```
1024
+
1025
+ Args:
1026
+ *children: Child elements to expose at the parent level. ``None``
1027
+ children are dropped, which makes conditional rendering with
1028
+ ``cond and pn.Text(...)`` ergonomic.
1029
+ key: Optional key for the Fragment itself (rarely useful since
1030
+ Fragment doesn't appear in the native tree).
1031
+
1032
+ Returns:
1033
+ An [`Element`][pythonnative.Element] of type ``"__Fragment__"``.
1034
+
1035
+ Note:
1036
+ Today, returning a Fragment from a ``@pn.component`` function
1037
+ only mounts its first child as the component's root. To return
1038
+ multiple top-level elements from a function component, use a
1039
+ container such as [`Column`][pythonnative.Column] or
1040
+ [`Row`][pythonnative.Row] instead.
1041
+ """
1042
+ filtered = [c for c in children if c is not None]
1043
+ return Element("__Fragment__", {}, filtered, key=key)
1044
+
1045
+
1046
+ # ======================================================================
1047
+ # Error boundary
1048
+ # ======================================================================
765
1049
 
766
1050
 
767
1051
  def ErrorBoundary(
768
- child: Optional[Element] = None,
769
- *,
1052
+ *children: Element,
770
1053
  fallback: Optional[Any] = None,
771
1054
  key: Optional[str] = None,
772
1055
  ) -> Element:
773
- """Catch render errors in `child` and display `fallback` instead.
1056
+ """Catch render errors in the wrapped subtree and display ``fallback`` instead.
1057
+
1058
+ ``fallback`` may be an [`Element`][pythonnative.Element] or a
1059
+ callable that receives the exception and returns an ``Element``.
1060
+ Useful for isolating risky subtrees so a single failure doesn't
1061
+ crash the page.
774
1062
 
775
- `fallback` may be an [`Element`][pythonnative.Element] or a callable
776
- that receives the exception and returns an `Element`. Useful for
777
- isolating risky subtrees so a single failure doesn't crash the page.
1063
+ When multiple children are passed they're grouped under a
1064
+ [`Fragment`][pythonnative.Fragment] so the boundary still wraps a
1065
+ single logical subtree.
778
1066
 
779
1067
  Args:
780
- child: Subtree to wrap.
781
- fallback: Element to render when `child` raises during render,
782
- or a callable `fallback(err) -> Element`.
1068
+ *children: Subtree to wrap.
1069
+ fallback: Element rendered when the subtree raises during
1070
+ render, or a callable ``fallback(err) -> Element``.
783
1071
  key: Stable identity for keyed reconciliation.
784
1072
 
785
1073
  Returns:
786
- An [`Element`][pythonnative.Element] of type `"__ErrorBoundary__"`.
1074
+ An [`Element`][pythonnative.Element] of type
1075
+ ``"__ErrorBoundary__"``.
787
1076
 
788
1077
  Example:
789
1078
  ```python
@@ -798,8 +1087,16 @@ def ErrorBoundary(
798
1087
  props: Dict[str, Any] = {}
799
1088
  if fallback is not None:
800
1089
  props["__fallback__"] = fallback
801
- children = [child] if child is not None else []
802
- return Element("__ErrorBoundary__", props, children, key=key)
1090
+ if len(children) <= 1:
1091
+ kids = list(children)
1092
+ else:
1093
+ kids = [Fragment(*children)]
1094
+ return Element("__ErrorBoundary__", props, kids, key=key)
1095
+
1096
+
1097
+ # ======================================================================
1098
+ # Lists
1099
+ # ======================================================================
803
1100
 
804
1101
 
805
1102
  def FlatList(
@@ -811,20 +1108,21 @@ def FlatList(
811
1108
  separator_height: float = 0,
812
1109
  refresh_control: Optional[Dict[str, Any]] = None,
813
1110
  on_item_press: Optional[Callable[[int], None]] = None,
814
- style: StyleValue = None,
1111
+ style: StyleProp = None,
815
1112
  key: Optional[str] = None,
816
1113
  ) -> Element:
817
- """Virtualized scrollable list that renders items from `data` lazily.
1114
+ """Virtualized scrollable list that renders items from ``data`` lazily.
818
1115
 
819
- Backed by `UITableView` on iOS and `RecyclerView` on Android via the
820
- `VirtualList` element. Each visible row is mounted on demand by a
821
- nested [`Reconciler`][pythonnative.reconciler.Reconciler] when
1116
+ Backed by ``UITableView`` on iOS and ``RecyclerView`` on Android via
1117
+ the ``VirtualList`` element. Each visible row is mounted on demand
1118
+ by a nested
1119
+ [`Reconciler`][pythonnative.reconciler.Reconciler] when
822
1120
  ``item_height`` is specified.
823
1121
 
824
1122
  When ``item_height`` is omitted the implementation falls back to an
825
1123
  eager (non-virtualized) ``ScrollView`` of every row — keep the data
826
- set small in that mode (the fallback is convenient for short
827
- lists where virtualization overhead would dominate).
1124
+ set small in that mode (the fallback is convenient for short lists
1125
+ where virtualization overhead would dominate).
828
1126
 
829
1127
  Args:
830
1128
  data: Iterable of arbitrary item values.
@@ -844,11 +1142,12 @@ def FlatList(
844
1142
  on_item_press: Callback invoked with the row index when the
845
1143
  user taps a row (virtualized backend only).
846
1144
  style: Style dict (or list of dicts).
847
- key: Stable identity for keyed reconciliation of the list itself.
1145
+ key: Stable identity for keyed reconciliation of the list
1146
+ itself.
848
1147
 
849
1148
  Returns:
850
- An [`Element`][pythonnative.Element] of type `"VirtualList"`
851
- (virtualized) or `"ScrollView"` (eager fallback).
1149
+ An [`Element`][pythonnative.Element] of type ``"VirtualList"``
1150
+ (virtualized) or ``"ScrollView"`` (eager fallback).
852
1151
 
853
1152
  Example:
854
1153
  ```python
@@ -875,11 +1174,7 @@ def FlatList(
875
1174
  el = Element(el.type, el.props, el.children, key=key_extractor(item, i))
876
1175
  items_eager.append(el)
877
1176
  inner = Column(*items_eager, style={"spacing": separator_height} if separator_height else None)
878
- sv_props: Dict[str, Any] = {}
879
- if refresh_control is not None:
880
- sv_props["refresh_control"] = refresh_control
881
- sv_props.update(resolve_style(style))
882
- return Element("ScrollView", sv_props, [inner], key=key)
1177
+ return ScrollView(inner, refresh_control=refresh_control, style=style, key=key)
883
1178
 
884
1179
  # Virtualized path: render_item is invoked lazily by the native
885
1180
  # cell mount callback when each row scrolls into view.
@@ -920,17 +1215,16 @@ def FlatList(
920
1215
 
921
1216
  backend.add_child(content_view, native_root, "View")
922
1217
 
923
- list_props: Dict[str, Any] = {
924
- "count": len(items_list),
925
- "row_height": row_h,
926
- "mount_row": _mount_row,
927
- }
928
- if on_item_press is not None:
929
- list_props["on_row_press"] = on_item_press
930
- if refresh_control is not None:
931
- list_props["refresh_control"] = refresh_control
932
- list_props.update(resolve_style(style))
933
- return Element("VirtualList", list_props, [], key=key)
1218
+ return _make_element(
1219
+ "VirtualList",
1220
+ style=style,
1221
+ key=key,
1222
+ count=len(items_list),
1223
+ row_height=row_h,
1224
+ mount_row=_mount_row,
1225
+ on_row_press=on_item_press,
1226
+ refresh_control=refresh_control,
1227
+ )
934
1228
 
935
1229
 
936
1230
  def SectionList(
@@ -941,7 +1235,7 @@ def SectionList(
941
1235
  item_height: Optional[float] = None,
942
1236
  section_header_height: float = 32.0,
943
1237
  separator_height: float = 0,
944
- style: StyleValue = None,
1238
+ style: StyleProp = None,
945
1239
  key: Optional[str] = None,
946
1240
  ) -> Element:
947
1241
  """Virtualized list that supports section headers.
@@ -953,8 +1247,10 @@ def SectionList(
953
1247
 
954
1248
  Args:
955
1249
  sections: Each section is ``{"title": ..., "data": [...]}``.
956
- render_item: ``render_item(item, item_index, section_index) -> Element``.
957
- render_section_header: ``render_section_header(section, section_index) -> Element``.
1250
+ render_item: ``render_item(item, item_index, section_index) ->
1251
+ Element``.
1252
+ render_section_header: ``render_section_header(section,
1253
+ section_index) -> Element``.
958
1254
  item_height: Fixed row height for items, in layout units.
959
1255
  section_header_height: Fixed header height in layout units.
960
1256
  separator_height: Gap appended below each item, in layout units.
@@ -962,9 +1258,9 @@ def SectionList(
962
1258
  key: Stable identity for keyed reconciliation.
963
1259
 
964
1260
  Returns:
965
- An [`Element`][pythonnative.Element] of type `"VirtualList"`
966
- (virtualized). When ``item_height`` is omitted the layout
967
- falls back to an eager column.
1261
+ An [`Element`][pythonnative.Element] of type ``"VirtualList"``
1262
+ (virtualized). When ``item_height`` is omitted the layout falls
1263
+ back to an eager column.
968
1264
  """
969
1265
  sections_list = list(sections or [])
970
1266
 
@@ -989,9 +1285,7 @@ def SectionList(
989
1285
  else:
990
1286
  children.append(Text(str(entry["item"])))
991
1287
  inner = Column(*children, style={"spacing": separator_height} if separator_height else None)
992
- sv_props: Dict[str, Any] = {}
993
- sv_props.update(resolve_style(style))
994
- return Element("ScrollView", sv_props, [inner], key=key)
1288
+ return ScrollView(inner, style=style, key=key)
995
1289
 
996
1290
  # Virtualized: mixed row heights aren't supported in v1, so we
997
1291
  # use the larger of section_header_height and item_height + sep.
@@ -1024,24 +1318,25 @@ def SectionList(
1024
1318
  except Exception:
1025
1319
  pass
1026
1320
 
1027
- list_props: Dict[str, Any] = {
1028
- "count": len(flat),
1029
- "row_height": row_h,
1030
- "mount_row": _mount_row,
1031
- }
1032
- list_props.update(resolve_style(style))
1033
- return Element("VirtualList", list_props, [], key=key)
1321
+ return _make_element(
1322
+ "VirtualList",
1323
+ style=style,
1324
+ key=key,
1325
+ count=len(flat),
1326
+ row_height=row_h,
1327
+ mount_row=_mount_row,
1328
+ )
1034
1329
 
1035
1330
 
1036
1331
  # ======================================================================
1037
- # Status bar / keyboard / refresh / alert / picker
1332
+ # StatusBar / KeyboardAvoidingView / RefreshControl / Picker
1038
1333
  # ======================================================================
1039
1334
 
1040
1335
 
1041
1336
  def StatusBar(
1042
1337
  *,
1043
- style: Optional[str] = None,
1044
- background_color: Optional[str] = None,
1338
+ bar_style: Optional[Literal["light", "dark", "default"]] = None,
1339
+ background_color: Optional[Color] = None,
1045
1340
  hidden: Optional[bool] = None,
1046
1341
  key: Optional[str] = None,
1047
1342
  ) -> Element:
@@ -1051,8 +1346,13 @@ def StatusBar(
1051
1346
  content but applies its props to the host platform's status bar.
1052
1347
  Mount one near the top of your tree.
1053
1348
 
1349
+ The ``bar_style`` parameter is named separately from the universal
1350
+ ``style`` kwarg (which is unused here) to avoid the conflict that
1351
+ ``style="light"`` would create with the visual-style dict used
1352
+ elsewhere.
1353
+
1054
1354
  Args:
1055
- style: ``"light"`` (light icons over dark backgrounds),
1355
+ bar_style: ``"light"`` (light icons over dark backgrounds),
1056
1356
  ``"dark"`` (dark icons over light backgrounds), or
1057
1357
  ``"default"`` (system default).
1058
1358
  background_color: Color of the status-bar background (Android
@@ -1061,11 +1361,11 @@ def StatusBar(
1061
1361
  key: Stable identity for keyed reconciliation.
1062
1362
 
1063
1363
  Returns:
1064
- An [`Element`][pythonnative.Element] of type `"StatusBar"`.
1364
+ An [`Element`][pythonnative.Element] of type ``"StatusBar"``.
1065
1365
  """
1066
1366
  props: Dict[str, Any] = {}
1067
- if style is not None:
1068
- props["style"] = style
1367
+ if bar_style is not None:
1368
+ props["bar_style"] = bar_style
1069
1369
  if background_color is not None:
1070
1370
  props["background_color"] = background_color
1071
1371
  if hidden is not None:
@@ -1075,16 +1375,16 @@ def StatusBar(
1075
1375
 
1076
1376
  def KeyboardAvoidingView(
1077
1377
  *children: Element,
1078
- behavior: str = "padding",
1079
- style: StyleValue = None,
1378
+ behavior: Literal["padding", "position"] = "padding",
1379
+ style: StyleProp = None,
1080
1380
  key: Optional[str] = None,
1081
1381
  ) -> Element:
1082
1382
  """Wrap content that should shift up when the keyboard is shown.
1083
1383
 
1084
1384
  Subscribes to the platform-reported keyboard height (via
1085
1385
  [`use_keyboard_height`][pythonnative.use_keyboard_height]
1086
- internally) and applies it as bottom padding so the focused
1087
- text input stays visible.
1386
+ internally) and applies it as bottom padding so the focused text
1387
+ input stays visible.
1088
1388
 
1089
1389
  Args:
1090
1390
  *children: Children rendered inside the avoiding container.
@@ -1095,32 +1395,36 @@ def KeyboardAvoidingView(
1095
1395
 
1096
1396
  Returns:
1097
1397
  An [`Element`][pythonnative.Element] of type
1098
- `"KeyboardAvoidingView"`.
1398
+ ``"KeyboardAvoidingView"``.
1099
1399
  """
1100
- props: Dict[str, Any] = {"behavior": behavior}
1101
- props.update(resolve_style(style))
1102
- return Element("KeyboardAvoidingView", props, list(children), key=key)
1400
+ return _make_element(
1401
+ "KeyboardAvoidingView",
1402
+ *children,
1403
+ style=style,
1404
+ key=key,
1405
+ behavior=behavior,
1406
+ )
1103
1407
 
1104
1408
 
1105
1409
  def RefreshControl(
1106
1410
  *,
1107
1411
  refreshing: bool = False,
1108
1412
  on_refresh: Optional[Callable[[], None]] = None,
1109
- tint_color: Optional[str] = None,
1413
+ tint_color: Optional[Color] = None,
1110
1414
  ) -> Dict[str, Any]:
1111
1415
  """Pull-to-refresh spec for [`ScrollView`][pythonnative.ScrollView] / [`FlatList`][pythonnative.FlatList].
1112
1416
 
1113
1417
  Returns a plain dict that should be passed as the
1114
- ``refresh_control=`` prop. Modeled as a dict (not an Element) so
1115
- the host scroll container can hold one without it appearing as a
1116
- child node.
1418
+ ``refresh_control=`` prop. Modeled as a dict (not an
1419
+ [`Element`][pythonnative.Element]) so the host scroll container can
1420
+ hold one without it appearing as a child node.
1117
1421
 
1118
1422
  Args:
1119
1423
  refreshing: Drive the spinner's visibility from a use_state
1120
1424
  value.
1121
- on_refresh: Callback invoked when the user pulls down past
1122
- the threshold. Set ``refreshing`` to True for the
1123
- duration of the work, then back to False on completion.
1425
+ on_refresh: Callback invoked when the user pulls down past the
1426
+ threshold. Set ``refreshing`` to ``True`` for the duration
1427
+ of the work, then back to ``False`` on completion.
1124
1428
  tint_color: Color of the spinner.
1125
1429
 
1126
1430
  Returns:
@@ -1162,54 +1466,50 @@ def Picker(
1162
1466
  items: Optional[List[Dict[str, Any]]] = None,
1163
1467
  on_change: Optional[Callable[[Any], None]] = None,
1164
1468
  placeholder: str = "Select…",
1165
- style: StyleValue = None,
1469
+ style: StyleProp = None,
1470
+ accessibility_label: Optional[str] = None,
1471
+ accessibility_hint: Optional[str] = None,
1472
+ accessible: Optional[bool] = None,
1473
+ ref: Optional[Dict[str, Any]] = None,
1166
1474
  key: Optional[str] = None,
1167
1475
  ) -> Element:
1168
- """A select / dropdown widget.
1476
+ """A real native dropdown / select widget.
1477
+
1478
+ Renders a tappable trigger labelled with the selected item; the
1479
+ iOS handler attaches a ``UIMenu`` (system dropdown) and the Android
1480
+ handler uses a native ``Spinner``. Selecting an item fires
1481
+ ``on_change(value)``.
1169
1482
 
1170
- Implemented as a plain
1171
- [`Pressable`][pythonnative.Pressable] that, on tap, presents an
1172
- [`Alert`][pythonnative.Alert]-style action sheet listing the
1173
- options. Selecting an option fires ``on_change(value)``.
1483
+ ``items`` is an ordered list of ``{"value": Any, "label": str}``
1484
+ entries (``label`` defaults to ``str(value)`` when omitted).
1174
1485
 
1175
1486
  Args:
1176
1487
  value: Currently selected value (matched against
1177
1488
  ``items[i]["value"]``).
1178
- items: Each item is ``{"value": ..., "label": ...}``.
1179
- on_change: Callback invoked with the selected value.
1180
- placeholder: Label shown when nothing is selected.
1181
- style: Style dict applied to the trigger pressable.
1489
+ items: Selectable options.
1490
+ on_change: Callback invoked with the new value.
1491
+ placeholder: Label shown when no item matches ``value``.
1492
+ style: Style dict applied to the trigger.
1493
+ accessibility_label: Spoken description for screen readers.
1494
+ accessibility_hint: Spoken extra detail (iOS only).
1495
+ accessible: Override whether the element is exposed to AT.
1496
+ ref: Optional ``use_ref()`` dict.
1182
1497
  key: Stable identity for keyed reconciliation.
1183
1498
 
1184
1499
  Returns:
1185
- An [`Element`][pythonnative.Element] of type `"Pressable"`.
1500
+ An [`Element`][pythonnative.Element] of type ``"Picker"``.
1186
1501
  """
1187
- items_list = list(items or [])
1188
- selected_label = placeholder
1189
- for it in items_list:
1190
- if it.get("value") == value:
1191
- selected_label = str(it.get("label", value))
1192
- break
1193
-
1194
- def _open() -> None:
1195
- try:
1196
- from .alerts import Alert
1197
- except Exception:
1198
- return
1199
-
1200
- def _make_btn(item: Dict[str, Any]) -> Dict[str, Any]:
1201
- def _press() -> None:
1202
- if on_change is not None:
1203
- try:
1204
- on_change(item.get("value"))
1205
- except Exception:
1206
- pass
1207
-
1208
- return {"label": str(item.get("label", item.get("value"))), "on_press": _press}
1209
-
1210
- buttons = [_make_btn(it) for it in items_list]
1211
- buttons.append({"label": "Cancel", "style": "cancel"})
1212
- Alert.show(title=placeholder, buttons=buttons, style="action_sheet")
1213
-
1214
- label_text = Text(selected_label)
1215
- return Pressable(label_text, on_press=_open, style=style, key=key)
1502
+ return _make_element(
1503
+ "Picker",
1504
+ style=style,
1505
+ ref=ref,
1506
+ key=key,
1507
+ value=value,
1508
+ items=list(items) if items is not None else [],
1509
+ on_change=on_change,
1510
+ placeholder=placeholder,
1511
+ accessibility_label=accessibility_label,
1512
+ accessibility_hint=accessibility_hint,
1513
+ accessible=accessible,
1514
+ _defaults={"accessibility_role": "button"},
1515
+ )