pythonnative 0.17.0__py3-none-any.whl → 0.18.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.
@@ -151,6 +151,12 @@ class TextInputProps(Props):
151
151
  return_key_type: Optional[ReturnKeyType] = None
152
152
  max_length: Optional[int] = None
153
153
  placeholder_color: Optional[Color] = None
154
+ editable: bool = True
155
+ clear_button: bool = False
156
+ on_focus: Optional[Callable[[], None]] = None
157
+ on_blur: Optional[Callable[[], None]] = None
158
+ selection_color: Optional[Color] = None
159
+ text_content_type: Optional[str] = None
154
160
  accessibility_label: Optional[str] = None
155
161
  accessibility_hint: Optional[str] = None
156
162
  accessible: Optional[bool] = None
@@ -181,6 +187,9 @@ class ProgressBarProps(Props):
181
187
  """Props for [`ProgressBar`][pythonnative.ProgressBar]."""
182
188
 
183
189
  value: float = 0.0
190
+ color: Optional[Color] = None
191
+ track_color: Optional[Color] = None
192
+ indeterminate: bool = False
184
193
 
185
194
 
186
195
  @dataclass(frozen=True)
@@ -188,6 +197,8 @@ class ActivityIndicatorProps(Props):
188
197
  """Props for [`ActivityIndicator`][pythonnative.ActivityIndicator]."""
189
198
 
190
199
  animating: bool = True
200
+ color: Optional[Color] = None
201
+ size: Literal["small", "large"] = "small"
191
202
 
192
203
 
193
204
  @dataclass(frozen=True)
@@ -195,6 +206,12 @@ class WebViewProps(Props):
195
206
  """Props for [`WebView`][pythonnative.WebView]."""
196
207
 
197
208
  url: Optional[str] = None
209
+ html: Optional[str] = None
210
+ on_load: Optional[Callable[[str], None]] = None
211
+ on_message: Optional[Callable[[str], None]] = None
212
+ on_navigation_state_change: Optional[Callable[[str], None]] = None
213
+ inject_javascript: Optional[str] = None
214
+ scroll_enabled: bool = True
198
215
 
199
216
 
200
217
  @dataclass(frozen=True)
@@ -231,6 +248,12 @@ class ScrollViewProps(Props):
231
248
 
232
249
  refresh_control: Optional[Dict[str, Any]] = None
233
250
  scroll_axis: Optional[Literal["vertical", "horizontal"]] = None
251
+ on_scroll: Optional[Callable[[float, float], None]] = None
252
+ shows_scroll_indicator: bool = True
253
+ paging_enabled: bool = False
254
+ bounces: bool = True
255
+ content_container_style: StyleProp = None
256
+ keyboard_dismiss_mode: Optional[Literal["none", "on_drag", "interactive"]] = None
234
257
 
235
258
 
236
259
  @dataclass(frozen=True)
@@ -244,9 +267,12 @@ class ModalProps(Props):
244
267
 
245
268
  visible: bool = False
246
269
  on_dismiss: Optional[Callable[[], None]] = None
270
+ on_show: Optional[Callable[[], None]] = None
247
271
  title: Optional[str] = None
248
272
  animation_type: Literal["slide", "fade", "none"] = "slide"
249
273
  transparent: bool = False
274
+ presentation_style: Literal["page_sheet", "form_sheet", "full_screen", "overlay"] = "page_sheet"
275
+ dismiss_on_backdrop: bool = True
250
276
 
251
277
 
252
278
  @dataclass(frozen=True)
@@ -296,6 +322,77 @@ class PickerProps(Props):
296
322
  accessible: Optional[bool] = None
297
323
 
298
324
 
325
+ @dataclass(frozen=True)
326
+ class TouchableOpacityProps(Props):
327
+ """Props for [`TouchableOpacity`][pythonnative.TouchableOpacity]."""
328
+
329
+ on_press: Optional[Callable[[], None]] = None
330
+ on_long_press: Optional[Callable[[], None]] = None
331
+ active_opacity: float = 0.2
332
+ disabled: bool = False
333
+ accessibility_label: Optional[str] = None
334
+ accessibility_hint: Optional[str] = None
335
+ accessibility_role: Optional[str] = None
336
+ accessible: Optional[bool] = None
337
+
338
+
339
+ @dataclass(frozen=True)
340
+ class ImageBackgroundProps(Props):
341
+ """Props for [`ImageBackground`][pythonnative.ImageBackground]."""
342
+
343
+ source: Optional[str] = None
344
+ scale_type: Optional[ScaleType] = None
345
+ accessibility_label: Optional[str] = None
346
+ accessible: Optional[bool] = None
347
+
348
+
349
+ @dataclass(frozen=True)
350
+ class CheckboxProps(Props):
351
+ """Props for [`Checkbox`][pythonnative.Checkbox]."""
352
+
353
+ value: bool = False
354
+ on_change: Optional[Callable[[bool], None]] = None
355
+ label: Optional[str] = None
356
+ disabled: bool = False
357
+ color: Optional[Color] = None
358
+ accessibility_label: Optional[str] = None
359
+ accessibility_hint: Optional[str] = None
360
+ accessible: Optional[bool] = None
361
+
362
+
363
+ @dataclass(frozen=True)
364
+ class SegmentedControlProps(Props):
365
+ """Props for [`SegmentedControl`][pythonnative.SegmentedControl]."""
366
+
367
+ segments: List[str] = field(default_factory=list)
368
+ selected_index: int = 0
369
+ on_change: Optional[Callable[[int], None]] = None
370
+ enabled: bool = True
371
+ tint_color: Optional[Color] = None
372
+ accessibility_label: Optional[str] = None
373
+ accessible: Optional[bool] = None
374
+
375
+
376
+ @dataclass(frozen=True)
377
+ class DatePickerProps(Props):
378
+ """Props for [`DatePicker`][pythonnative.DatePicker].
379
+
380
+ ``value`` and the value passed to ``on_change`` are ISO-8601
381
+ strings (``"2026-05-31"`` for ``mode="date"``, ``"14:30"`` for
382
+ ``mode="time"``, ``"2026-05-31T14:30"`` for ``mode="datetime"``),
383
+ so the schema stays JSON-serializable and platform-agnostic.
384
+ """
385
+
386
+ value: Optional[str] = None
387
+ mode: Literal["date", "time", "datetime"] = "date"
388
+ on_change: Optional[Callable[[str], None]] = None
389
+ minimum: Optional[str] = None
390
+ maximum: Optional[str] = None
391
+ enabled: bool = True
392
+ accessibility_label: Optional[str] = None
393
+ accessible: Optional[bool] = None
394
+
395
+
299
396
  # ======================================================================
300
397
  # Leaf factories
301
398
  # ======================================================================
@@ -416,6 +513,12 @@ def TextInput(
416
513
  return_key_type: Optional[ReturnKeyType] = None,
417
514
  max_length: Optional[int] = None,
418
515
  placeholder_color: Optional[Color] = None,
516
+ editable: bool = True,
517
+ clear_button: bool = False,
518
+ on_focus: Optional[Callable[[], None]] = None,
519
+ on_blur: Optional[Callable[[], None]] = None,
520
+ selection_color: Optional[Color] = None,
521
+ text_content_type: Optional[str] = None,
419
522
  style: StyleProp = None,
420
523
  accessibility_label: Optional[str] = None,
421
524
  accessibility_hint: Optional[str] = None,
@@ -446,6 +549,16 @@ def TextInput(
446
549
  ``"next"``, ``"send"``, ``"search"``.
447
550
  max_length: Maximum number of characters allowed.
448
551
  placeholder_color: Color used for the placeholder string.
552
+ editable: When ``False``, the field is read-only (still
553
+ selectable).
554
+ clear_button: When ``True``, shows a clear ("x") button while
555
+ editing (iOS ``clearButtonMode``; an inline button on
556
+ Android).
557
+ on_focus: Callback invoked when the field gains focus.
558
+ on_blur: Callback invoked when the field loses focus.
559
+ selection_color: Cursor / selection highlight color.
560
+ text_content_type: Semantic content hint for autofill (e.g.
561
+ ``"username"``, ``"password"``, ``"one_time_code"``).
449
562
  style: Style dict (or list of dicts).
450
563
  accessibility_label: Spoken description for screen readers.
451
564
  accessibility_hint: Spoken extra detail (iOS only).
@@ -474,6 +587,12 @@ def TextInput(
474
587
  return_key_type=return_key_type,
475
588
  max_length=max_length,
476
589
  placeholder_color=placeholder_color,
590
+ editable=False if editable is False else None,
591
+ clear_button=clear_button or None,
592
+ on_focus=on_focus,
593
+ on_blur=on_blur,
594
+ selection_color=selection_color,
595
+ text_content_type=text_content_type,
477
596
  accessibility_label=accessibility_label,
478
597
  accessibility_hint=accessibility_hint,
479
598
  accessible=accessible,
@@ -563,17 +682,25 @@ def Switch(
563
682
  def ProgressBar(
564
683
  *,
565
684
  value: float = 0.0,
685
+ color: Optional[Color] = None,
686
+ track_color: Optional[Color] = None,
687
+ indeterminate: bool = False,
566
688
  style: StyleProp = None,
567
689
  key: Optional[str] = None,
568
690
  ) -> Element:
569
691
  """Show determinate progress as a value between ``0.0`` and ``1.0``.
570
692
 
571
- For indeterminate progress, use
572
- [`ActivityIndicator`][pythonnative.ActivityIndicator] instead.
693
+ For a spinner instead of a bar, use
694
+ [`ActivityIndicator`][pythonnative.ActivityIndicator]; for an
695
+ indeterminate *bar* pass ``indeterminate=True``.
573
696
 
574
697
  Args:
575
698
  value: Fraction complete (clamped to ``[0.0, 1.0]`` by the
576
699
  platform handler).
700
+ color: Color of the filled portion of the bar.
701
+ track_color: Color of the unfilled track behind the fill.
702
+ indeterminate: When ``True``, the bar animates continuously and
703
+ ``value`` is ignored.
577
704
  style: Style dict (or list of dicts).
578
705
  key: Stable identity for keyed reconciliation.
579
706
 
@@ -585,12 +712,17 @@ def ProgressBar(
585
712
  style=style,
586
713
  key=key,
587
714
  value=value,
715
+ color=color,
716
+ track_color=track_color,
717
+ indeterminate=indeterminate or None,
588
718
  )
589
719
 
590
720
 
591
721
  def ActivityIndicator(
592
722
  *,
593
723
  animating: bool = True,
724
+ color: Optional[Color] = None,
725
+ size: Literal["small", "large"] = "small",
594
726
  style: StyleProp = None,
595
727
  key: Optional[str] = None,
596
728
  ) -> Element:
@@ -598,6 +730,8 @@ def ActivityIndicator(
598
730
 
599
731
  Args:
600
732
  animating: When ``False``, the spinner is hidden.
733
+ color: Spinner color.
734
+ size: ``"small"`` (default) or ``"large"``.
601
735
  style: Style dict (or list of dicts).
602
736
  key: Stable identity for keyed reconciliation.
603
737
 
@@ -610,19 +744,40 @@ def ActivityIndicator(
610
744
  style=style,
611
745
  key=key,
612
746
  animating=animating,
747
+ color=color,
748
+ size=size,
613
749
  )
614
750
 
615
751
 
616
752
  def WebView(
617
753
  *,
618
754
  url: str = "",
755
+ html: Optional[str] = None,
756
+ on_load: Optional[Callable[[str], None]] = None,
757
+ on_message: Optional[Callable[[str], None]] = None,
758
+ on_navigation_state_change: Optional[Callable[[str], None]] = None,
759
+ inject_javascript: Optional[str] = None,
760
+ scroll_enabled: bool = True,
619
761
  style: StyleProp = None,
620
762
  key: Optional[str] = None,
621
763
  ) -> Element:
622
- """Embed web content from a URL.
764
+ """Embed web content from a URL or an inline HTML string.
623
765
 
624
766
  Args:
625
- url: HTTP(S) URL to load.
767
+ url: HTTP(S) URL to load. Ignored when ``html`` is given.
768
+ html: Inline HTML markup to render instead of loading a URL.
769
+ on_load: Callback invoked with the final URL once a page
770
+ finishes loading.
771
+ on_message: Callback invoked with the string payload whenever
772
+ page JavaScript calls
773
+ ``window.pythonnative.postMessage(...)``.
774
+ on_navigation_state_change: Callback invoked with the URL each
775
+ time the top-level document begins navigating.
776
+ inject_javascript: JavaScript evaluated after each page load
777
+ (useful for installing the ``postMessage`` bridge or
778
+ tweaking the DOM).
779
+ scroll_enabled: When ``False``, disables scrolling inside the
780
+ web content.
626
781
  style: Style dict (or list of dicts).
627
782
  key: Stable identity for keyed reconciliation.
628
783
 
@@ -634,6 +789,12 @@ def WebView(
634
789
  style=style,
635
790
  key=key,
636
791
  url=url or None,
792
+ html=html,
793
+ on_load=on_load,
794
+ on_message=on_message,
795
+ on_navigation_state_change=on_navigation_state_change,
796
+ inject_javascript=inject_javascript,
797
+ scroll_enabled=False if scroll_enabled is False else None,
637
798
  )
638
799
 
639
800
 
@@ -833,6 +994,12 @@ def ScrollView(
833
994
  *children: Element,
834
995
  refresh_control: Optional[Dict[str, Any]] = None,
835
996
  scroll_axis: Optional[Literal["vertical", "horizontal"]] = None,
997
+ on_scroll: Optional[Callable[[float, float], None]] = None,
998
+ shows_scroll_indicator: bool = True,
999
+ paging_enabled: bool = False,
1000
+ bounces: bool = True,
1001
+ content_container_style: StyleProp = None,
1002
+ keyboard_dismiss_mode: Optional[Literal["none", "on_drag", "interactive"]] = None,
836
1003
  style: StyleProp = None,
837
1004
  ref: Optional[Dict[str, Any]] = None,
838
1005
  key: Optional[str] = None,
@@ -852,6 +1019,18 @@ def ScrollView(
852
1019
  must have ``refreshing`` (bool) and ``on_refresh``
853
1020
  (callable).
854
1021
  scroll_axis: ``"vertical"`` (default) or ``"horizontal"``.
1022
+ on_scroll: Callback invoked with ``(x, y)`` content offsets as
1023
+ the user scrolls.
1024
+ shows_scroll_indicator: When ``False``, hides the scroll bar.
1025
+ paging_enabled: When ``True``, the scroll view snaps to
1026
+ multiples of its own size (carousel behavior).
1027
+ bounces: When ``False``, disables the iOS rubber-band overscroll.
1028
+ content_container_style: Style applied to the inner content
1029
+ wrapper (padding, alignment, spacing of the scrollable
1030
+ content), distinct from ``style`` (the scroll view frame).
1031
+ keyboard_dismiss_mode: ``"none"`` (default), ``"on_drag"``, or
1032
+ ``"interactive"`` — controls whether scrolling dismisses
1033
+ the keyboard.
855
1034
  style: Style dict (or list of dicts).
856
1035
  ref: Optional ``use_ref()`` dict.
857
1036
  key: Stable identity for keyed reconciliation.
@@ -867,6 +1046,12 @@ def ScrollView(
867
1046
  key=key,
868
1047
  refresh_control=refresh_control,
869
1048
  scroll_axis=scroll_axis,
1049
+ on_scroll=on_scroll,
1050
+ shows_scroll_indicator=False if shows_scroll_indicator is False else None,
1051
+ paging_enabled=paging_enabled or None,
1052
+ bounces=False if bounces is False else None,
1053
+ content_container_style=resolve_style(content_container_style) or None,
1054
+ keyboard_dismiss_mode=keyboard_dismiss_mode,
870
1055
  )
871
1056
 
872
1057
 
@@ -897,9 +1082,12 @@ def Modal(
897
1082
  *children: Element,
898
1083
  visible: bool = False,
899
1084
  on_dismiss: Optional[Callable[[], None]] = None,
1085
+ on_show: Optional[Callable[[], None]] = None,
900
1086
  title: Optional[str] = None,
901
1087
  animation_type: Literal["slide", "fade", "none"] = "slide",
902
1088
  transparent: bool = False,
1089
+ presentation_style: Literal["page_sheet", "form_sheet", "full_screen", "overlay"] = "page_sheet",
1090
+ dismiss_on_backdrop: bool = True,
903
1091
  style: StyleProp = None,
904
1092
  key: Optional[str] = None,
905
1093
  ) -> Element:
@@ -919,10 +1107,20 @@ def Modal(
919
1107
  visible: Controls whether the modal is presented.
920
1108
  on_dismiss: Callback invoked when the user dismisses the modal
921
1109
  via system gesture.
1110
+ on_show: Callback invoked once the modal has finished
1111
+ presenting.
922
1112
  title: Optional title-bar text.
923
1113
  animation_type: ``"slide"`` (default), ``"fade"``, or ``"none"``.
924
1114
  transparent: When ``True``, the underlying view is dimmed
925
1115
  instead of fully covered.
1116
+ presentation_style: iOS presentation style —
1117
+ ``"page_sheet"`` (default), ``"form_sheet"``,
1118
+ ``"full_screen"``, or ``"overlay"`` (custom dimmed
1119
+ overlay). On Android, ``"overlay"`` keeps the dialog
1120
+ non-fullscreen.
1121
+ dismiss_on_backdrop: When ``True`` (default) and
1122
+ ``transparent`` / ``"overlay"``, tapping the dimmed
1123
+ backdrop dismisses the modal.
926
1124
  style: Style dict (or list of dicts).
927
1125
  key: Stable identity for keyed reconciliation.
928
1126
 
@@ -937,7 +1135,10 @@ def Modal(
937
1135
  visible=visible,
938
1136
  animation_type=animation_type,
939
1137
  transparent=transparent,
1138
+ presentation_style=presentation_style,
1139
+ dismiss_on_backdrop=False if dismiss_on_backdrop is False else None,
940
1140
  on_dismiss=on_dismiss,
1141
+ on_show=on_show,
941
1142
  title=title,
942
1143
  )
943
1144
 
@@ -994,6 +1195,264 @@ def Pressable(
994
1195
  )
995
1196
 
996
1197
 
1198
+ # ======================================================================
1199
+ # Touchables & controls
1200
+ # ======================================================================
1201
+
1202
+
1203
+ def TouchableOpacity(
1204
+ *children: Element,
1205
+ on_press: Optional[Callable[[], None]] = None,
1206
+ on_long_press: Optional[Callable[[], None]] = None,
1207
+ active_opacity: float = 0.2,
1208
+ disabled: bool = False,
1209
+ style: StyleProp = None,
1210
+ accessibility_label: Optional[str] = None,
1211
+ accessibility_hint: Optional[str] = None,
1212
+ accessibility_role: Optional[str] = None,
1213
+ accessible: Optional[bool] = None,
1214
+ key: Optional[str] = None,
1215
+ ) -> Element:
1216
+ """Wrap children so they fade to ``active_opacity`` while pressed.
1217
+
1218
+ A thin ergonomic alias over [`Pressable`][pythonnative.Pressable]
1219
+ that mirrors React Native's ``TouchableOpacity``: the only visual
1220
+ feedback is an opacity dip on touch-down. When ``disabled`` is set,
1221
+ the press callbacks are dropped so the wrapper is inert.
1222
+
1223
+ Args:
1224
+ *children: Elements to make tappable.
1225
+ on_press: Callback invoked on a normal tap.
1226
+ on_long_press: Callback invoked on a sustained press.
1227
+ active_opacity: Opacity (0–1) applied while the finger is down.
1228
+ disabled: When ``True``, ignores presses and renders at reduced
1229
+ opacity.
1230
+ style: Style dict applied to the wrapper.
1231
+ accessibility_label: Spoken description for screen readers.
1232
+ accessibility_hint: Spoken extra detail (iOS only).
1233
+ accessibility_role: Override the default ``"button"`` role.
1234
+ accessible: Override whether the element is exposed to AT.
1235
+ key: Stable identity for keyed reconciliation.
1236
+
1237
+ Returns:
1238
+ An [`Element`][pythonnative.Element] of type ``"Pressable"``.
1239
+ """
1240
+ merged_style: StyleProp
1241
+ if disabled:
1242
+ base = resolve_style(style)
1243
+ base.setdefault("opacity", 0.4)
1244
+ merged_style = base
1245
+ else:
1246
+ merged_style = style
1247
+ return Pressable(
1248
+ *children,
1249
+ on_press=None if disabled else on_press,
1250
+ on_long_press=None if disabled else on_long_press,
1251
+ pressed_opacity=active_opacity,
1252
+ style=merged_style,
1253
+ accessibility_label=accessibility_label,
1254
+ accessibility_hint=accessibility_hint,
1255
+ accessibility_role=accessibility_role,
1256
+ accessible=accessible,
1257
+ key=key,
1258
+ )
1259
+
1260
+
1261
+ def ImageBackground(
1262
+ *children: Element,
1263
+ source: str = "",
1264
+ scale_type: Optional[ScaleType] = None,
1265
+ style: StyleProp = None,
1266
+ accessibility_label: Optional[str] = None,
1267
+ accessible: Optional[bool] = None,
1268
+ key: Optional[str] = None,
1269
+ ) -> Element:
1270
+ """Render ``children`` layered on top of a background image.
1271
+
1272
+ Composed entirely from existing primitives: an absolutely-filled
1273
+ [`Image`][pythonnative.Image] sits behind a content
1274
+ [`View`][pythonnative.View] holding ``children``. The container's
1275
+ ``style`` controls sizing/padding; the image stretches to fill it
1276
+ via ``position: "absolute"`` and zeroed insets.
1277
+
1278
+ Args:
1279
+ *children: Foreground content drawn over the image.
1280
+ source: Image resource name or URL.
1281
+ scale_type: Background fit mode (``"cover"`` is the most common
1282
+ for backgrounds).
1283
+ style: Style dict for the container (size, padding, alignment).
1284
+ accessibility_label: Spoken description of the background image.
1285
+ accessible: Override whether the image is exposed to AT.
1286
+ key: Stable identity for keyed reconciliation.
1287
+
1288
+ Returns:
1289
+ An [`Element`][pythonnative.Element] of type ``"View"`` wrapping
1290
+ the background image and foreground content.
1291
+ """
1292
+ fill = {"position": "absolute", "top": 0, "left": 0, "right": 0, "bottom": 0}
1293
+ background = Image(
1294
+ source,
1295
+ scale_type=scale_type or "cover",
1296
+ style=fill,
1297
+ accessibility_label=accessibility_label,
1298
+ accessible=accessible,
1299
+ )
1300
+ content = View(*children, style={"flex": 1})
1301
+ return View(
1302
+ background,
1303
+ content,
1304
+ style=[{"overflow": "hidden"}, resolve_style(style)],
1305
+ key=key,
1306
+ )
1307
+
1308
+
1309
+ def Checkbox(
1310
+ *,
1311
+ value: bool = False,
1312
+ on_change: Optional[Callable[[bool], None]] = None,
1313
+ label: Optional[str] = None,
1314
+ disabled: bool = False,
1315
+ color: Optional[Color] = None,
1316
+ style: StyleProp = None,
1317
+ accessibility_label: Optional[str] = None,
1318
+ accessibility_hint: Optional[str] = None,
1319
+ accessible: Optional[bool] = None,
1320
+ key: Optional[str] = None,
1321
+ ) -> Element:
1322
+ """A boolean checkbox with an optional inline label.
1323
+
1324
+ Backed by ``android.widget.CheckBox`` on Android and a checkmark
1325
+ ``UIButton`` on iOS. Tapping the control (or its label) toggles the
1326
+ value and fires ``on_change(new_value)``.
1327
+
1328
+ Args:
1329
+ value: Current checked state.
1330
+ on_change: Callback invoked with the new boolean state.
1331
+ label: Optional text shown beside the box (also tappable).
1332
+ disabled: When ``True``, the control is greyed out and inert.
1333
+ color: Tint applied to the checked box.
1334
+ style: Style dict (or list of dicts).
1335
+ accessibility_label: Spoken description for screen readers.
1336
+ accessibility_hint: Spoken extra detail (iOS only).
1337
+ accessible: Override whether the element is exposed to AT.
1338
+ key: Stable identity for keyed reconciliation.
1339
+
1340
+ Returns:
1341
+ An [`Element`][pythonnative.Element] of type ``"Checkbox"``.
1342
+ """
1343
+ return _make_element(
1344
+ "Checkbox",
1345
+ style=style,
1346
+ key=key,
1347
+ value=value,
1348
+ on_change=on_change,
1349
+ label=label,
1350
+ disabled=disabled or None,
1351
+ color=color,
1352
+ accessibility_label=accessibility_label,
1353
+ accessibility_hint=accessibility_hint,
1354
+ accessible=accessible,
1355
+ _defaults={"accessibility_role": "checkbox"},
1356
+ )
1357
+
1358
+
1359
+ def SegmentedControl(
1360
+ *,
1361
+ segments: Optional[List[str]] = None,
1362
+ selected_index: int = 0,
1363
+ on_change: Optional[Callable[[int], None]] = None,
1364
+ enabled: bool = True,
1365
+ tint_color: Optional[Color] = None,
1366
+ style: StyleProp = None,
1367
+ accessibility_label: Optional[str] = None,
1368
+ accessible: Optional[bool] = None,
1369
+ key: Optional[str] = None,
1370
+ ) -> Element:
1371
+ """A horizontal multi-choice control (one selected segment at a time).
1372
+
1373
+ Backed by ``UISegmentedControl`` on iOS and a styled toggle row on
1374
+ Android. Selecting a segment fires ``on_change(index)``.
1375
+
1376
+ Args:
1377
+ segments: Ordered list of segment labels.
1378
+ selected_index: Index of the currently selected segment.
1379
+ on_change: Callback invoked with the newly selected index.
1380
+ enabled: When ``False``, the control is disabled.
1381
+ tint_color: Accent color for the selected segment.
1382
+ style: Style dict (or list of dicts).
1383
+ accessibility_label: Spoken description for screen readers.
1384
+ accessible: Override whether the element is exposed to AT.
1385
+ key: Stable identity for keyed reconciliation.
1386
+
1387
+ Returns:
1388
+ An [`Element`][pythonnative.Element] of type
1389
+ ``"SegmentedControl"``.
1390
+ """
1391
+ return _make_element(
1392
+ "SegmentedControl",
1393
+ style=style,
1394
+ key=key,
1395
+ segments=list(segments) if segments is not None else [],
1396
+ selected_index=selected_index,
1397
+ on_change=on_change,
1398
+ enabled=False if enabled is False else None,
1399
+ tint_color=tint_color,
1400
+ accessibility_label=accessibility_label,
1401
+ accessible=accessible,
1402
+ )
1403
+
1404
+
1405
+ def DatePicker(
1406
+ *,
1407
+ value: Optional[str] = None,
1408
+ mode: Literal["date", "time", "datetime"] = "date",
1409
+ on_change: Optional[Callable[[str], None]] = None,
1410
+ minimum: Optional[str] = None,
1411
+ maximum: Optional[str] = None,
1412
+ enabled: bool = True,
1413
+ style: StyleProp = None,
1414
+ accessibility_label: Optional[str] = None,
1415
+ accessible: Optional[bool] = None,
1416
+ key: Optional[str] = None,
1417
+ ) -> Element:
1418
+ """A native date / time picker.
1419
+
1420
+ Backed by ``UIDatePicker`` on iOS and a trigger button that opens
1421
+ the platform ``DatePickerDialog`` / ``TimePickerDialog`` on
1422
+ Android. ``value`` and the value reported to ``on_change`` are
1423
+ ISO-8601 strings (see [`DatePickerProps`][pythonnative.DatePickerProps]).
1424
+
1425
+ Args:
1426
+ value: Currently selected value as an ISO-8601 string.
1427
+ mode: ``"date"`` (default), ``"time"``, or ``"datetime"``.
1428
+ on_change: Callback invoked with the new ISO-8601 string.
1429
+ minimum: Earliest selectable value (ISO-8601), if any.
1430
+ maximum: Latest selectable value (ISO-8601), if any.
1431
+ enabled: When ``False``, the picker is disabled.
1432
+ style: Style dict (or list of dicts).
1433
+ accessibility_label: Spoken description for screen readers.
1434
+ accessible: Override whether the element is exposed to AT.
1435
+ key: Stable identity for keyed reconciliation.
1436
+
1437
+ Returns:
1438
+ An [`Element`][pythonnative.Element] of type ``"DatePicker"``.
1439
+ """
1440
+ return _make_element(
1441
+ "DatePicker",
1442
+ style=style,
1443
+ key=key,
1444
+ value=value,
1445
+ mode=mode,
1446
+ on_change=on_change,
1447
+ minimum=minimum,
1448
+ maximum=maximum,
1449
+ enabled=False if enabled is False else None,
1450
+ accessibility_label=accessibility_label,
1451
+ accessible=accessible,
1452
+ _defaults={"accessibility_role": "button"},
1453
+ )
1454
+
1455
+
997
1456
  # ======================================================================
998
1457
  # Fragment
999
1458
  # ======================================================================
@@ -1108,6 +1567,14 @@ def FlatList(
1108
1567
  separator_height: float = 0,
1109
1568
  refresh_control: Optional[Dict[str, Any]] = None,
1110
1569
  on_item_press: Optional[Callable[[int], None]] = None,
1570
+ horizontal: bool = False,
1571
+ num_columns: int = 1,
1572
+ list_header: Optional[Element] = None,
1573
+ list_footer: Optional[Element] = None,
1574
+ list_empty: Optional[Element] = None,
1575
+ on_end_reached: Optional[Callable[[], None]] = None,
1576
+ on_end_reached_threshold: float = 0.5,
1577
+ content_container_style: StyleProp = None,
1111
1578
  style: StyleProp = None,
1112
1579
  key: Optional[str] = None,
1113
1580
  ) -> Element:
@@ -1141,6 +1608,20 @@ def FlatList(
1141
1608
  [`RefreshControl`][pythonnative.RefreshControl].
1142
1609
  on_item_press: Callback invoked with the row index when the
1143
1610
  user taps a row (virtualized backend only).
1611
+ horizontal: Lay rows out left-to-right instead of top-to-bottom
1612
+ (forces the eager backend).
1613
+ num_columns: Render items in a grid of this many columns
1614
+ (forces the eager backend). ``1`` (default) is a plain list.
1615
+ list_header: Optional element rendered once above all rows.
1616
+ list_footer: Optional element rendered once below all rows.
1617
+ list_empty: Optional element rendered when ``data`` is empty.
1618
+ on_end_reached: Callback invoked when the user scrolls within
1619
+ ``on_end_reached_threshold`` of the end (virtualized
1620
+ backend).
1621
+ on_end_reached_threshold: Fraction of the viewport from the end
1622
+ at which ``on_end_reached`` fires.
1623
+ content_container_style: Style applied to the inner content
1624
+ wrapper (forces the eager backend).
1144
1625
  style: Style dict (or list of dicts).
1145
1626
  key: Stable identity for keyed reconciliation of the list
1146
1627
  itself.
@@ -1165,16 +1646,70 @@ def FlatList(
1165
1646
  """
1166
1647
  items_list = list(data or [])
1167
1648
 
1168
- if item_height is None:
1169
- # Eager fallback for short lists.
1170
- items_eager: List[Element] = []
1649
+ has_ornaments = (
1650
+ num_columns > 1
1651
+ or horizontal
1652
+ or list_header is not None
1653
+ or list_footer is not None
1654
+ or content_container_style is not None
1655
+ or (not items_list and list_empty is not None)
1656
+ )
1657
+
1658
+ if item_height is None or has_ornaments:
1659
+ # Eager fallback for short lists, grids, and lists with
1660
+ # header/footer/empty ornaments (which the fixed-height
1661
+ # virtualizer can't express).
1662
+ rendered: List[Element] = []
1171
1663
  for i, item in enumerate(items_list):
1172
1664
  el = render_item(item, i) if render_item else Text(str(item))
1173
1665
  if key_extractor is not None:
1174
1666
  el = Element(el.type, el.props, el.children, key=key_extractor(item, i))
1175
- items_eager.append(el)
1176
- inner = Column(*items_eager, style={"spacing": separator_height} if separator_height else None)
1177
- return ScrollView(inner, refresh_control=refresh_control, style=style, key=key)
1667
+ rendered.append(el)
1668
+
1669
+ sep = separator_height or None
1670
+
1671
+ if not has_ornaments:
1672
+ # Backward-compatible shape: ScrollView wrapping a single
1673
+ # Column of the rendered rows.
1674
+ inner = Column(*rendered, style={"spacing": sep} if sep else None)
1675
+ return ScrollView(inner, refresh_control=refresh_control, style=style, key=key)
1676
+
1677
+ if not rendered and list_empty is not None:
1678
+ content: List[Element] = [list_empty]
1679
+ elif num_columns > 1:
1680
+ rows: List[Element] = []
1681
+ for start in range(0, len(rendered), num_columns):
1682
+ chunk = rendered[start : start + num_columns]
1683
+ rows.append(
1684
+ Row(
1685
+ *chunk,
1686
+ style={"spacing": separator_height, "flex": 1} if separator_height else {"flex": 1},
1687
+ key=f"row-{start}",
1688
+ )
1689
+ )
1690
+ content = rows
1691
+ elif horizontal:
1692
+ content = [Row(*rendered, style={"spacing": sep} if sep else None)]
1693
+ else:
1694
+ content = [Column(*rendered, style={"spacing": sep} if sep else None)]
1695
+
1696
+ body: List[Element] = []
1697
+ if list_header is not None:
1698
+ body.append(list_header)
1699
+ body.extend(content)
1700
+ if list_footer is not None:
1701
+ body.append(list_footer)
1702
+
1703
+ axis: Literal["vertical", "horizontal"] = "horizontal" if horizontal else "vertical"
1704
+ wrapper = Row if horizontal else Column
1705
+ inner = wrapper(*body, style=content_container_style)
1706
+ return ScrollView(
1707
+ inner,
1708
+ refresh_control=refresh_control,
1709
+ scroll_axis=axis,
1710
+ style=style,
1711
+ key=key,
1712
+ )
1178
1713
 
1179
1714
  # Virtualized path: render_item is invoked lazily by the native
1180
1715
  # cell mount callback when each row scrolls into view.
@@ -1223,6 +1758,8 @@ def FlatList(
1223
1758
  row_height=row_h,
1224
1759
  mount_row=_mount_row,
1225
1760
  on_row_press=on_item_press,
1761
+ on_end_reached=on_end_reached,
1762
+ on_end_reached_threshold=on_end_reached_threshold,
1226
1763
  refresh_control=refresh_control,
1227
1764
  )
1228
1765