pythonnative 0.14.0__tar.gz → 0.15.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 (116) hide show
  1. {pythonnative-0.14.0/src/pythonnative.egg-info → pythonnative-0.15.0}/PKG-INFO +5 -4
  2. {pythonnative-0.14.0 → pythonnative-0.15.0}/README.md +4 -3
  3. {pythonnative-0.14.0 → pythonnative-0.15.0}/pyproject.toml +2 -1
  4. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/__init__.py +77 -5
  5. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/animated.py +2 -2
  6. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/components.py +41 -33
  7. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_views/__init__.py +62 -14
  8. pythonnative-0.15.0/src/pythonnative/sdk/__init__.py +132 -0
  9. pythonnative-0.15.0/src/pythonnative/sdk/_components.py +429 -0
  10. pythonnative-0.15.0/src/pythonnative/style.py +548 -0
  11. {pythonnative-0.14.0 → pythonnative-0.15.0/src/pythonnative.egg-info}/PKG-INFO +5 -4
  12. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative.egg-info/SOURCES.txt +3 -0
  13. pythonnative-0.15.0/tests/test_sdk.py +446 -0
  14. pythonnative-0.15.0/tests/test_style.py +170 -0
  15. pythonnative-0.14.0/src/pythonnative/style.py +0 -196
  16. pythonnative-0.14.0/tests/test_style.py +0 -80
  17. {pythonnative-0.14.0 → pythonnative-0.15.0}/LICENSE +0 -0
  18. {pythonnative-0.14.0 → pythonnative-0.15.0}/setup.cfg +0 -0
  19. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/_ios_log.py +0 -0
  20. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/alerts.py +0 -0
  21. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/cli/__init__.py +0 -0
  22. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/cli/pn.py +0 -0
  23. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/element.py +0 -0
  24. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/hooks.py +0 -0
  25. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/hot_reload.py +0 -0
  26. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/layout.py +0 -0
  27. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_modules/__init__.py +0 -0
  28. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_modules/camera.py +0 -0
  29. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_modules/file_system.py +0 -0
  30. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_modules/location.py +0 -0
  31. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_modules/notifications.py +0 -0
  32. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_views/android.py +0 -0
  33. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_views/base.py +0 -0
  34. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/native_views/ios.py +0 -0
  35. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/navigation.py +0 -0
  36. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/platform.py +0 -0
  37. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/platform_metrics.py +0 -0
  38. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/reconciler.py +0 -0
  39. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/screen.py +0 -0
  40. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/build.gradle +0 -0
  41. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
  42. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
  43. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
  44. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
  45. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
  46. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +0 -0
  47. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +0 -0
  48. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
  49. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
  50. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
  51. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
  52. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
  53. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  54. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  55. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  56. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  57. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  58. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  59. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  60. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  61. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  62. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  63. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
  64. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
  65. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
  66. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
  67. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
  68. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
  69. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
  70. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
  71. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
  72. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  73. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
  74. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
  75. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/gradlew +0 -0
  76. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
  77. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
  78. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
  79. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
  80. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  81. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
  82. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
  83. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -0
  84. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
  85. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
  86. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -0
  87. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
  88. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  89. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
  90. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
  91. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
  92. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative/utils.py +0 -0
  93. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
  94. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
  95. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative.egg-info/requires.txt +0 -0
  96. {pythonnative-0.14.0 → pythonnative-0.15.0}/src/pythonnative.egg-info/top_level.txt +0 -0
  97. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_alert.py +0 -0
  98. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_animated.py +0 -0
  99. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_cli.py +0 -0
  100. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_components.py +0 -0
  101. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_element.py +0 -0
  102. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_hooks.py +0 -0
  103. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_hot_reload.py +0 -0
  104. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_ios_log.py +0 -0
  105. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_layout.py +0 -0
  106. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_metric_hooks.py +0 -0
  107. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_native_views.py +0 -0
  108. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_navigation.py +0 -0
  109. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_new_components.py +0 -0
  110. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_platform.py +0 -0
  111. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_platform_metrics.py +0 -0
  112. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_reconciler.py +0 -0
  113. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_ref.py +0 -0
  114. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_screen.py +0 -0
  115. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_smoke.py +0 -0
  116. {pythonnative-0.14.0 → pythonnative-0.15.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -96,10 +96,11 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
96
96
 
97
97
  - **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
98
98
  - **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
99
- - **`style` prop:** Pass all visual and layout properties through a single `style` dict, composable via `StyleSheet`.
99
+ - **Typed `style` prop:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`.
100
100
  - **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.
101
101
  - **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
102
102
  - **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
103
+ - **Custom-component SDK:** Wrap any platform widget as a first-class element with type-checked props via `pythonnative.sdk` (`Props`, `@native_component`, `element_factory`). Plugins distributed on PyPI auto-register through the `pythonnative.handlers` entry-point group.
103
104
  - **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
104
105
  - **Native-backed navigation:** Declarative `Stack`, `Tab`, and `Drawer` navigators inspired by React Navigation. The root stack drives the platform's native navigation controller (`UINavigationController` on iOS, AndroidX Navigation Component on Android), so transitions, back gestures, and the hardware back button match what users expect.
105
106
  - **Fast Refresh hot reload:** `pn run --hot-reload` watches `app/` and patches edits into the running app on save, preserving component state across most changes.
@@ -123,12 +124,12 @@ import pythonnative as pn
123
124
  def App():
124
125
  count, set_count = pn.use_state(0)
125
126
  return pn.Column(
126
- pn.Text(f"Count: {count}", style={"font_size": 24}),
127
+ pn.Text(f"Count: {count}", style=pn.style(font_size=24, bold=True)),
127
128
  pn.Button(
128
129
  "Tap me",
129
130
  on_click=lambda: set_count(count + 1),
130
131
  ),
131
- style={"spacing": 12, "padding": 16},
132
+ style=pn.style(spacing=12, padding=16),
132
133
  )
133
134
  ```
134
135
 
@@ -32,10 +32,11 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
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
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
+ - **Typed `style` prop:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`.
36
36
  - **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.
37
37
  - **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
38
38
  - **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
39
+ - **Custom-component SDK:** Wrap any platform widget as a first-class element with type-checked props via `pythonnative.sdk` (`Props`, `@native_component`, `element_factory`). Plugins distributed on PyPI auto-register through the `pythonnative.handlers` entry-point group.
39
40
  - **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
40
41
  - **Native-backed navigation:** Declarative `Stack`, `Tab`, and `Drawer` navigators inspired by React Navigation. The root stack drives the platform's native navigation controller (`UINavigationController` on iOS, AndroidX Navigation Component on Android), so transitions, back gestures, and the hardware back button match what users expect.
41
42
  - **Fast Refresh hot reload:** `pn run --hot-reload` watches `app/` and patches edits into the running app on save, preserving component state across most changes.
@@ -59,12 +60,12 @@ import pythonnative as pn
59
60
  def App():
60
61
  count, set_count = pn.use_state(0)
61
62
  return pn.Column(
62
- pn.Text(f"Count: {count}", style={"font_size": 24}),
63
+ pn.Text(f"Count: {count}", style=pn.style(font_size=24, bold=True)),
63
64
  pn.Button(
64
65
  "Tap me",
65
66
  on_click=lambda: set_count(count + 1),
66
67
  ),
67
- style={"spacing": 12, "padding": 16},
68
+ style=pn.style(spacing=12, padding=16),
68
69
  )
69
70
  ```
70
71
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pythonnative"
7
- version = "0.14.0"
7
+ version = "0.15.0"
8
8
  description = "Cross-platform native UI toolkit for Android and iOS"
9
9
  authors = [
10
10
  { name = "Owen Carey" }
@@ -64,6 +64,7 @@ Documentation = "https://docs.pythonnative.com/"
64
64
 
65
65
 
66
66
 
67
+
67
68
  [tool.setuptools.packages.find]
68
69
  where = ["src"]
69
70
 
@@ -23,8 +23,18 @@ Key building blocks:
23
23
  factories.
24
24
  - **Styling** uses a single ``style`` dict per element (or a list of
25
25
  dicts), composable via [`StyleSheet`][pythonnative.StyleSheet].
26
+ PythonNative ships a fully-typed [`Style`][pythonnative.style.Style]
27
+ TypedDict so editors and ``mypy`` validate every key as you type.
26
28
  - **Animations** use the ``Animated`` namespace, modeled on React
27
29
  Native's animation API.
30
+ - **Custom native components** can be authored with the
31
+ ``pythonnative.sdk`` package: define a typed
32
+ [`Props`][pythonnative.sdk.Props] dataclass, implement a
33
+ [`ViewHandler`][pythonnative.native_views.base.ViewHandler] for each
34
+ platform, and register it via
35
+ [`@native_component`][pythonnative.sdk.native_component] (or expose
36
+ it from a PyPI package via the ``pythonnative.handlers`` entry-point
37
+ group).
28
38
 
29
39
  Example:
30
40
  ```python
@@ -34,15 +44,16 @@ Example:
34
44
  def App():
35
45
  count, set_count = pn.use_state(0)
36
46
  return pn.Column(
37
- pn.Text(f"Count: {count}", style={"font_size": 24}),
47
+ pn.Text(f"Count: {count}", style=pn.style(font_size=24)),
38
48
  pn.Button("+", on_click=lambda: set_count(count + 1)),
39
- style={"spacing": 12},
49
+ style=pn.style(spacing=12),
40
50
  )
41
51
  ```
42
52
  """
43
53
 
44
- __version__ = "0.14.0"
54
+ __version__ = "0.15.0"
45
55
 
56
+ from . import sdk
46
57
  from .alerts import Alert
47
58
  from .animated import Animated, AnimatedValue
48
59
  from .components import (
@@ -100,7 +111,39 @@ from .navigation import (
100
111
  )
101
112
  from .platform import Platform
102
113
  from .screen import create_screen
103
- from .style import StyleSheet, ThemeContext
114
+ from .sdk import (
115
+ Props,
116
+ ViewHandler,
117
+ element_factory,
118
+ native_component,
119
+ register_component,
120
+ )
121
+ from .style import (
122
+ AlignItems,
123
+ AlignSelf,
124
+ AutoCapitalize,
125
+ Color,
126
+ Dimension,
127
+ EdgeInsets,
128
+ FlexDirection,
129
+ FontWeight,
130
+ JustifyContent,
131
+ KeyboardType,
132
+ Overflow,
133
+ Position,
134
+ ReturnKeyType,
135
+ ScaleType,
136
+ ShadowOffset,
137
+ Style,
138
+ StyleProp,
139
+ StyleSheet,
140
+ TextAlign,
141
+ TextDecoration,
142
+ ThemeContext,
143
+ TransformSpec,
144
+ resolve_style,
145
+ style,
146
+ )
104
147
 
105
148
  __all__ = [
106
149
  # Components
@@ -154,9 +197,31 @@ __all__ = [
154
197
  "create_drawer_navigator",
155
198
  "create_stack_navigator",
156
199
  "create_tab_navigator",
157
- # Styling
200
+ # Styling - typed primitives
201
+ "AlignItems",
202
+ "AlignSelf",
203
+ "AutoCapitalize",
204
+ "Color",
205
+ "Dimension",
206
+ "EdgeInsets",
207
+ "FlexDirection",
208
+ "FontWeight",
209
+ "JustifyContent",
210
+ "KeyboardType",
211
+ "Overflow",
212
+ "Position",
213
+ "ReturnKeyType",
214
+ "ScaleType",
215
+ "ShadowOffset",
216
+ "Style",
217
+ "StyleProp",
158
218
  "StyleSheet",
219
+ "TextAlign",
220
+ "TextDecoration",
159
221
  "ThemeContext",
222
+ "TransformSpec",
223
+ "resolve_style",
224
+ "style",
160
225
  # Animation
161
226
  "Animated",
162
227
  "AnimatedValue",
@@ -169,4 +234,11 @@ __all__ = [
169
234
  "Notifications",
170
235
  # Platform
171
236
  "Platform",
237
+ # Custom-component SDK
238
+ "Props",
239
+ "ViewHandler",
240
+ "element_factory",
241
+ "native_component",
242
+ "register_component",
243
+ "sdk",
172
244
  ]
@@ -58,7 +58,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple
58
58
 
59
59
  from .element import Element
60
60
  from .hooks import use_effect, use_ref
61
- from .style import StyleValue, resolve_style
61
+ from .style import StyleProp, resolve_style
62
62
 
63
63
  # Maximum frame rate at which the Python ticker drives animations.
64
64
  # We aim for 60 Hz but back off when no animation is active.
@@ -433,7 +433,7 @@ class _AnimationHandle:
433
433
  # ======================================================================
434
434
 
435
435
 
436
- def _resolve_style_with_values(style: StyleValue) -> Tuple[Dict[str, Any], Dict[str, AnimatedValue]]:
436
+ def _resolve_style_with_values(style: StyleProp) -> Tuple[Dict[str, Any], Dict[str, AnimatedValue]]:
437
437
  """Return ``(plain_style, animated_bindings)``.
438
438
 
439
439
  AnimatedValue entries in the style are replaced with their
@@ -35,10 +35,18 @@ Example:
35
35
  ```
36
36
  """
37
37
 
38
- from typing import Any, Callable, Dict, List, Optional
38
+ from typing import Any, Callable, Dict, List, Literal, Optional
39
39
 
40
40
  from .element import Element
41
- from .style import StyleValue, resolve_style
41
+ from .style import (
42
+ AutoCapitalize,
43
+ Color,
44
+ KeyboardType,
45
+ ReturnKeyType,
46
+ ScaleType,
47
+ StyleProp,
48
+ resolve_style,
49
+ )
42
50
 
43
51
  # ======================================================================
44
52
  # Leaf components
@@ -73,7 +81,7 @@ def _accessibility_props(
73
81
  def Text(
74
82
  text: str = "",
75
83
  *,
76
- style: StyleValue = None,
84
+ style: StyleProp = None,
77
85
  accessibility_label: Optional[str] = None,
78
86
  accessibility_hint: Optional[str] = None,
79
87
  accessibility_role: Optional[str] = None,
@@ -118,7 +126,7 @@ def Button(
118
126
  *,
119
127
  on_click: Optional[Callable[[], None]] = None,
120
128
  enabled: bool = True,
121
- style: StyleValue = None,
129
+ style: StyleProp = None,
122
130
  accessibility_label: Optional[str] = None,
123
131
  accessibility_hint: Optional[str] = None,
124
132
  accessible: Optional[bool] = None,
@@ -174,14 +182,14 @@ def TextInput(
174
182
  on_submit: Optional[Callable[[str], None]] = None,
175
183
  secure: bool = False,
176
184
  multiline: bool = False,
177
- keyboard_type: Optional[str] = None,
178
- auto_capitalize: Optional[str] = None,
185
+ keyboard_type: Optional[KeyboardType] = None,
186
+ auto_capitalize: Optional[AutoCapitalize] = None,
179
187
  auto_correct: Optional[bool] = None,
180
188
  auto_focus: bool = False,
181
- return_key_type: Optional[str] = None,
189
+ return_key_type: Optional[ReturnKeyType] = None,
182
190
  max_length: Optional[int] = None,
183
- placeholder_color: Optional[str] = None,
184
- style: StyleValue = None,
191
+ placeholder_color: Optional[Color] = None,
192
+ style: StyleProp = None,
185
193
  accessibility_label: Optional[str] = None,
186
194
  accessibility_hint: Optional[str] = None,
187
195
  accessible: Optional[bool] = None,
@@ -257,9 +265,9 @@ def TextInput(
257
265
  def Image(
258
266
  source: str = "",
259
267
  *,
260
- scale_type: Optional[str] = None,
261
- tint_color: Optional[str] = None,
262
- style: StyleValue = None,
268
+ scale_type: Optional[ScaleType] = None,
269
+ tint_color: Optional[Color] = None,
270
+ style: StyleProp = None,
263
271
  accessibility_label: Optional[str] = None,
264
272
  accessible: Optional[bool] = None,
265
273
  ref: Optional[Dict[str, Any]] = None,
@@ -307,7 +315,7 @@ def Switch(
307
315
  *,
308
316
  value: bool = False,
309
317
  on_change: Optional[Callable[[bool], None]] = None,
310
- style: StyleValue = None,
318
+ style: StyleProp = None,
311
319
  key: Optional[str] = None,
312
320
  ) -> Element:
313
321
  """Display a toggle switch.
@@ -331,7 +339,7 @@ def Switch(
331
339
  def ProgressBar(
332
340
  *,
333
341
  value: float = 0.0,
334
- style: StyleValue = None,
342
+ style: StyleProp = None,
335
343
  key: Optional[str] = None,
336
344
  ) -> Element:
337
345
  """Show determinate progress as a value between 0.0 and 1.0.
@@ -356,7 +364,7 @@ def ProgressBar(
356
364
  def ActivityIndicator(
357
365
  *,
358
366
  animating: bool = True,
359
- style: StyleValue = None,
367
+ style: StyleProp = None,
360
368
  key: Optional[str] = None,
361
369
  ) -> Element:
362
370
  """Show an indeterminate loading spinner.
@@ -378,7 +386,7 @@ def ActivityIndicator(
378
386
  def WebView(
379
387
  *,
380
388
  url: str = "",
381
- style: StyleValue = None,
389
+ style: StyleProp = None,
382
390
  key: Optional[str] = None,
383
391
  ) -> Element:
384
392
  """Embed web content from a URL.
@@ -440,7 +448,7 @@ def Slider(
440
448
  min_value: float = 0.0,
441
449
  max_value: float = 1.0,
442
450
  on_change: Optional[Callable[[float], None]] = None,
443
- style: StyleValue = None,
451
+ style: StyleProp = None,
444
452
  key: Optional[str] = None,
445
453
  ) -> Element:
446
454
  """Continuous-value slider between `min_value` and `max_value`.
@@ -475,7 +483,7 @@ def Slider(
475
483
 
476
484
  def View(
477
485
  *children: Element,
478
- style: StyleValue = None,
486
+ style: StyleProp = None,
479
487
  accessibility_label: Optional[str] = None,
480
488
  accessibility_hint: Optional[str] = None,
481
489
  accessibility_role: Optional[str] = None,
@@ -530,7 +538,7 @@ def View(
530
538
 
531
539
  def Column(
532
540
  *children: Element,
533
- style: StyleValue = None,
541
+ style: StyleProp = None,
534
542
  ref: Optional[Dict[str, Any]] = None,
535
543
  key: Optional[str] = None,
536
544
  ) -> Element:
@@ -571,7 +579,7 @@ def Column(
571
579
 
572
580
  def Row(
573
581
  *children: Element,
574
- style: StyleValue = None,
582
+ style: StyleProp = None,
575
583
  ref: Optional[Dict[str, Any]] = None,
576
584
  key: Optional[str] = None,
577
585
  ) -> Element:
@@ -614,7 +622,7 @@ def ScrollView(
614
622
  child: Optional[Element] = None,
615
623
  *,
616
624
  refresh_control: Optional[Dict[str, Any]] = None,
617
- style: StyleValue = None,
625
+ style: StyleProp = None,
618
626
  ref: Optional[Dict[str, Any]] = None,
619
627
  key: Optional[str] = None,
620
628
  ) -> Element:
@@ -647,7 +655,7 @@ def ScrollView(
647
655
 
648
656
  def SafeAreaView(
649
657
  *children: Element,
650
- style: StyleValue = None,
658
+ style: StyleProp = None,
651
659
  key: Optional[str] = None,
652
660
  ) -> Element:
653
661
  """Container that respects safe-area insets (notch, status bar, home indicator).
@@ -670,9 +678,9 @@ def Modal(
670
678
  visible: bool = False,
671
679
  on_dismiss: Optional[Callable[[], None]] = None,
672
680
  title: Optional[str] = None,
673
- animation_type: str = "slide",
681
+ animation_type: Literal["slide", "fade", "none"] = "slide",
674
682
  transparent: bool = False,
675
- style: StyleValue = None,
683
+ style: StyleProp = None,
676
684
  key: Optional[str] = None,
677
685
  ) -> Element:
678
686
  """Overlay modal dialog backed by a real native presentation.
@@ -720,7 +728,7 @@ def Pressable(
720
728
  on_press: Optional[Callable[[], None]] = None,
721
729
  on_long_press: Optional[Callable[[], None]] = None,
722
730
  pressed_opacity: float = 0.6,
723
- style: StyleValue = None,
731
+ style: StyleProp = None,
724
732
  accessibility_label: Optional[str] = None,
725
733
  accessibility_hint: Optional[str] = None,
726
734
  accessible: Optional[bool] = None,
@@ -811,7 +819,7 @@ def FlatList(
811
819
  separator_height: float = 0,
812
820
  refresh_control: Optional[Dict[str, Any]] = None,
813
821
  on_item_press: Optional[Callable[[int], None]] = None,
814
- style: StyleValue = None,
822
+ style: StyleProp = None,
815
823
  key: Optional[str] = None,
816
824
  ) -> Element:
817
825
  """Virtualized scrollable list that renders items from `data` lazily.
@@ -941,7 +949,7 @@ def SectionList(
941
949
  item_height: Optional[float] = None,
942
950
  section_header_height: float = 32.0,
943
951
  separator_height: float = 0,
944
- style: StyleValue = None,
952
+ style: StyleProp = None,
945
953
  key: Optional[str] = None,
946
954
  ) -> Element:
947
955
  """Virtualized list that supports section headers.
@@ -1040,8 +1048,8 @@ def SectionList(
1040
1048
 
1041
1049
  def StatusBar(
1042
1050
  *,
1043
- style: Optional[str] = None,
1044
- background_color: Optional[str] = None,
1051
+ style: Optional[Literal["light", "dark", "default"]] = None,
1052
+ background_color: Optional[Color] = None,
1045
1053
  hidden: Optional[bool] = None,
1046
1054
  key: Optional[str] = None,
1047
1055
  ) -> Element:
@@ -1075,8 +1083,8 @@ def StatusBar(
1075
1083
 
1076
1084
  def KeyboardAvoidingView(
1077
1085
  *children: Element,
1078
- behavior: str = "padding",
1079
- style: StyleValue = None,
1086
+ behavior: Literal["padding", "position"] = "padding",
1087
+ style: StyleProp = None,
1080
1088
  key: Optional[str] = None,
1081
1089
  ) -> Element:
1082
1090
  """Wrap content that should shift up when the keyboard is shown.
@@ -1106,7 +1114,7 @@ def RefreshControl(
1106
1114
  *,
1107
1115
  refreshing: bool = False,
1108
1116
  on_refresh: Optional[Callable[[], None]] = None,
1109
- tint_color: Optional[str] = None,
1117
+ tint_color: Optional[Color] = None,
1110
1118
  ) -> Dict[str, Any]:
1111
1119
  """Pull-to-refresh spec for [`ScrollView`][pythonnative.ScrollView] / [`FlatList`][pythonnative.FlatList].
1112
1120
 
@@ -1162,7 +1170,7 @@ def Picker(
1162
1170
  items: Optional[List[Dict[str, Any]]] = None,
1163
1171
  on_change: Optional[Callable[[Any], None]] = None,
1164
1172
  placeholder: str = "Select…",
1165
- style: StyleValue = None,
1173
+ style: StyleProp = None,
1166
1174
  key: Optional[str] = None,
1167
1175
  ) -> Element:
1168
1176
  """A select / dropdown widget.
@@ -237,12 +237,50 @@ class NativeViewRegistry:
237
237
  _registry: Optional[NativeViewRegistry] = None
238
238
 
239
239
 
240
+ def _active_platform_name() -> str:
241
+ """Return ``"android"`` or ``"ios"`` for the active runtime."""
242
+ from ..utils import IS_ANDROID
243
+
244
+ return "android" if IS_ANDROID else "ios"
245
+
246
+
247
+ def _register_builtin_handlers(registry: NativeViewRegistry) -> None:
248
+ """Register every built-in handler for the active platform."""
249
+ from ..utils import IS_ANDROID
250
+
251
+ if IS_ANDROID:
252
+ from .android import register_handlers
253
+ else:
254
+ from .ios import register_handlers
255
+ register_handlers(registry)
256
+
257
+
258
+ def _install_sdk_handlers(registry: NativeViewRegistry) -> None:
259
+ """Copy decorator-registered SDK handlers + entry-point plugins.
260
+
261
+ Imported lazily so unit tests that never touch the SDK don't pay the
262
+ entry-point discovery cost.
263
+ """
264
+ try:
265
+ from ..sdk._components import install_into_registry as _sdk_install
266
+ except Exception:
267
+ return
268
+ try:
269
+ _sdk_install(registry, _active_platform_name())
270
+ except Exception:
271
+ # A misbehaving plugin must not break PythonNative's startup.
272
+ pass
273
+
274
+
240
275
  def get_registry() -> NativeViewRegistry:
241
276
  """Return the process-wide registry, lazily registering handlers.
242
277
 
243
- The first call instantiates the registry and registers either the
244
- Android or iOS handlers based on `IS_ANDROID`. Subsequent calls
245
- return the same instance.
278
+ The first call instantiates the registry, registers either the
279
+ Android or iOS handlers based on `IS_ANDROID`, then layers on every
280
+ decorator-registered SDK handler (and any handlers exposed by
281
+ third-party packages via the
282
+ [`pythonnative.handlers`][pythonnative.sdk.ENTRY_POINT_GROUP] entry
283
+ point group). Subsequent calls return the same instance.
246
284
 
247
285
  Returns:
248
286
  The active `NativeViewRegistry`.
@@ -251,30 +289,40 @@ def get_registry() -> NativeViewRegistry:
251
289
  if _registry is not None:
252
290
  return _registry
253
291
  _registry = NativeViewRegistry()
292
+ _register_builtin_handlers(_registry)
293
+ _install_sdk_handlers(_registry)
294
+ return _registry
254
295
 
255
- from ..utils import IS_ANDROID
256
296
 
257
- if IS_ANDROID:
258
- from .android import register_handlers
297
+ def refresh_registry() -> NativeViewRegistry:
298
+ """Re-run SDK handler installation against the existing registry.
259
299
 
260
- register_handlers(_registry)
261
- else:
262
- from .ios import register_handlers
300
+ Call this after registering a new component at runtime if the
301
+ registry has already been instantiated. This is mostly useful in
302
+ REPL sessions and tests; the normal flow is "register, then call
303
+ [`get_registry`][pythonnative.native_views.get_registry]" and the
304
+ handlers come along automatically.
263
305
 
264
- register_handlers(_registry)
265
- return _registry
306
+ Returns:
307
+ The active `NativeViewRegistry`.
308
+ """
309
+ registry = get_registry()
310
+ _install_sdk_handlers(registry)
311
+ return registry
266
312
 
267
313
 
268
- def set_registry(registry: NativeViewRegistry) -> None:
314
+ def set_registry(registry: Optional[NativeViewRegistry]) -> None:
269
315
  """Install a custom registry (primarily for testing).
270
316
 
271
317
  Replaces the lazy singleton so subsequent
272
318
  [`get_registry`][pythonnative.native_views.get_registry] calls
273
319
  return `registry`. Pass a mock to drive the reconciler from
274
- unit tests without touching real native APIs.
320
+ unit tests without touching real native APIs. Pass ``None`` to
321
+ reset the singleton; the next ``get_registry`` call will then
322
+ rebuild it from scratch.
275
323
 
276
324
  Args:
277
- registry: The replacement registry.
325
+ registry: The replacement registry, or ``None`` to clear.
278
326
  """
279
327
  global _registry
280
328
  _registry = registry