pythonnative 0.6.0__tar.gz → 0.7.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 (87) hide show
  1. {pythonnative-0.6.0/src/pythonnative.egg-info → pythonnative-0.7.0}/PKG-INFO +17 -20
  2. {pythonnative-0.6.0 → pythonnative-0.7.0}/README.md +16 -19
  3. {pythonnative-0.6.0 → pythonnative-0.7.0}/pyproject.toml +1 -1
  4. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/__init__.py +9 -19
  5. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/cli/pn.py +10 -18
  6. pythonnative-0.7.0/src/pythonnative/components.py +351 -0
  7. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/hooks.py +49 -2
  8. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/native_views.py +86 -16
  9. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/page.py +138 -172
  10. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/style.py +27 -7
  11. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PageFragment.kt +2 -9
  12. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +7 -20
  13. {pythonnative-0.6.0 → pythonnative-0.7.0/src/pythonnative.egg-info}/PKG-INFO +17 -20
  14. {pythonnative-0.6.0 → pythonnative-0.7.0}/tests/test_cli.py +1 -1
  15. {pythonnative-0.6.0 → pythonnative-0.7.0}/tests/test_components.py +36 -16
  16. {pythonnative-0.6.0 → pythonnative-0.7.0}/tests/test_hooks.py +32 -0
  17. {pythonnative-0.6.0 → pythonnative-0.7.0}/tests/test_smoke.py +3 -1
  18. {pythonnative-0.6.0 → pythonnative-0.7.0}/tests/test_style.py +23 -1
  19. pythonnative-0.6.0/src/pythonnative/components.py +0 -563
  20. {pythonnative-0.6.0 → pythonnative-0.7.0}/LICENSE +0 -0
  21. {pythonnative-0.6.0 → pythonnative-0.7.0}/setup.cfg +0 -0
  22. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/cli/__init__.py +0 -0
  23. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/element.py +0 -0
  24. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/hot_reload.py +0 -0
  25. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/native_modules/__init__.py +0 -0
  26. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/native_modules/camera.py +0 -0
  27. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/native_modules/file_system.py +0 -0
  28. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/native_modules/location.py +0 -0
  29. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/native_modules/notifications.py +0 -0
  30. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/reconciler.py +0 -0
  31. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/build.gradle +0 -0
  32. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
  33. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
  34. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
  35. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
  36. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
  37. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
  38. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
  39. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
  40. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
  41. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
  42. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  43. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  44. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  45. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  46. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  47. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  48. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  49. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  50. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  51. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  52. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
  53. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
  54. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
  55. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
  56. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
  57. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
  58. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
  59. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
  60. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
  61. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  62. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
  63. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
  64. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradlew +0 -0
  65. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
  66. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
  67. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
  68. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
  69. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  70. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
  71. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
  72. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -0
  73. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
  74. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
  75. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
  76. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  77. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
  78. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
  79. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
  80. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative/utils.py +0 -0
  81. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/SOURCES.txt +0 -0
  82. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
  83. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
  84. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/requires.txt +0 -0
  85. {pythonnative-0.6.0 → pythonnative-0.7.0}/src/pythonnative.egg-info/top_level.txt +0 -0
  86. {pythonnative-0.6.0 → pythonnative-0.7.0}/tests/test_element.py +0 -0
  87. {pythonnative-0.6.0 → pythonnative-0.7.0}/tests/test_reconciler.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.7.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.7.0"
8
8
  description = "Cross-platform native UI toolkit for Android and iOS"
9
9
  authors = [
10
10
  { name = "Owen Carey" }
@@ -5,28 +5,16 @@ 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.7.0"
30
18
 
31
19
  from .components import (
32
20
  ActivityIndicator,
@@ -57,10 +45,11 @@ from .hooks import (
57
45
  use_context,
58
46
  use_effect,
59
47
  use_memo,
48
+ use_navigation,
60
49
  use_ref,
61
50
  use_state,
62
51
  )
63
- from .page import Page
52
+ from .page import create_page
64
53
  from .style import StyleSheet, ThemeContext
65
54
 
66
55
  __all__ = [
@@ -85,7 +74,7 @@ __all__ = [
85
74
  "WebView",
86
75
  # Core
87
76
  "Element",
88
- "Page",
77
+ "create_page",
89
78
  # Hooks
90
79
  "component",
91
80
  "create_context",
@@ -93,6 +82,7 @@ __all__ = [
93
82
  "use_context",
94
83
  "use_effect",
95
84
  "use_memo",
85
+ "use_navigation",
96
86
  "use_ref",
97
87
  "use_state",
98
88
  "Provider",
@@ -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,351 @@
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, margin, min_width, max_width, min_height,
13
+ max_height, align_self
14
+
15
+ Container-specific layout properties (Column / Row)::
16
+
17
+ spacing, padding, align_items, justify_content
18
+ """
19
+
20
+ from typing import Any, Callable, Dict, List, Optional
21
+
22
+ from .element import Element
23
+ from .style import StyleValue, resolve_style
24
+
25
+ # ======================================================================
26
+ # Leaf components
27
+ # ======================================================================
28
+
29
+
30
+ def Text(
31
+ text: str = "",
32
+ *,
33
+ style: StyleValue = None,
34
+ key: Optional[str] = None,
35
+ ) -> Element:
36
+ """Display text.
37
+
38
+ Style properties: ``font_size``, ``color``, ``bold``, ``text_align``,
39
+ ``background_color``, ``max_lines``, plus common layout props.
40
+ """
41
+ props: Dict[str, Any] = {"text": text}
42
+ props.update(resolve_style(style))
43
+ return Element("Text", props, [], key=key)
44
+
45
+
46
+ def Button(
47
+ title: str = "",
48
+ *,
49
+ on_click: Optional[Callable[[], None]] = None,
50
+ enabled: bool = True,
51
+ style: StyleValue = None,
52
+ key: Optional[str] = None,
53
+ ) -> Element:
54
+ """Create a tappable button.
55
+
56
+ Style properties: ``color``, ``background_color``, ``font_size``,
57
+ plus common layout props.
58
+ """
59
+ props: Dict[str, Any] = {"title": title}
60
+ if on_click is not None:
61
+ props["on_click"] = on_click
62
+ if not enabled:
63
+ props["enabled"] = False
64
+ props.update(resolve_style(style))
65
+ return Element("Button", props, [], key=key)
66
+
67
+
68
+ def TextInput(
69
+ *,
70
+ value: str = "",
71
+ placeholder: str = "",
72
+ on_change: Optional[Callable[[str], None]] = None,
73
+ secure: bool = False,
74
+ style: StyleValue = None,
75
+ key: Optional[str] = None,
76
+ ) -> Element:
77
+ """Create a single-line text entry field.
78
+
79
+ Style properties: ``font_size``, ``color``, ``background_color``,
80
+ plus common layout props.
81
+ """
82
+ props: Dict[str, Any] = {"value": value}
83
+ if placeholder:
84
+ props["placeholder"] = placeholder
85
+ if on_change is not None:
86
+ props["on_change"] = on_change
87
+ if secure:
88
+ props["secure"] = True
89
+ props.update(resolve_style(style))
90
+ return Element("TextInput", props, [], key=key)
91
+
92
+
93
+ def Image(
94
+ source: str = "",
95
+ *,
96
+ scale_type: Optional[str] = None,
97
+ style: StyleValue = None,
98
+ key: Optional[str] = None,
99
+ ) -> Element:
100
+ """Display an image from a resource path or URL.
101
+
102
+ Style properties: ``background_color``, plus common layout props.
103
+ """
104
+ props: Dict[str, Any] = {}
105
+ if source:
106
+ props["source"] = source
107
+ if scale_type is not None:
108
+ props["scale_type"] = scale_type
109
+ props.update(resolve_style(style))
110
+ return Element("Image", props, [], key=key)
111
+
112
+
113
+ def Switch(
114
+ *,
115
+ value: bool = False,
116
+ on_change: Optional[Callable[[bool], None]] = None,
117
+ style: StyleValue = None,
118
+ key: Optional[str] = None,
119
+ ) -> Element:
120
+ """Create a toggle switch."""
121
+ props: Dict[str, Any] = {"value": value}
122
+ if on_change is not None:
123
+ props["on_change"] = on_change
124
+ props.update(resolve_style(style))
125
+ return Element("Switch", props, [], key=key)
126
+
127
+
128
+ def ProgressBar(
129
+ *,
130
+ value: float = 0.0,
131
+ style: StyleValue = None,
132
+ key: Optional[str] = None,
133
+ ) -> Element:
134
+ """Show determinate progress (0.0 – 1.0)."""
135
+ props: Dict[str, Any] = {"value": value}
136
+ props.update(resolve_style(style))
137
+ return Element("ProgressBar", props, [], key=key)
138
+
139
+
140
+ def ActivityIndicator(
141
+ *,
142
+ animating: bool = True,
143
+ style: StyleValue = None,
144
+ key: Optional[str] = None,
145
+ ) -> Element:
146
+ """Show an indeterminate loading spinner."""
147
+ props: Dict[str, Any] = {"animating": animating}
148
+ props.update(resolve_style(style))
149
+ return Element("ActivityIndicator", props, [], key=key)
150
+
151
+
152
+ def WebView(
153
+ *,
154
+ url: str = "",
155
+ style: StyleValue = None,
156
+ key: Optional[str] = None,
157
+ ) -> Element:
158
+ """Embed web content."""
159
+ props: Dict[str, Any] = {}
160
+ if url:
161
+ props["url"] = url
162
+ props.update(resolve_style(style))
163
+ return Element("WebView", props, [], key=key)
164
+
165
+
166
+ def Spacer(
167
+ *,
168
+ size: Optional[float] = None,
169
+ flex: Optional[float] = None,
170
+ key: Optional[str] = None,
171
+ ) -> Element:
172
+ """Insert empty space with an optional fixed size or flex weight."""
173
+ props: Dict[str, Any] = {}
174
+ if size is not None:
175
+ props["size"] = size
176
+ if flex is not None:
177
+ props["flex"] = flex
178
+ return Element("Spacer", props, [], key=key)
179
+
180
+
181
+ def Slider(
182
+ *,
183
+ value: float = 0.0,
184
+ min_value: float = 0.0,
185
+ max_value: float = 1.0,
186
+ on_change: Optional[Callable[[float], None]] = None,
187
+ style: StyleValue = None,
188
+ key: Optional[str] = None,
189
+ ) -> Element:
190
+ """Continuous value slider."""
191
+ props: Dict[str, Any] = {
192
+ "value": value,
193
+ "min_value": min_value,
194
+ "max_value": max_value,
195
+ }
196
+ if on_change is not None:
197
+ props["on_change"] = on_change
198
+ props.update(resolve_style(style))
199
+ return Element("Slider", props, [], key=key)
200
+
201
+
202
+ # ======================================================================
203
+ # Container components
204
+ # ======================================================================
205
+
206
+
207
+ def Column(
208
+ *children: Element,
209
+ style: StyleValue = None,
210
+ key: Optional[str] = None,
211
+ ) -> Element:
212
+ """Arrange children vertically.
213
+
214
+ Style properties: ``spacing``, ``padding``, ``align_items``,
215
+ ``justify_content``, ``background_color``, plus common layout props.
216
+
217
+ ``align_items`` controls cross-axis (horizontal) alignment:
218
+ ``"stretch"`` (default), ``"flex_start"``/``"leading"``,
219
+ ``"center"``, ``"flex_end"``/``"trailing"``.
220
+
221
+ ``justify_content`` controls main-axis (vertical) distribution:
222
+ ``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
223
+ ``"space_between"``, ``"space_around"``, ``"space_evenly"``.
224
+ """
225
+ props: Dict[str, Any] = {}
226
+ props.update(resolve_style(style))
227
+ return Element("Column", props, list(children), key=key)
228
+
229
+
230
+ def Row(
231
+ *children: Element,
232
+ style: StyleValue = None,
233
+ key: Optional[str] = None,
234
+ ) -> Element:
235
+ """Arrange children horizontally.
236
+
237
+ Style properties: ``spacing``, ``padding``, ``align_items``,
238
+ ``justify_content``, ``background_color``, plus common layout props.
239
+
240
+ ``align_items`` controls cross-axis (vertical) alignment:
241
+ ``"stretch"`` (default), ``"flex_start"``/``"top"``,
242
+ ``"center"``, ``"flex_end"``/``"bottom"``.
243
+
244
+ ``justify_content`` controls main-axis (horizontal) distribution:
245
+ ``"flex_start"`` (default), ``"center"``, ``"flex_end"``,
246
+ ``"space_between"``, ``"space_around"``, ``"space_evenly"``.
247
+ """
248
+ props: Dict[str, Any] = {}
249
+ props.update(resolve_style(style))
250
+ return Element("Row", props, list(children), key=key)
251
+
252
+
253
+ def ScrollView(
254
+ child: Optional[Element] = None,
255
+ *,
256
+ style: StyleValue = None,
257
+ key: Optional[str] = None,
258
+ ) -> Element:
259
+ """Wrap a single child in a scrollable container."""
260
+ children = [child] if child is not None else []
261
+ props: Dict[str, Any] = {}
262
+ props.update(resolve_style(style))
263
+ return Element("ScrollView", props, children, key=key)
264
+
265
+
266
+ def View(
267
+ *children: Element,
268
+ style: StyleValue = None,
269
+ key: Optional[str] = None,
270
+ ) -> Element:
271
+ """Generic container view (``UIView`` / ``android.view.View``)."""
272
+ props: Dict[str, Any] = {}
273
+ props.update(resolve_style(style))
274
+ return Element("View", props, list(children), key=key)
275
+
276
+
277
+ def SafeAreaView(
278
+ *children: Element,
279
+ style: StyleValue = None,
280
+ key: Optional[str] = None,
281
+ ) -> Element:
282
+ """Container that respects safe area insets (notch, status bar)."""
283
+ props: Dict[str, Any] = {}
284
+ props.update(resolve_style(style))
285
+ return Element("SafeAreaView", props, list(children), key=key)
286
+
287
+
288
+ def Modal(
289
+ *children: Element,
290
+ visible: bool = False,
291
+ on_dismiss: Optional[Callable[[], None]] = None,
292
+ title: Optional[str] = None,
293
+ style: StyleValue = None,
294
+ key: Optional[str] = None,
295
+ ) -> Element:
296
+ """Overlay modal dialog.
297
+
298
+ The modal is shown when ``visible=True`` and hidden when ``False``.
299
+ """
300
+ props: Dict[str, Any] = {"visible": visible}
301
+ if on_dismiss is not None:
302
+ props["on_dismiss"] = on_dismiss
303
+ if title is not None:
304
+ props["title"] = title
305
+ props.update(resolve_style(style))
306
+ return Element("Modal", props, list(children), key=key)
307
+
308
+
309
+ def Pressable(
310
+ child: Optional[Element] = None,
311
+ *,
312
+ on_press: Optional[Callable[[], None]] = None,
313
+ on_long_press: Optional[Callable[[], None]] = None,
314
+ key: Optional[str] = None,
315
+ ) -> Element:
316
+ """Wrapper that adds press handling to any child element."""
317
+ props: Dict[str, Any] = {}
318
+ if on_press is not None:
319
+ props["on_press"] = on_press
320
+ if on_long_press is not None:
321
+ props["on_long_press"] = on_long_press
322
+ children = [child] if child is not None else []
323
+ return Element("Pressable", props, children, key=key)
324
+
325
+
326
+ def FlatList(
327
+ *,
328
+ data: Optional[List[Any]] = None,
329
+ render_item: Optional[Callable[[Any, int], Element]] = None,
330
+ key_extractor: Optional[Callable[[Any, int], str]] = None,
331
+ separator_height: float = 0,
332
+ style: StyleValue = None,
333
+ key: Optional[str] = None,
334
+ ) -> Element:
335
+ """Scrollable list that renders items from *data* using *render_item*.
336
+
337
+ Each item is rendered by calling ``render_item(item, index)``. If
338
+ ``key_extractor`` is provided, it is called as ``key_extractor(item, index)``
339
+ to produce a stable key for each child element.
340
+ """
341
+ items: List[Element] = []
342
+ for i, item in enumerate(data or []):
343
+ el = render_item(item, i) if render_item else Text(str(item))
344
+ if key_extractor is not None:
345
+ el = Element(el.type, el.props, el.children, key=key_extractor(item, i))
346
+ items.append(el)
347
+
348
+ inner = Column(*items, style={"spacing": separator_height} if separator_height else None)
349
+ sv_props: Dict[str, Any] = {}
350
+ sv_props.update(resolve_style(style))
351
+ return Element("ScrollView", sv_props, [inner], key=key)
@@ -1,7 +1,8 @@
1
1
  """Hook primitives for function components.
2
2
 
3
3
  Provides React-like hooks for managing state, effects, memoisation,
4
- and context within function components decorated with :func:`component`.
4
+ context, and navigation within function components decorated with
5
+ :func:`component`.
5
6
 
6
7
  Usage::
7
8
 
@@ -18,7 +19,7 @@ Usage::
18
19
 
19
20
  import inspect
20
21
  import threading
21
- from typing import Any, Callable, List, Optional, Tuple, TypeVar
22
+ from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar
22
23
 
23
24
  from .element import Element
24
25
 
@@ -246,6 +247,52 @@ def Provider(context: Context, value: Any, child: Element) -> Element:
246
247
  return Element("__Provider__", {"__context__": context, "__value__": value}, [child])
247
248
 
248
249
 
250
+ # ======================================================================
251
+ # Navigation
252
+ # ======================================================================
253
+
254
+ _NavigationContext: Context = create_context(None)
255
+
256
+
257
+ class NavigationHandle:
258
+ """Object returned by :func:`use_navigation` providing push/pop/get_args.
259
+
260
+ Navigates by component reference rather than string path, e.g.::
261
+
262
+ nav = pn.use_navigation()
263
+ nav.push(DetailScreen, args={"id": 42})
264
+ """
265
+
266
+ def __init__(self, host: Any) -> None:
267
+ self._host = host
268
+
269
+ def push(self, page: Any, args: Optional[Dict[str, Any]] = None) -> None:
270
+ """Navigate forward to *page* (a ``@component`` function or class)."""
271
+ self._host._push(page, args)
272
+
273
+ def pop(self) -> None:
274
+ """Navigate back to the previous screen."""
275
+ self._host._pop()
276
+
277
+ def get_args(self) -> Dict[str, Any]:
278
+ """Return arguments passed from the previous screen."""
279
+ return self._host._get_nav_args()
280
+
281
+
282
+ def use_navigation() -> NavigationHandle:
283
+ """Return a :class:`NavigationHandle` for the current screen.
284
+
285
+ Must be called inside a ``@component`` function rendered by PythonNative.
286
+ """
287
+ handle = use_context(_NavigationContext)
288
+ if handle is None:
289
+ raise RuntimeError(
290
+ "use_navigation() called outside a PythonNative page. "
291
+ "Ensure your component is rendered via create_page()."
292
+ )
293
+ return handle
294
+
295
+
249
296
  # ======================================================================
250
297
  # @component decorator
251
298
  # ======================================================================