pythonnative 0.6.0__tar.gz → 0.8.0__tar.gz

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.
Files changed (94) hide show
  1. {pythonnative-0.6.0/src/pythonnative.egg-info → pythonnative-0.8.0}/PKG-INFO +17 -20
  2. {pythonnative-0.6.0 → pythonnative-0.8.0}/README.md +16 -19
  3. {pythonnative-0.6.0 → pythonnative-0.8.0}/pyproject.toml +1 -1
  4. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/__init__.py +30 -19
  5. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/cli/pn.py +10 -18
  6. pythonnative-0.8.0/src/pythonnative/components.py +408 -0
  7. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/hooks.py +174 -21
  8. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/hot_reload.py +2 -2
  9. pythonnative-0.8.0/src/pythonnative/native_views/__init__.py +87 -0
  10. pythonnative-0.8.0/src/pythonnative/native_views/android.py +832 -0
  11. pythonnative-0.8.0/src/pythonnative/native_views/base.py +150 -0
  12. pythonnative-0.8.0/src/pythonnative/native_views/ios.py +777 -0
  13. pythonnative-0.8.0/src/pythonnative/navigation.py +571 -0
  14. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/page.py +178 -167
  15. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/reconciler.py +89 -1
  16. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/style.py +27 -7
  17. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt +2 -9
  18. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +7 -20
  19. {pythonnative-0.6.0 → pythonnative-0.8.0/src/pythonnative.egg-info}/PKG-INFO +17 -20
  20. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative.egg-info/SOURCES.txt +7 -1
  21. {pythonnative-0.6.0 → pythonnative-0.8.0}/tests/test_cli.py +1 -1
  22. {pythonnative-0.6.0 → pythonnative-0.8.0}/tests/test_components.py +120 -17
  23. {pythonnative-0.6.0 → pythonnative-0.8.0}/tests/test_hooks.py +296 -5
  24. pythonnative-0.8.0/tests/test_native_views.py +252 -0
  25. pythonnative-0.8.0/tests/test_navigation.py +846 -0
  26. {pythonnative-0.6.0 → pythonnative-0.8.0}/tests/test_reconciler.py +194 -1
  27. {pythonnative-0.6.0 → pythonnative-0.8.0}/tests/test_smoke.py +13 -1
  28. {pythonnative-0.6.0 → pythonnative-0.8.0}/tests/test_style.py +23 -1
  29. pythonnative-0.6.0/src/pythonnative/components.py +0 -563
  30. pythonnative-0.6.0/src/pythonnative/native_views.py +0 -1334
  31. {pythonnative-0.6.0 → pythonnative-0.8.0}/LICENSE +0 -0
  32. {pythonnative-0.6.0 → pythonnative-0.8.0}/setup.cfg +0 -0
  33. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/cli/__init__.py +0 -0
  34. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/element.py +0 -0
  35. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/native_modules/__init__.py +0 -0
  36. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/native_modules/camera.py +0 -0
  37. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/native_modules/file_system.py +0 -0
  38. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/native_modules/location.py +0 -0
  39. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/native_modules/notifications.py +0 -0
  40. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/build.gradle +0 -0
  41. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
  42. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
  43. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
  44. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
  45. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
  46. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
  47. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
  48. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
  49. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
  50. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
  51. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  52. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  53. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  54. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  55. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  56. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  57. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  58. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  59. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  60. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  61. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
  62. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
  63. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
  64. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
  65. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
  66. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
  67. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
  68. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
  69. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
  70. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  71. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
  72. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
  73. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/gradlew +0 -0
  74. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
  75. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
  76. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
  77. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
  78. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  79. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
  80. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
  81. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -0
  82. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
  83. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
  84. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
  85. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  86. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
  87. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
  88. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
  89. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative/utils.py +0 -0
  90. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
  91. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
  92. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative.egg-info/requires.txt +0 -0
  93. {pythonnative-0.6.0 → pythonnative-0.8.0}/src/pythonnative.egg-info/top_level.txt +0 -0
  94. {pythonnative-0.6.0 → pythonnative-0.8.0}/tests/test_element.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -88,17 +88,18 @@ Dynamic: license-file
88
88
 
89
89
  ## Overview
90
90
 
91
- PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Describe your UI as a tree of elements, manage state with `set_state()`, and let PythonNative handle creating and updating native views.
91
+ PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Write function components with `use_state`, `use_effect`, and friends, just like React, and let PythonNative handle creating and updating native views.
92
92
 
93
93
  ## Features
94
94
 
95
95
  - **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
96
- - **Reactive state:** Call `self.set_state(key=value)` and the framework re-renders only what changed no manual view mutation.
96
+ - **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
97
+ - **`style` prop:** Pass all visual and layout properties through a single `style` dict, composable via `StyleSheet`.
97
98
  - **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
98
99
  - **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
99
100
  - **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
100
- - **Navigation:** Push and pop screens with argument passing for multi-page apps.
101
- - **Bundled templates:** Android Gradle and iOS Xcode templates are included scaffolding requires no network access.
101
+ - **Navigation:** Push and pop screens with argument passing via the `use_navigation()` hook.
102
+ - **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
102
103
 
103
104
  ## Quick Start
104
105
 
@@ -114,21 +115,17 @@ pip install pythonnative
114
115
  import pythonnative as pn
115
116
 
116
117
 
117
- class MainPage(pn.Page):
118
- def __init__(self, native_instance):
119
- super().__init__(native_instance)
120
- self.state = {"count": 0}
121
-
122
- def render(self):
123
- return pn.Column(
124
- pn.Text(f"Count: {self.state['count']}", font_size=24),
125
- pn.Button(
126
- "Tap me",
127
- on_click=lambda: self.set_state(count=self.state["count"] + 1),
128
- ),
129
- spacing=12,
130
- padding=16,
131
- )
118
+ @pn.component
119
+ def MainPage():
120
+ count, set_count = pn.use_state(0)
121
+ return pn.Column(
122
+ pn.Text(f"Count: {count}", style={"font_size": 24}),
123
+ pn.Button(
124
+ "Tap me",
125
+ on_click=lambda: set_count(count + 1),
126
+ ),
127
+ style={"spacing": 12, "padding": 16},
128
+ )
132
129
  ```
133
130
 
134
131
  ## Documentation
@@ -26,17 +26,18 @@
26
26
 
27
27
  ## Overview
28
28
 
29
- PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Describe your UI as a tree of elements, manage state with `set_state()`, and let PythonNative handle creating and updating native views.
29
+ PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Write function components with `use_state`, `use_effect`, and friends, just like React, and let PythonNative handle creating and updating native views.
30
30
 
31
31
  ## Features
32
32
 
33
33
  - **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
34
- - **Reactive state:** Call `self.set_state(key=value)` and the framework re-renders only what changed no manual view mutation.
34
+ - **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
35
+ - **`style` prop:** Pass all visual and layout properties through a single `style` dict, composable via `StyleSheet`.
35
36
  - **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
36
37
  - **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
37
38
  - **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
38
- - **Navigation:** Push and pop screens with argument passing for multi-page apps.
39
- - **Bundled templates:** Android Gradle and iOS Xcode templates are included scaffolding requires no network access.
39
+ - **Navigation:** Push and pop screens with argument passing via the `use_navigation()` hook.
40
+ - **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
40
41
 
41
42
  ## Quick Start
42
43
 
@@ -52,21 +53,17 @@ pip install pythonnative
52
53
  import pythonnative as pn
53
54
 
54
55
 
55
- class MainPage(pn.Page):
56
- def __init__(self, native_instance):
57
- super().__init__(native_instance)
58
- self.state = {"count": 0}
59
-
60
- def render(self):
61
- return pn.Column(
62
- pn.Text(f"Count: {self.state['count']}", font_size=24),
63
- pn.Button(
64
- "Tap me",
65
- on_click=lambda: self.set_state(count=self.state["count"] + 1),
66
- ),
67
- spacing=12,
68
- padding=16,
69
- )
56
+ @pn.component
57
+ def MainPage():
58
+ count, set_count = pn.use_state(0)
59
+ return pn.Column(
60
+ pn.Text(f"Count: {count}", style={"font_size": 24}),
61
+ pn.Button(
62
+ "Tap me",
63
+ on_click=lambda: set_count(count + 1),
64
+ ),
65
+ style={"spacing": 12, "padding": 16},
66
+ )
70
67
  ```
71
68
 
72
69
  ## Documentation
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pythonnative"
7
- version = "0.6.0"
7
+ version = "0.8.0"
8
8
  description = "Cross-platform native UI toolkit for Android and iOS"
9
9
  authors = [
10
10
  { name = "Owen Carey" }
@@ -5,33 +5,22 @@ Public API::
5
5
  import pythonnative as pn
6
6
 
7
7
  @pn.component
8
- def counter(initial=0):
9
- count, set_count = pn.use_state(initial)
8
+ def App():
9
+ count, set_count = pn.use_state(0)
10
10
  return pn.Column(
11
- pn.Text(f"Count: {count}", font_size=24),
11
+ pn.Text(f"Count: {count}", style={"font_size": 24}),
12
12
  pn.Button("+", on_click=lambda: set_count(count + 1)),
13
- spacing=12,
13
+ style={"spacing": 12},
14
14
  )
15
-
16
- class MainPage(pn.Page):
17
- def __init__(self, native_instance):
18
- super().__init__(native_instance)
19
-
20
- def render(self):
21
- return pn.Column(
22
- counter(initial=0),
23
- counter(initial=10),
24
- spacing=16,
25
- padding=16,
26
- )
27
15
  """
28
16
 
29
- __version__ = "0.6.0"
17
+ __version__ = "0.8.0"
30
18
 
31
19
  from .components import (
32
20
  ActivityIndicator,
33
21
  Button,
34
22
  Column,
23
+ ErrorBoundary,
35
24
  FlatList,
36
25
  Image,
37
26
  Modal,
@@ -51,16 +40,27 @@ from .components import (
51
40
  from .element import Element
52
41
  from .hooks import (
53
42
  Provider,
43
+ batch_updates,
54
44
  component,
55
45
  create_context,
56
46
  use_callback,
57
47
  use_context,
58
48
  use_effect,
59
49
  use_memo,
50
+ use_navigation,
51
+ use_reducer,
60
52
  use_ref,
61
53
  use_state,
62
54
  )
63
- from .page import Page
55
+ from .navigation import (
56
+ NavigationContainer,
57
+ create_drawer_navigator,
58
+ create_stack_navigator,
59
+ create_tab_navigator,
60
+ use_focus_effect,
61
+ use_route,
62
+ )
63
+ from .page import create_page
64
64
  from .style import StyleSheet, ThemeContext
65
65
 
66
66
  __all__ = [
@@ -68,6 +68,7 @@ __all__ = [
68
68
  "ActivityIndicator",
69
69
  "Button",
70
70
  "Column",
71
+ "ErrorBoundary",
71
72
  "FlatList",
72
73
  "Image",
73
74
  "Modal",
@@ -85,17 +86,27 @@ __all__ = [
85
86
  "WebView",
86
87
  # Core
87
88
  "Element",
88
- "Page",
89
+ "create_page",
89
90
  # Hooks
91
+ "batch_updates",
90
92
  "component",
91
93
  "create_context",
92
94
  "use_callback",
93
95
  "use_context",
94
96
  "use_effect",
97
+ "use_focus_effect",
95
98
  "use_memo",
99
+ "use_navigation",
100
+ "use_reducer",
96
101
  "use_ref",
102
+ "use_route",
97
103
  "use_state",
98
104
  "Provider",
105
+ # Navigation
106
+ "NavigationContainer",
107
+ "create_drawer_navigator",
108
+ "create_stack_navigator",
109
+ "create_tab_navigator",
99
110
  # Styling
100
111
  "StyleSheet",
101
112
  "ThemeContext",
@@ -48,25 +48,17 @@ def init_project(args: argparse.Namespace) -> None:
48
48
  f.write("""import pythonnative as pn
49
49
 
50
50
 
51
- class MainPage(pn.Page):
52
- def __init__(self, native_instance):
53
- super().__init__(native_instance)
54
- self.state = {"count": 0}
55
-
56
- def increment(self):
57
- self.set_state(count=self.state["count"] + 1)
58
-
59
- def render(self):
60
- return pn.ScrollView(
61
- pn.Column(
62
- pn.Text("Hello from PythonNative!", font_size=24, bold=True),
63
- pn.Text(f"Tapped {self.state['count']} times"),
64
- pn.Button("Tap me", on_click=self.increment),
65
- spacing=12,
66
- padding=16,
67
- alignment="fill",
68
- )
51
+ @pn.component
52
+ def MainPage():
53
+ count, set_count = pn.use_state(0)
54
+ return pn.ScrollView(
55
+ pn.Column(
56
+ pn.Text("Hello from PythonNative!", style={"font_size": 24, "bold": True}),
57
+ pn.Text(f"Tapped {count} times"),
58
+ pn.Button("Tap me", on_click=lambda: set_count(count + 1)),
59
+ style={"spacing": 12, "padding": 16, "align_items": "stretch"},
69
60
  )
61
+ )
70
62
  """)
71
63
 
72
64
  # Create config
@@ -0,0 +1,408 @@
1
+ """Built-in element-creating functions for declarative UI composition.
2
+
3
+ Each function returns an :class:`Element` describing a native UI widget.
4
+ These are pure data — no native views are created until the reconciler
5
+ mounts the element tree.
6
+
7
+ All visual and layout properties are passed via the ``style`` parameter,
8
+ which accepts a dict or a list of dicts (later entries override earlier).
9
+
10
+ Layout properties supported by all components::
11
+
12
+ width, height, flex, flex_grow, flex_shrink, margin,
13
+ min_width, max_width, min_height, max_height, align_self
14
+
15
+ Flex container properties (View / Column / Row)::
16
+
17
+ flex_direction, justify_content, align_items, overflow,
18
+ spacing, padding
19
+
20
+ ``View`` is the universal flex container (like React Native's ``View``).
21
+ It defaults to ``flex_direction: "column"``. ``Column`` and ``Row``
22
+ are convenience wrappers that fix the direction to ``"column"`` and
23
+ ``"row"`` respectively.
24
+ """
25
+
26
+ from typing import Any, Callable, Dict, List, Optional
27
+
28
+ from .element import Element
29
+ from .style import StyleValue, resolve_style
30
+
31
+ # ======================================================================
32
+ # Leaf components
33
+ # ======================================================================
34
+
35
+
36
+ def Text(
37
+ text: str = "",
38
+ *,
39
+ style: StyleValue = None,
40
+ key: Optional[str] = None,
41
+ ) -> Element:
42
+ """Display text.
43
+
44
+ Style properties: ``font_size``, ``color``, ``bold``, ``text_align``,
45
+ ``background_color``, ``max_lines``, plus common layout props.
46
+ """
47
+ props: Dict[str, Any] = {"text": text}
48
+ props.update(resolve_style(style))
49
+ return Element("Text", props, [], key=key)
50
+
51
+
52
+ def Button(
53
+ title: str = "",
54
+ *,
55
+ on_click: Optional[Callable[[], None]] = None,
56
+ enabled: bool = True,
57
+ style: StyleValue = None,
58
+ key: Optional[str] = None,
59
+ ) -> Element:
60
+ """Create a tappable button.
61
+
62
+ Style properties: ``color``, ``background_color``, ``font_size``,
63
+ plus common layout props.
64
+ """
65
+ props: Dict[str, Any] = {"title": title}
66
+ if on_click is not None:
67
+ props["on_click"] = on_click
68
+ if not enabled:
69
+ props["enabled"] = False
70
+ props.update(resolve_style(style))
71
+ return Element("Button", props, [], key=key)
72
+
73
+
74
+ def TextInput(
75
+ *,
76
+ value: str = "",
77
+ placeholder: str = "",
78
+ on_change: Optional[Callable[[str], None]] = None,
79
+ secure: bool = False,
80
+ style: StyleValue = None,
81
+ key: Optional[str] = None,
82
+ ) -> Element:
83
+ """Create a single-line text entry field.
84
+
85
+ Style properties: ``font_size``, ``color``, ``background_color``,
86
+ plus common layout props.
87
+ """
88
+ props: Dict[str, Any] = {"value": value}
89
+ if placeholder:
90
+ props["placeholder"] = placeholder
91
+ if on_change is not None:
92
+ props["on_change"] = on_change
93
+ if secure:
94
+ props["secure"] = True
95
+ props.update(resolve_style(style))
96
+ return Element("TextInput", props, [], key=key)
97
+
98
+
99
+ def Image(
100
+ source: str = "",
101
+ *,
102
+ scale_type: Optional[str] = None,
103
+ style: StyleValue = None,
104
+ key: Optional[str] = None,
105
+ ) -> Element:
106
+ """Display an image from a resource path or URL.
107
+
108
+ Style properties: ``background_color``, plus common layout props.
109
+ """
110
+ props: Dict[str, Any] = {}
111
+ if source:
112
+ props["source"] = source
113
+ if scale_type is not None:
114
+ props["scale_type"] = scale_type
115
+ props.update(resolve_style(style))
116
+ return Element("Image", props, [], key=key)
117
+
118
+
119
+ def Switch(
120
+ *,
121
+ value: bool = False,
122
+ on_change: Optional[Callable[[bool], None]] = None,
123
+ style: StyleValue = None,
124
+ key: Optional[str] = None,
125
+ ) -> Element:
126
+ """Create a toggle switch."""
127
+ props: Dict[str, Any] = {"value": value}
128
+ if on_change is not None:
129
+ props["on_change"] = on_change
130
+ props.update(resolve_style(style))
131
+ return Element("Switch", props, [], key=key)
132
+
133
+
134
+ def ProgressBar(
135
+ *,
136
+ value: float = 0.0,
137
+ style: StyleValue = None,
138
+ key: Optional[str] = None,
139
+ ) -> Element:
140
+ """Show determinate progress (0.0 – 1.0)."""
141
+ props: Dict[str, Any] = {"value": value}
142
+ props.update(resolve_style(style))
143
+ return Element("ProgressBar", props, [], key=key)
144
+
145
+
146
+ def ActivityIndicator(
147
+ *,
148
+ animating: bool = True,
149
+ style: StyleValue = None,
150
+ key: Optional[str] = None,
151
+ ) -> Element:
152
+ """Show an indeterminate loading spinner."""
153
+ props: Dict[str, Any] = {"animating": animating}
154
+ props.update(resolve_style(style))
155
+ return Element("ActivityIndicator", props, [], key=key)
156
+
157
+
158
+ def WebView(
159
+ *,
160
+ url: str = "",
161
+ style: StyleValue = None,
162
+ key: Optional[str] = None,
163
+ ) -> Element:
164
+ """Embed web content."""
165
+ props: Dict[str, Any] = {}
166
+ if url:
167
+ props["url"] = url
168
+ props.update(resolve_style(style))
169
+ return Element("WebView", props, [], key=key)
170
+
171
+
172
+ def Spacer(
173
+ *,
174
+ size: Optional[float] = None,
175
+ flex: Optional[float] = None,
176
+ key: Optional[str] = None,
177
+ ) -> Element:
178
+ """Insert empty space with an optional fixed size or flex weight."""
179
+ props: Dict[str, Any] = {}
180
+ if size is not None:
181
+ props["size"] = size
182
+ if flex is not None:
183
+ props["flex"] = flex
184
+ return Element("Spacer", props, [], key=key)
185
+
186
+
187
+ def Slider(
188
+ *,
189
+ value: float = 0.0,
190
+ min_value: float = 0.0,
191
+ max_value: float = 1.0,
192
+ on_change: Optional[Callable[[float], None]] = None,
193
+ style: StyleValue = None,
194
+ key: Optional[str] = None,
195
+ ) -> Element:
196
+ """Continuous value slider."""
197
+ props: Dict[str, Any] = {
198
+ "value": value,
199
+ "min_value": min_value,
200
+ "max_value": max_value,
201
+ }
202
+ if on_change is not None:
203
+ props["on_change"] = on_change
204
+ props.update(resolve_style(style))
205
+ return Element("Slider", props, [], key=key)
206
+
207
+
208
+ # ======================================================================
209
+ # Container components
210
+ # ======================================================================
211
+
212
+
213
+ def View(
214
+ *children: Element,
215
+ style: StyleValue = None,
216
+ key: Optional[str] = None,
217
+ ) -> Element:
218
+ """Universal flex container (like React Native's ``View``).
219
+
220
+ Defaults to ``flex_direction: "column"``. Override via ``style``::
221
+
222
+ pn.View(child_a, child_b, style={"flex_direction": "row"})
223
+
224
+ Flex container properties (inside ``style``):
225
+
226
+ - ``flex_direction`` — ``"column"`` (default), ``"row"``,
227
+ ``"column_reverse"``, ``"row_reverse"``
228
+ - ``justify_content`` — main-axis distribution:
229
+ ``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
230
+ ``"space_between"``, ``"space_around"``, ``"space_evenly"``
231
+ - ``align_items`` — cross-axis alignment:
232
+ ``"stretch"`` (default), ``"flex_start"``, ``"center"``,
233
+ ``"flex_end"``
234
+ - ``overflow`` — ``"visible"`` (default) or ``"hidden"``
235
+ - ``spacing``, ``padding``, ``background_color``
236
+ """
237
+ props: Dict[str, Any] = {"flex_direction": "column"}
238
+ props.update(resolve_style(style))
239
+ return Element("View", props, list(children), key=key)
240
+
241
+
242
+ def Column(
243
+ *children: Element,
244
+ style: StyleValue = None,
245
+ key: Optional[str] = None,
246
+ ) -> Element:
247
+ """Arrange children vertically (``flex_direction: "column"``).
248
+
249
+ Convenience wrapper around :func:`View`. The direction is fixed;
250
+ use :func:`View` directly if you need ``flex_direction: "row"``.
251
+
252
+ Style properties: ``spacing``, ``padding``, ``align_items``,
253
+ ``justify_content``, ``background_color``, ``overflow``,
254
+ plus common layout props.
255
+
256
+ ``align_items`` controls cross-axis (horizontal) alignment:
257
+ ``"stretch"`` (default), ``"flex_start"``/``"leading"``,
258
+ ``"center"``, ``"flex_end"``/``"trailing"``.
259
+
260
+ ``justify_content`` controls main-axis (vertical) distribution:
261
+ ``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
262
+ ``"space_between"``, ``"space_around"``, ``"space_evenly"``.
263
+ """
264
+ props: Dict[str, Any] = {"flex_direction": "column"}
265
+ props.update(resolve_style(style))
266
+ props["flex_direction"] = "column"
267
+ return Element("Column", props, list(children), key=key)
268
+
269
+
270
+ def Row(
271
+ *children: Element,
272
+ style: StyleValue = None,
273
+ key: Optional[str] = None,
274
+ ) -> Element:
275
+ """Arrange children horizontally (``flex_direction: "row"``).
276
+
277
+ Convenience wrapper around :func:`View`. The direction is fixed;
278
+ use :func:`View` directly if you need ``flex_direction: "column"``.
279
+
280
+ Style properties: ``spacing``, ``padding``, ``align_items``,
281
+ ``justify_content``, ``background_color``, ``overflow``,
282
+ plus common layout props.
283
+
284
+ ``align_items`` controls cross-axis (vertical) alignment:
285
+ ``"stretch"`` (default), ``"flex_start"``/``"top"``,
286
+ ``"center"``, ``"flex_end"``/``"bottom"``.
287
+
288
+ ``justify_content`` controls main-axis (horizontal) distribution:
289
+ ``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
290
+ ``"space_between"``, ``"space_around"``, ``"space_evenly"``.
291
+ """
292
+ props: Dict[str, Any] = {"flex_direction": "row"}
293
+ props.update(resolve_style(style))
294
+ props["flex_direction"] = "row"
295
+ return Element("Row", props, list(children), key=key)
296
+
297
+
298
+ def ScrollView(
299
+ child: Optional[Element] = None,
300
+ *,
301
+ style: StyleValue = None,
302
+ key: Optional[str] = None,
303
+ ) -> Element:
304
+ """Wrap a single child in a scrollable container."""
305
+ children = [child] if child is not None else []
306
+ props: Dict[str, Any] = {}
307
+ props.update(resolve_style(style))
308
+ return Element("ScrollView", props, children, key=key)
309
+
310
+
311
+ def SafeAreaView(
312
+ *children: Element,
313
+ style: StyleValue = None,
314
+ key: Optional[str] = None,
315
+ ) -> Element:
316
+ """Container that respects safe area insets (notch, status bar)."""
317
+ props: Dict[str, Any] = {}
318
+ props.update(resolve_style(style))
319
+ return Element("SafeAreaView", props, list(children), key=key)
320
+
321
+
322
+ def Modal(
323
+ *children: Element,
324
+ visible: bool = False,
325
+ on_dismiss: Optional[Callable[[], None]] = None,
326
+ title: Optional[str] = None,
327
+ style: StyleValue = None,
328
+ key: Optional[str] = None,
329
+ ) -> Element:
330
+ """Overlay modal dialog.
331
+
332
+ The modal is shown when ``visible=True`` and hidden when ``False``.
333
+ """
334
+ props: Dict[str, Any] = {"visible": visible}
335
+ if on_dismiss is not None:
336
+ props["on_dismiss"] = on_dismiss
337
+ if title is not None:
338
+ props["title"] = title
339
+ props.update(resolve_style(style))
340
+ return Element("Modal", props, list(children), key=key)
341
+
342
+
343
+ def Pressable(
344
+ child: Optional[Element] = None,
345
+ *,
346
+ on_press: Optional[Callable[[], None]] = None,
347
+ on_long_press: Optional[Callable[[], None]] = None,
348
+ key: Optional[str] = None,
349
+ ) -> Element:
350
+ """Wrapper that adds press handling to any child element."""
351
+ props: Dict[str, Any] = {}
352
+ if on_press is not None:
353
+ props["on_press"] = on_press
354
+ if on_long_press is not None:
355
+ props["on_long_press"] = on_long_press
356
+ children = [child] if child is not None else []
357
+ return Element("Pressable", props, children, key=key)
358
+
359
+
360
+ def ErrorBoundary(
361
+ child: Optional[Element] = None,
362
+ *,
363
+ fallback: Optional[Any] = None,
364
+ key: Optional[str] = None,
365
+ ) -> Element:
366
+ """Catch render errors in *child* and display *fallback* instead.
367
+
368
+ *fallback* may be an ``Element`` or a callable that receives the
369
+ exception and returns an ``Element``::
370
+
371
+ pn.ErrorBoundary(
372
+ MyRiskyComponent(),
373
+ fallback=lambda err: pn.Text(f"Error: {err}"),
374
+ )
375
+ """
376
+ props: Dict[str, Any] = {}
377
+ if fallback is not None:
378
+ props["__fallback__"] = fallback
379
+ children = [child] if child is not None else []
380
+ return Element("__ErrorBoundary__", props, children, key=key)
381
+
382
+
383
+ def FlatList(
384
+ *,
385
+ data: Optional[List[Any]] = None,
386
+ render_item: Optional[Callable[[Any, int], Element]] = None,
387
+ key_extractor: Optional[Callable[[Any, int], str]] = None,
388
+ separator_height: float = 0,
389
+ style: StyleValue = None,
390
+ key: Optional[str] = None,
391
+ ) -> Element:
392
+ """Scrollable list that renders items from *data* using *render_item*.
393
+
394
+ Each item is rendered by calling ``render_item(item, index)``. If
395
+ ``key_extractor`` is provided, it is called as ``key_extractor(item, index)``
396
+ to produce a stable key for each child element.
397
+ """
398
+ items: List[Element] = []
399
+ for i, item in enumerate(data or []):
400
+ el = render_item(item, i) if render_item else Text(str(item))
401
+ if key_extractor is not None:
402
+ el = Element(el.type, el.props, el.children, key=key_extractor(item, i))
403
+ items.append(el)
404
+
405
+ inner = Column(*items, style={"spacing": separator_height} if separator_height else None)
406
+ sv_props: Dict[str, Any] = {}
407
+ sv_props.update(resolve_style(style))
408
+ return Element("ScrollView", sv_props, [inner], key=key)