pythonnative 0.40.0__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. pythonnative/__init__.py +411 -0
  2. pythonnative/_ios_log.py +92 -0
  3. pythonnative/_yoga.cp313-win_amd64.pyd +0 -0
  4. pythonnative/alerts.py +236 -0
  5. pythonnative/animated.py +1687 -0
  6. pythonnative/animation_graph.py +78 -0
  7. pythonnative/appearance.py +124 -0
  8. pythonnative/bootstrap.py +100 -0
  9. pythonnative/bridge/__init__.py +302 -0
  10. pythonnative/bridge/android.py +78 -0
  11. pythonnative/bridge/codec.py +170 -0
  12. pythonnative/bridge/commits.py +114 -0
  13. pythonnative/bridge/fake.py +243 -0
  14. pythonnative/bridge/ios.py +138 -0
  15. pythonnative/bridge/web.py +489 -0
  16. pythonnative/cli/__init__.py +7 -0
  17. pythonnative/cli/pn.py +1214 -0
  18. pythonnative/component.py +271 -0
  19. pythonnative/components/__init__.py +111 -0
  20. pythonnative/components/_base.py +166 -0
  21. pythonnative/components/controls.py +521 -0
  22. pythonnative/components/layout.py +518 -0
  23. pythonnative/components/lists.py +785 -0
  24. pythonnative/components/media.py +204 -0
  25. pythonnative/components/overlays.py +107 -0
  26. pythonnative/components/pressable.py +252 -0
  27. pythonnative/components/structural.py +167 -0
  28. pythonnative/components/text.py +291 -0
  29. pythonnative/devclient.py +824 -0
  30. pythonnative/devserver/__init__.py +34 -0
  31. pythonnative/devserver/server.py +718 -0
  32. pythonnative/devserver/static/animation_graph.js +82 -0
  33. pythonnative/devserver/static/bridge.js +147 -0
  34. pythonnative/devserver/static/colors.js +70 -0
  35. pythonnative/devserver/static/host.js +552 -0
  36. pythonnative/devserver/static/index.html +60 -0
  37. pythonnative/devserver/static/layout.js +85 -0
  38. pythonnative/devserver/static/preview.css +415 -0
  39. pythonnative/devserver/static/renderer.js +1994 -0
  40. pythonnative/devserver/static/shell.js +345 -0
  41. pythonnative/devserver/static/yoga/LICENSE +21 -0
  42. pythonnative/devserver/static/yoga/binaries/yoga-wasm-base64-esm.js +75 -0
  43. pythonnative/devserver/static/yoga/src/generated/YGEnums.d.ts +189 -0
  44. pythonnative/devserver/static/yoga/src/generated/YGEnums.js +211 -0
  45. pythonnative/devserver/static/yoga/src/generated/YGEnums.js.map +1 -0
  46. pythonnative/devserver/static/yoga/src/index.d.ts +12 -0
  47. pythonnative/devserver/static/yoga/src/index.js +16 -0
  48. pythonnative/devserver/static/yoga/src/index.js.map +1 -0
  49. pythonnative/devserver/static/yoga/src/load.d.ts +11 -0
  50. pythonnative/devserver/static/yoga/src/load.js +17 -0
  51. pythonnative/devserver/static/yoga/src/load.js.map +1 -0
  52. pythonnative/devserver/static/yoga/src/wrapAssembly.d.ts +155 -0
  53. pythonnative/devserver/static/yoga/src/wrapAssembly.js +125 -0
  54. pythonnative/devserver/static/yoga/src/wrapAssembly.js.map +1 -0
  55. pythonnative/devserver/watcher.py +267 -0
  56. pythonnative/devserver/ws.py +421 -0
  57. pythonnative/diagnostics.py +241 -0
  58. pythonnative/element.py +159 -0
  59. pythonnative/equality.py +19 -0
  60. pythonnative/events.py +231 -0
  61. pythonnative/gestures.py +1333 -0
  62. pythonnative/hooks.py +1621 -0
  63. pythonnative/hosts/__init__.py +63 -0
  64. pythonnative/hosts/base.py +596 -0
  65. pythonnative/hosts/native.py +302 -0
  66. pythonnative/hot_reload.py +585 -0
  67. pythonnative/layout.py +328 -0
  68. pythonnative/mutations.py +142 -0
  69. pythonnative/native/android/build.gradle +52 -0
  70. pythonnative/native/android/gradle.properties +3 -0
  71. pythonnative/native/android/settings.gradle +12 -0
  72. pythonnative/native/android/src/main/AndroidManifest.xml +11 -0
  73. pythonnative/native/android/src/main/cpp/CMakeLists.txt +9 -0
  74. pythonnative/native/android/src/main/cpp/yoga_jni.cpp +52 -0
  75. pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeModules.kt +5 -0
  76. pythonnative/native/android/src/main/java/com/pythonnative/generated/NativeProps.kt +3705 -0
  77. pythonnative/native/android/src/main/java/com/pythonnative/generated/PNContracts.kt +129 -0
  78. pythonnative/native/android/src/main/java/com/pythonnative/runtime/PNBridge.kt +261 -0
  79. pythonnative/native/android/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  80. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationGraph.kt +108 -0
  81. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  82. pythonnative/native/android/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +257 -0
  83. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/CommitState.kt +106 -0
  84. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  85. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +40 -0
  86. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  87. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  88. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  89. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +117 -0
  90. pythonnative/native/android/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  91. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +37 -0
  92. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +32 -0
  93. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  94. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  95. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  96. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  97. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  98. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +162 -0
  99. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  100. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  101. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +86 -0
  102. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  103. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  104. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScreenStackManager.kt +110 -0
  105. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  106. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  107. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  108. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  109. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +250 -0
  110. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  111. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  112. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +159 -0
  113. pythonnative/native/android/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  114. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  115. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  116. pythonnative/native/android/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  117. pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/NativeLayout.kt +145 -0
  118. pythonnative/native/android/src/main/java/com/pythonnative/runtime/layout/YogaNode.kt +29 -0
  119. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  120. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  121. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +61 -0
  122. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  123. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +158 -0
  124. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  125. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +99 -0
  126. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  127. pythonnative/native/android/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  128. pythonnative/native/android/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  129. pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +188 -0
  130. pythonnative/native/android/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  131. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNAccessibilityDelegate.kt +48 -0
  132. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNBorderDrawable.kt +76 -0
  133. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  134. pythonnative/native/android/src/main/java/com/pythonnative/runtime/views/PNFrameLayout.kt +49 -0
  135. pythonnative/native/android/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  136. pythonnative/native/android/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  137. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  138. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  139. pythonnative/native/android/src/test/java/com/pythonnative/runtime/PromiseTest.kt +89 -0
  140. pythonnative/native/ios/Package.swift +23 -0
  141. pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimationGraph.swift +199 -0
  142. pythonnative/native/ios/Sources/PythonNativeKit/Animation/PNAnimator.swift +312 -0
  143. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNBridge.swift +255 -0
  144. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNCommit.swift +104 -0
  145. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNRegistry.swift +142 -0
  146. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNTransaction.swift +166 -0
  147. pythonnative/native/ios/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  148. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNButtonManagers.swift +207 -0
  149. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  150. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  151. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  152. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  153. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNImageManager.swift +222 -0
  154. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  155. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  156. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScreenStackManager.swift +107 -0
  157. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  158. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  159. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextInputManager.swift +297 -0
  160. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  161. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  162. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  163. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  164. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  165. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +155 -0
  166. pythonnative/native/ios/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  167. pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeModules.swift +1 -0
  168. pythonnative/native/ios/Sources/PythonNativeKit/Generated/NativeProps.swift +3775 -0
  169. pythonnative/native/ios/Sources/PythonNativeKit/Generated/PNContracts.swift +46 -0
  170. pythonnative/native/ios/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  171. pythonnative/native/ios/Sources/PythonNativeKit/Layout/PNLayout.swift +173 -0
  172. pythonnative/native/ios/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  173. pythonnative/native/ios/Sources/PythonNativeKit/Modules/HostModule.swift +80 -0
  174. pythonnative/native/ios/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  175. pythonnative/native/ios/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  176. pythonnative/native/ios/Sources/PythonNativeKit/Modules/PNNativeModule.swift +168 -0
  177. pythonnative/native/ios/Sources/PythonNativeKit/Modules/PermissionModules.swift +266 -0
  178. pythonnative/native/ios/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  179. pythonnative/native/ios/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  180. pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  181. pythonnative/native/ios/Sources/PythonNativeKit/Screens/PNViewController.swift +237 -0
  182. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  183. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  184. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  185. pythonnative/native/ios/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  186. pythonnative/native/ios/Tests/PythonNativeKitTests/PNAnimationGraphTests.swift +26 -0
  187. pythonnative/native/ios/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  188. pythonnative/native/ios/Tests/PythonNativeKitTests/PNCommitTests.swift +86 -0
  189. pythonnative/native/ios/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  190. pythonnative/native/ios/Tests/PythonNativeKitTests/PNManagerTests.swift +126 -0
  191. pythonnative/native/ios/Tests/PythonNativeKitTests/PNModuleTests.swift +111 -0
  192. pythonnative/native/ios/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  193. pythonnative/native/yoga/LICENSE +21 -0
  194. pythonnative/native/yoga/Package.swift +9 -0
  195. pythonnative/native/yoga/VENDORED.md +2 -0
  196. pythonnative/native/yoga/include/PNStyle.h +10 -0
  197. pythonnative/native/yoga/include/module.modulemap +5 -0
  198. pythonnative/native/yoga/include/yoga/YGConfig.h +158 -0
  199. pythonnative/native/yoga/include/yoga/YGEnums.h +142 -0
  200. pythonnative/native/yoga/include/yoga/YGMacros.h +99 -0
  201. pythonnative/native/yoga/include/yoga/YGNode.h +304 -0
  202. pythonnative/native/yoga/include/yoga/YGNodeLayout.h +35 -0
  203. pythonnative/native/yoga/include/yoga/YGNodeStyle.h +130 -0
  204. pythonnative/native/yoga/include/yoga/YGPixelGrid.h +29 -0
  205. pythonnative/native/yoga/include/yoga/YGValue.h +84 -0
  206. pythonnative/native/yoga/include/yoga/Yoga.h +21 -0
  207. pythonnative/native/yoga/python.cpp +16 -0
  208. pythonnative/native/yoga/style.cpp +272 -0
  209. pythonnative/native/yoga/yoga/CMakeLists.txt +41 -0
  210. pythonnative/native/yoga/yoga/YGConfig.cpp +92 -0
  211. pythonnative/native/yoga/yoga/YGConfig.h +158 -0
  212. pythonnative/native/yoga/yoga/YGEnums.cpp +262 -0
  213. pythonnative/native/yoga/yoga/YGEnums.h +142 -0
  214. pythonnative/native/yoga/yoga/YGMacros.h +99 -0
  215. pythonnative/native/yoga/yoga/YGNode.cpp +366 -0
  216. pythonnative/native/yoga/yoga/YGNode.h +304 -0
  217. pythonnative/native/yoga/yoga/YGNodeLayout.cpp +92 -0
  218. pythonnative/native/yoga/yoga/YGNodeLayout.h +35 -0
  219. pythonnative/native/yoga/yoga/YGNodeStyle.cpp +405 -0
  220. pythonnative/native/yoga/yoga/YGNodeStyle.h +130 -0
  221. pythonnative/native/yoga/yoga/YGPixelGrid.cpp +22 -0
  222. pythonnative/native/yoga/yoga/YGPixelGrid.h +29 -0
  223. pythonnative/native/yoga/yoga/YGValue.cpp +20 -0
  224. pythonnative/native/yoga/yoga/YGValue.h +84 -0
  225. pythonnative/native/yoga/yoga/Yoga.h +21 -0
  226. pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.cpp +563 -0
  227. pythonnative/native/yoga/yoga/algorithm/AbsoluteLayout.h +41 -0
  228. pythonnative/native/yoga/yoga/algorithm/Align.h +72 -0
  229. pythonnative/native/yoga/yoga/algorithm/Baseline.cpp +78 -0
  230. pythonnative/native/yoga/yoga/algorithm/Baseline.h +21 -0
  231. pythonnative/native/yoga/yoga/algorithm/BoundAxis.h +79 -0
  232. pythonnative/native/yoga/yoga/algorithm/Cache.cpp +121 -0
  233. pythonnative/native/yoga/yoga/algorithm/Cache.h +30 -0
  234. pythonnative/native/yoga/yoga/algorithm/CalculateLayout.cpp +2428 -0
  235. pythonnative/native/yoga/yoga/algorithm/CalculateLayout.h +38 -0
  236. pythonnative/native/yoga/yoga/algorithm/FlexDirection.h +120 -0
  237. pythonnative/native/yoga/yoga/algorithm/FlexLine.cpp +124 -0
  238. pythonnative/native/yoga/yoga/algorithm/FlexLine.h +75 -0
  239. pythonnative/native/yoga/yoga/algorithm/PixelGrid.cpp +132 -0
  240. pythonnative/native/yoga/yoga/algorithm/PixelGrid.h +29 -0
  241. pythonnative/native/yoga/yoga/algorithm/SizingMode.h +73 -0
  242. pythonnative/native/yoga/yoga/algorithm/TrailingPosition.h +44 -0
  243. pythonnative/native/yoga/yoga/config/Config.cpp +135 -0
  244. pythonnative/native/yoga/yoga/config/Config.h +92 -0
  245. pythonnative/native/yoga/yoga/debug/AssertFatal.cpp +53 -0
  246. pythonnative/native/yoga/yoga/debug/AssertFatal.h +29 -0
  247. pythonnative/native/yoga/yoga/debug/Log.cpp +107 -0
  248. pythonnative/native/yoga/yoga/debug/Log.h +34 -0
  249. pythonnative/native/yoga/yoga/enums/Align.h +47 -0
  250. pythonnative/native/yoga/yoga/enums/BoxSizing.h +40 -0
  251. pythonnative/native/yoga/yoga/enums/Dimension.h +40 -0
  252. pythonnative/native/yoga/yoga/enums/Direction.h +41 -0
  253. pythonnative/native/yoga/yoga/enums/Display.h +41 -0
  254. pythonnative/native/yoga/yoga/enums/Edge.h +47 -0
  255. pythonnative/native/yoga/yoga/enums/Errata.h +41 -0
  256. pythonnative/native/yoga/yoga/enums/ExperimentalFeature.h +39 -0
  257. pythonnative/native/yoga/yoga/enums/FlexDirection.h +42 -0
  258. pythonnative/native/yoga/yoga/enums/Gutter.h +41 -0
  259. pythonnative/native/yoga/yoga/enums/Justify.h +44 -0
  260. pythonnative/native/yoga/yoga/enums/LogLevel.h +44 -0
  261. pythonnative/native/yoga/yoga/enums/MeasureMode.h +41 -0
  262. pythonnative/native/yoga/yoga/enums/NodeType.h +40 -0
  263. pythonnative/native/yoga/yoga/enums/Overflow.h +41 -0
  264. pythonnative/native/yoga/yoga/enums/PhysicalEdge.h +21 -0
  265. pythonnative/native/yoga/yoga/enums/PositionType.h +41 -0
  266. pythonnative/native/yoga/yoga/enums/Unit.h +42 -0
  267. pythonnative/native/yoga/yoga/enums/Wrap.h +41 -0
  268. pythonnative/native/yoga/yoga/enums/YogaEnums.h +85 -0
  269. pythonnative/native/yoga/yoga/event/event.cpp +87 -0
  270. pythonnative/native/yoga/yoga/event/event.h +130 -0
  271. pythonnative/native/yoga/yoga/module.modulemap +21 -0
  272. pythonnative/native/yoga/yoga/node/CachedMeasurement.h +53 -0
  273. pythonnative/native/yoga/yoga/node/LayoutResults.cpp +48 -0
  274. pythonnative/native/yoga/yoga/node/LayoutResults.h +122 -0
  275. pythonnative/native/yoga/yoga/node/LayoutableChildren.h +148 -0
  276. pythonnative/native/yoga/yoga/node/Node.cpp +443 -0
  277. pythonnative/native/yoga/yoga/node/Node.h +337 -0
  278. pythonnative/native/yoga/yoga/numeric/Comparison.h +81 -0
  279. pythonnative/native/yoga/yoga/numeric/FloatOptional.h +93 -0
  280. pythonnative/native/yoga/yoga/style/SmallValueBuffer.h +133 -0
  281. pythonnative/native/yoga/yoga/style/Style.h +757 -0
  282. pythonnative/native/yoga/yoga/style/StyleLength.h +107 -0
  283. pythonnative/native/yoga/yoga/style/StyleValueHandle.h +98 -0
  284. pythonnative/native/yoga/yoga/style/StyleValuePool.h +127 -0
  285. pythonnative/native_modules/__init__.py +124 -0
  286. pythonnative/native_modules/app_state.py +99 -0
  287. pythonnative/native_modules/battery.py +75 -0
  288. pythonnative/native_modules/biometrics.py +42 -0
  289. pythonnative/native_modules/camera.py +65 -0
  290. pythonnative/native_modules/clipboard.py +46 -0
  291. pythonnative/native_modules/fallback.py +324 -0
  292. pythonnative/native_modules/file_system.py +145 -0
  293. pythonnative/native_modules/haptics.py +75 -0
  294. pythonnative/native_modules/linking.py +123 -0
  295. pythonnative/native_modules/location.py +75 -0
  296. pythonnative/native_modules/net_info.py +112 -0
  297. pythonnative/native_modules/notifications.py +129 -0
  298. pythonnative/native_modules/permissions.py +108 -0
  299. pythonnative/native_modules/registry.py +526 -0
  300. pythonnative/native_modules/secure_store.py +52 -0
  301. pythonnative/native_modules/share.py +51 -0
  302. pythonnative/native_views/__init__.py +375 -0
  303. pythonnative/native_views/base.py +316 -0
  304. pythonnative/native_views/bridge_backend.py +277 -0
  305. pythonnative/navigation/__init__.py +103 -0
  306. pythonnative/navigation/container.py +154 -0
  307. pythonnative/navigation/handle.py +540 -0
  308. pythonnative/navigation/hooks.py +133 -0
  309. pythonnative/navigation/host.py +58 -0
  310. pythonnative/navigation/linking.py +200 -0
  311. pythonnative/navigation/navigators.py +659 -0
  312. pythonnative/navigation/screen.py +149 -0
  313. pythonnative/navigation/state.py +269 -0
  314. pythonnative/net.py +248 -0
  315. pythonnative/platform.py +166 -0
  316. pythonnative/platform_metrics.py +252 -0
  317. pythonnative/preview.py +287 -0
  318. pythonnative/profiling.py +101 -0
  319. pythonnative/project/__init__.py +68 -0
  320. pythonnative/project/android.py +557 -0
  321. pythonnative/project/builder.py +817 -0
  322. pythonnative/project/config.py +773 -0
  323. pythonnative/project/deps.py +719 -0
  324. pythonnative/project/devices.py +304 -0
  325. pythonnative/project/doctor.py +283 -0
  326. pythonnative/project/fingerprint.py +171 -0
  327. pythonnative/project/icons.py +247 -0
  328. pythonnative/project/ios.py +325 -0
  329. pythonnative/project/lockfile.py +100 -0
  330. pythonnative/project/permissions.py +361 -0
  331. pythonnative/project/plugins.py +519 -0
  332. pythonnative/project/runtime_assets.py +183 -0
  333. pythonnative/py.typed +0 -0
  334. pythonnative/query.py +153 -0
  335. pythonnative/reconciler/__init__.py +29 -0
  336. pythonnative/reconciler/boundaries.py +375 -0
  337. pythonnative/reconciler/children.py +88 -0
  338. pythonnative/reconciler/core.py +1165 -0
  339. pythonnative/reconciler/layout_pass.py +393 -0
  340. pythonnative/reconciler/vnode.py +266 -0
  341. pythonnative/refresh.py +55 -0
  342. pythonnative/runtime.py +306 -0
  343. pythonnative/scheduler.py +159 -0
  344. pythonnative/sdk/__init__.py +153 -0
  345. pythonnative/sdk/_components.py +427 -0
  346. pythonnative/sdk/builtins.py +91 -0
  347. pythonnative/sdk/codegen.py +149 -0
  348. pythonnative/sdk/module_codegen.py +184 -0
  349. pythonnative/sdk/schema.py +224 -0
  350. pythonnative/sdk/templates/contracts.kt +46 -0
  351. pythonnative/sdk/templates/contracts.swift +46 -0
  352. pythonnative/storage.py +184 -0
  353. pythonnative/style.py +841 -0
  354. pythonnative/suspense.py +183 -0
  355. pythonnative/templates/android_template/app/build.gradle +75 -0
  356. pythonnative/templates/android_template/app/proguard-rules.pro +21 -0
  357. pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +24 -0
  358. pythonnative/templates/android_template/app/src/debug/AndroidManifest.xml +7 -0
  359. pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +32 -0
  360. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +130 -0
  361. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -0
  362. pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  363. pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +30 -0
  364. pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +10 -0
  365. pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
  366. pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
  367. pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  368. pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  369. pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  370. pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  371. pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  372. pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  373. pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  374. pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  375. pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  376. pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  377. pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +22 -0
  378. pythonnative/templates/android_template/app/src/main/res/values/colors.xml +5 -0
  379. pythonnative/templates/android_template/app/src/main/res/values/strings.xml +5 -0
  380. pythonnative/templates/android_template/app/src/main/res/values/themes.xml +9 -0
  381. pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +7 -0
  382. pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +13 -0
  383. pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +19 -0
  384. pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +17 -0
  385. pythonnative/templates/android_template/build.gradle +10 -0
  386. pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  387. pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +6 -0
  388. pythonnative/templates/android_template/gradle.properties +23 -0
  389. pythonnative/templates/android_template/gradlew +185 -0
  390. pythonnative/templates/android_template/gradlew.bat +89 -0
  391. pythonnative/templates/android_template/settings.gradle +17 -0
  392. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +51 -0
  393. pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  394. pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
  395. pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +6 -0
  396. pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +25 -0
  397. pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
  398. pythonnative/templates/ios_template/ios_template/Info.plist +28 -0
  399. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +390 -0
  400. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +61 -0
  401. pythonnative/templates/ios_template/ios_template/ViewController.swift +23 -0
  402. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +745 -0
  403. pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  404. pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +36 -0
  405. pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +41 -0
  406. pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +32 -0
  407. pythonnative/testing/__init__.py +53 -0
  408. pythonnative/testing/backend.py +276 -0
  409. pythonnative/testing/harness.py +369 -0
  410. pythonnative/utils.py +145 -0
  411. pythonnative-0.40.0.dist-info/DELVEWHEEL +2 -0
  412. pythonnative-0.40.0.dist-info/METADATA +150 -0
  413. pythonnative-0.40.0.dist-info/RECORD +418 -0
  414. pythonnative-0.40.0.dist-info/WHEEL +5 -0
  415. pythonnative-0.40.0.dist-info/entry_points.txt +2 -0
  416. pythonnative-0.40.0.dist-info/licenses/LICENSE +21 -0
  417. pythonnative-0.40.0.dist-info/top_level.txt +1 -0
  418. pythonnative.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
pythonnative/hooks.py ADDED
@@ -0,0 +1,1621 @@
1
+ """Hook primitives for function components.
2
+
3
+ Provides React-like hooks for managing state, effects, memoization, and
4
+ context within components decorated with
5
+ [`component`][pythonnative.component.component]. Hooks must be called at the top
6
+ level of a component (not inside conditionals or loops) so they map to
7
+ the same slot across renders. In dev mode the framework verifies this
8
+ and raises [`HookOrderError`][pythonnative.diagnostics.HookOrderError]
9
+ on a violation instead of silently cross-wiring state.
10
+
11
+ Two effect phases exist, mirroring React:
12
+
13
+ - [`use_layout_effect`][pythonnative.use_layout_effect] callbacks run
14
+ synchronously inside the commit, after native mutations and the
15
+ layout pass have been applied. They can measure committed frames and
16
+ issue imperative view commands before the user sees the new frame.
17
+ - [`use_effect`][pythonnative.use_effect] callbacks (passive effects)
18
+ run after the layout effects, at the end of the same commit. An
19
+ effect may be an ``async def``; it runs as a task on the framework
20
+ loop and is cancelled when its dependencies change or the component
21
+ unmounts.
22
+
23
+ The current hook state travels in a :mod:`contextvars` context rather
24
+ than a plain global, so ``async def`` component bodies keep their hook
25
+ identity across ``await`` boundaries even when several coroutine
26
+ renders interleave on the event loop.
27
+
28
+ Hooks talk to the reconciler through the small
29
+ [`RenderOwner`][pythonnative.hooks.RenderOwner] protocol (mark a
30
+ component dirty, request a render, defer a transition, register a back
31
+ handler). That is the whole contract between the two modules.
32
+
33
+ Example:
34
+ ```python
35
+ import pythonnative as pn
36
+
37
+ @pn.component
38
+ def Counter(initial: int = 0):
39
+ count, set_count = pn.use_state(initial)
40
+ return pn.Column(
41
+ pn.Text(f"Count: {count}"),
42
+ pn.Button("+", on_press=lambda: set_count(count + 1)),
43
+ )
44
+ ```
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import asyncio
50
+ import inspect
51
+ from contextvars import ContextVar, Token
52
+ from dataclasses import dataclass, field, replace
53
+ from typing import (
54
+ Any,
55
+ Awaitable,
56
+ Callable,
57
+ Dict,
58
+ Generic,
59
+ List,
60
+ Optional,
61
+ Protocol,
62
+ Tuple,
63
+ TypeVar,
64
+ Union,
65
+ overload,
66
+ )
67
+
68
+ from . import diagnostics
69
+ from .element import Element, Node
70
+ from .platform_metrics import SafeAreaInsets, WindowDimensions
71
+ from .runtime import TaskScope, _scope, call_on_application_thread
72
+ from .scheduler import TransitionQueue, in_transition, run_in_transition, schedule_trigger
73
+ from .suspense import Resource
74
+
75
+ T = TypeVar("T")
76
+
77
+ StateSetter = Callable[[Union[T, Callable[[T], T]]], None]
78
+ """Setter returned by [`use_state`][pythonnative.use_state]: accepts a value or ``current -> new``."""
79
+
80
+ _SENTINEL = object()
81
+
82
+ # The component whose body is currently executing. A ContextVar (not a
83
+ # global or thread-local) so coroutine component bodies resume with the
84
+ # right hook state after every ``await``, no matter how renders
85
+ # interleave on the loop.
86
+ _hook_context: ContextVar[Optional["HookState"]] = ContextVar("pn_hook_state", default=None)
87
+
88
+
89
+ # ======================================================================
90
+ # Reconciler contract
91
+ # ======================================================================
92
+
93
+
94
+ class RenderOwner(Protocol):
95
+ """What a hook needs from the object that renders its component.
96
+
97
+ The reconciler implements this; tests may substitute a stub.
98
+ """
99
+
100
+ transitions: TransitionQueue
101
+
102
+ def mark_dirty(self, vnode: Any) -> None:
103
+ """Queue ``vnode``'s component for a local re-render."""
104
+
105
+ def request_render(self) -> None:
106
+ """Ask the host to flush dirty components (may be deferred)."""
107
+
108
+ def register_back_handler(self, handler: Callable[[], bool]) -> Callable[[], None]:
109
+ """Register a system back-press handler; returns an unregister callable."""
110
+
111
+
112
+ # ======================================================================
113
+ # Ref
114
+ # ======================================================================
115
+
116
+
117
+ class Ref(Generic[T]):
118
+ """Mutable container returned by [`use_ref`][pythonnative.use_ref].
119
+
120
+ A ``Ref`` holds one value on its ``current`` attribute. Mutating
121
+ ``current`` never triggers a re-render, which makes refs the right
122
+ place for timers, last-seen values, and imperative handles.
123
+
124
+ When a ``Ref`` is passed to a built-in element via the ``ref=``
125
+ prop, the reconciler populates ``current`` with the underlying
126
+ native view (``UIView`` on iOS, ``android.view.View`` on Android,
127
+ a DOM element in the browser preview) after commit, and clears it back to
128
+ ``None`` on unmount. Composite components (e.g.
129
+ [`FlatList`][pythonnative.FlatList]) instead publish a typed
130
+ controller object on ``current`` via
131
+ [`use_imperative_handle`][pythonnative.use_imperative_handle].
132
+
133
+ Attributes:
134
+ current: The referenced value. ``None`` until populated.
135
+ """
136
+
137
+ __slots__ = ("current", "_pn_tag", "_pn_frame")
138
+
139
+ def __init__(self, initial: Optional[T] = None) -> None:
140
+ self.current: Optional[T] = initial
141
+ # Internal: the native view tag, populated by the reconciler
142
+ # when the ref is attached to a built-in element.
143
+ self._pn_tag: Optional[int] = None
144
+ # Internal: the last committed frame ``(x, y, w, h)``, mirrored
145
+ # by the layout pass so Python code can read measured geometry
146
+ # without a native round-trip.
147
+ self._pn_frame: Optional[Tuple[float, float, float, float]] = None
148
+
149
+ def __repr__(self) -> str:
150
+ return f"Ref({self.current!r})"
151
+
152
+
153
+ # ======================================================================
154
+ # Hook state container
155
+ # ======================================================================
156
+
157
+
158
+ class HookState:
159
+ """Per-instance storage for one component's hooks.
160
+
161
+ Each component instance owns one ``HookState``. Hooks are matched
162
+ to slots by call order, so they must always be called in the same
163
+ order across renders. Effects scheduled during render are deferred
164
+ (layout effects into ``_pending_layout_effects``, passive effects
165
+ into ``_pending_effects``) and flushed by the reconciler in two
166
+ phases after native mutations commit.
167
+
168
+ Attributes:
169
+ states: One entry per ``use_state`` / ``use_reducer`` call.
170
+ effects: One ``(deps, cleanup)`` tuple per ``use_effect`` call.
171
+ layout_effects: One ``(deps, cleanup)`` tuple per
172
+ ``use_layout_effect`` call.
173
+ memos: One ``(deps, value)`` tuple per ``use_memo`` / ``use_callback``.
174
+ refs: One [`Ref`][pythonnative.Ref] per ``use_ref`` call.
175
+ owner: The [`RenderOwner`][pythonnative.hooks.RenderOwner]
176
+ (reconciler) this component is mounted in, or ``None``.
177
+ vnode: The reconciler's node for this component, or ``None``.
178
+ """
179
+
180
+ __slots__ = (
181
+ "states",
182
+ "effects",
183
+ "layout_effects",
184
+ "memos",
185
+ "refs",
186
+ "resources",
187
+ "state_index",
188
+ "effect_index",
189
+ "layout_effect_index",
190
+ "memo_index",
191
+ "ref_index",
192
+ "resource_index",
193
+ "context_deps",
194
+ "owner",
195
+ "vnode",
196
+ "_pending_effects",
197
+ "_pending_layout_effects",
198
+ "_pending_effects_mark",
199
+ "_pending_layout_effects_mark",
200
+ "_dirty",
201
+ "_hook_log",
202
+ "_hook_signature",
203
+ "_component_name",
204
+ "_async_task",
205
+ "_async_inputs",
206
+ "task_scope",
207
+ )
208
+
209
+ def __init__(self) -> None:
210
+ self.states: List[Any] = []
211
+ self.effects: List[Tuple[Any, Any]] = []
212
+ self.layout_effects: List[Tuple[Any, Any]] = []
213
+ self.memos: List[Tuple[Any, Any]] = []
214
+ self.refs: List[Ref] = []
215
+ # One ``(deps, Resource)`` per ``use_resource`` call.
216
+ self.resources: List[Tuple[Any, Resource]] = []
217
+ self.state_index: int = 0
218
+ self.effect_index: int = 0
219
+ self.layout_effect_index: int = 0
220
+ self.memo_index: int = 0
221
+ self.ref_index: int = 0
222
+ self.resource_index: int = 0
223
+ # Contexts read during the last completed render, keyed by
224
+ # ``id(context)``. The reconciler consults this when a
225
+ # Provider's value changes so consumers re-render even when a
226
+ # memoized ancestor skipped (reactive context).
227
+ self.context_deps: Dict[int, Any] = {}
228
+ self.owner: Optional[RenderOwner] = None
229
+ self.vnode: Any = None
230
+ self._pending_effects: List[Tuple[int, Callable, Any]] = []
231
+ self._pending_layout_effects: List[Tuple[int, Callable, Any]] = []
232
+ # Cleared by the reconciler after each successful render.
233
+ # ``use_state`` / ``use_reducer`` setters flip it to ``True``
234
+ # whenever they actually mutate state, so a memoized component
235
+ # still re-renders even when its props didn't change.
236
+ self._dirty: bool = False
237
+ # Dev-mode hook-order guard: the sequence of hook kinds called
238
+ # during the in-flight render, and the signature captured from
239
+ # the first successful render.
240
+ self._hook_log: Optional[List[str]] = None
241
+ self._hook_signature: Optional[List[str]] = None
242
+ self._component_name: str = ""
243
+ # For ``async def`` components: the asyncio task running the
244
+ # in-flight body, cancelled when a newer render supersedes it.
245
+ self._async_task: Any = None
246
+ self._async_inputs: Any = None
247
+ self.task_scope = TaskScope("component")
248
+ # Effect-queue lengths at ``begin_render``, so a suspended
249
+ # render can be rolled back without double-queueing effects.
250
+ self._pending_effects_mark: int = 0
251
+ self._pending_layout_effects_mark: int = 0
252
+
253
+ # ------------------------------------------------------------------
254
+ # Render lifecycle
255
+ # ------------------------------------------------------------------
256
+
257
+ def begin_render(self, component_name: str = "") -> None:
258
+ """Prepare for a render pass: reset cursors and the dev-mode hook log."""
259
+ self.state_index = 0
260
+ self.effect_index = 0
261
+ self.layout_effect_index = 0
262
+ self.memo_index = 0
263
+ self.ref_index = 0
264
+ self.resource_index = 0
265
+ self.context_deps = {}
266
+ if component_name:
267
+ self._component_name = component_name
268
+ self._hook_log = [] if diagnostics.is_dev() else None
269
+ self._pending_effects_mark = len(self._pending_effects)
270
+ self._pending_layout_effects_mark = len(self._pending_layout_effects)
271
+
272
+ def abort_render(self) -> None:
273
+ """Roll back a suspended render's effect queue.
274
+
275
+ A suspended body re-runs from the top on retry, so any effects
276
+ it queued before suspending would otherwise be queued twice.
277
+ """
278
+ del self._pending_effects[self._pending_effects_mark :]
279
+ del self._pending_layout_effects[self._pending_layout_effects_mark :]
280
+ self._hook_log = None
281
+
282
+ def finish_render(self) -> None:
283
+ """Finalize a successful render: lock in / verify the hook signature.
284
+
285
+ Raises:
286
+ HookOrderError: In dev mode, when this render called fewer
287
+ hooks than the previous one.
288
+ """
289
+ log = self._hook_log
290
+ self._hook_log = None
291
+ if log is None:
292
+ return
293
+ if self._hook_signature is None:
294
+ self._hook_signature = log
295
+ return
296
+ if len(log) < len(self._hook_signature):
297
+ missing = self._hook_signature[len(log)]
298
+ raise diagnostics.HookOrderError(
299
+ f"{self._component_name or 'Component'} rendered fewer hooks than the previous "
300
+ f"render (expected {missing!r} at position {len(log) + 1}). Hooks must be called "
301
+ "unconditionally, in the same order, on every render."
302
+ )
303
+
304
+ def record_hook(self, kind: str) -> None:
305
+ """Record a hook call for the dev-mode order guard.
306
+
307
+ Raises:
308
+ HookOrderError: In dev mode, when the hook at this position
309
+ differs from (or extends past) the previous render.
310
+ """
311
+ log = self._hook_log
312
+ if log is None:
313
+ return
314
+ position = len(log)
315
+ log.append(kind)
316
+ signature = self._hook_signature
317
+ if signature is None:
318
+ return
319
+ if position >= len(signature):
320
+ raise diagnostics.HookOrderError(
321
+ f"{self._component_name or 'Component'} rendered more hooks than the previous "
322
+ f"render ({kind!r} at position {position + 1}). Hooks must be called "
323
+ "unconditionally, in the same order, on every render."
324
+ )
325
+ if signature[position] != kind:
326
+ raise diagnostics.HookOrderError(
327
+ f"{self._component_name or 'Component'} called {kind!r} at position "
328
+ f"{position + 1}, but the previous render called {signature[position]!r} there. "
329
+ "Hooks must be called unconditionally, in the same order, on every render."
330
+ )
331
+
332
+ def reset_hook_signature(self) -> None:
333
+ """Forget the recorded hook signature (used by Fast Refresh)."""
334
+ self._hook_signature = None
335
+
336
+ # ------------------------------------------------------------------
337
+ # Effects
338
+ # ------------------------------------------------------------------
339
+
340
+ def flush_layout_effects(self) -> None:
341
+ """Run layout effects queued during render (commit phase, pre-paint)."""
342
+ pending = self._pending_layout_effects
343
+ self._pending_layout_effects = []
344
+ self._pending_layout_effects_mark = 0
345
+ for idx, effect_fn, deps in pending:
346
+ _, prev_cleanup = self.layout_effects[idx]
347
+ _run_cleanup(prev_cleanup)
348
+ token = _scope.set(self.task_scope)
349
+ try:
350
+ cleanup = _activate_effect(effect_fn)
351
+ finally:
352
+ _scope.reset(token)
353
+ self.layout_effects[idx] = (list(deps) if deps is not None else None, cleanup)
354
+
355
+ def flush_pending_effects(self) -> None:
356
+ """Run passive effects queued during render, after native commit.
357
+
358
+ For each pending effect, the previous cleanup is invoked first
359
+ (if any), then the new effect callback. The new return value
360
+ becomes the next cleanup. Effects that are ``async def`` (or
361
+ that return an awaitable) run as tasks on the framework loop;
362
+ their cleanup cancels the task, and a callable returned by the
363
+ coroutine runs as an additional cleanup once it completed.
364
+ """
365
+ pending = self._pending_effects
366
+ self._pending_effects = []
367
+ self._pending_effects_mark = 0
368
+ for idx, effect_fn, deps in pending:
369
+ _, prev_cleanup = self.effects[idx]
370
+ _run_cleanup(prev_cleanup)
371
+ token = _scope.set(self.task_scope)
372
+ try:
373
+ cleanup = _activate_effect(effect_fn)
374
+ finally:
375
+ _scope.reset(token)
376
+ self.effects[idx] = (list(deps) if deps is not None else None, cleanup)
377
+
378
+ def cleanup_all_effects(self) -> None:
379
+ """Run every outstanding cleanup function, then clear state.
380
+
381
+ Layout-effect cleanups run before passive-effect cleanups,
382
+ matching the mount order in reverse. Also cancels in-flight
383
+ resources and any pending ``async def`` body. Called when the
384
+ component instance is unmounted by the reconciler.
385
+ """
386
+ for i, (_deps, cleanup) in enumerate(self.layout_effects):
387
+ _run_cleanup(cleanup)
388
+ self.layout_effects[i] = (_SENTINEL, None)
389
+ for i, (_deps, cleanup) in enumerate(self.effects):
390
+ _run_cleanup(cleanup)
391
+ self.effects[i] = (_SENTINEL, None)
392
+ self._pending_effects = []
393
+ self._pending_layout_effects = []
394
+ self._pending_effects_mark = 0
395
+ self._pending_layout_effects_mark = 0
396
+ for _deps, resource in self.resources:
397
+ try:
398
+ resource.cancel()
399
+ except Exception:
400
+ pass
401
+ self.resources = []
402
+ self.task_scope.close()
403
+ driver = self._async_task
404
+ self._async_task = None
405
+ if driver is not None:
406
+ driver.cancel()
407
+
408
+ def detach(self) -> None:
409
+ """Break the back-references to the reconciler (on unmount).
410
+
411
+ Lets the unmounted component's hook state (and the closures it
412
+ captured) be freed by plain refcounting, which matters on iOS
413
+ where the cyclic GC is disabled.
414
+ """
415
+ self.owner = None
416
+ self.vnode = None
417
+
418
+
419
+ # ======================================================================
420
+ # Context helpers (framework-internal)
421
+ # ======================================================================
422
+
423
+
424
+ def current_hook_state() -> Optional[HookState]:
425
+ """Return the active ``HookState``, or ``None`` if no render is in flight."""
426
+ return _hook_context.get()
427
+
428
+
429
+ def install_hook_state(state: Optional[HookState]) -> Token[Optional[HookState]]:
430
+ """Install ``state`` as the active ``HookState``; returns the reset token."""
431
+ return _hook_context.set(state)
432
+
433
+
434
+ def restore_hook_state(token: Token[Optional[HookState]]) -> None:
435
+ """Restore the hook state that was active before ``install_hook_state``."""
436
+ _hook_context.reset(token)
437
+
438
+
439
+ def _require_hook_state(hook_name: str) -> HookState:
440
+ ctx = _hook_context.get()
441
+ if ctx is None:
442
+ raise RuntimeError(f"{hook_name} must be called inside a @component function")
443
+ return ctx
444
+
445
+
446
+ def _deps_changed(prev: Any, current: Any) -> bool:
447
+ """Return whether the dependency arrays differ enough to re-run an effect."""
448
+ if prev is _SENTINEL:
449
+ return True
450
+ if prev is None or current is None:
451
+ return True
452
+ if len(prev) != len(current):
453
+ return True
454
+ from .equality import equal
455
+
456
+ return any(not equal(p, c) for p, c in zip(prev, current))
457
+
458
+
459
+ def _run_cleanup(cleanup: Any) -> None:
460
+ if callable(cleanup):
461
+ try:
462
+ cleanup()
463
+ except Exception as exc:
464
+ # Never let a failing cleanup abort an unmount; surface it
465
+ # through the RedBox in dev mode, a warning otherwise.
466
+ if not diagnostics.report_error(exc, phase="effect cleanup"):
467
+ diagnostics.warn(f"Effect cleanup raised {exc!r}")
468
+
469
+
470
+ def _activate_effect(effect_fn: Callable) -> Any:
471
+ """Invoke an effect callback, running coroutine effects as tasks.
472
+
473
+ Synchronous effects return their cleanup directly. When the effect
474
+ is an ``async def`` (or returns an awaitable), the coroutine runs
475
+ as a task on the framework loop and the returned cleanup cancels
476
+ it; if the coroutine already finished and returned a callable, that
477
+ callable runs as the cleanup instead.
478
+ """
479
+ result = effect_fn()
480
+ if not inspect.isawaitable(result):
481
+ return result
482
+
483
+ from .runtime import run_async
484
+
485
+ future = run_async(result)
486
+
487
+ def _observe(fut: Any) -> None:
488
+ # Surface unhandled async-effect crashes instead of letting the
489
+ # future's exception vanish unobserved: RedBox in dev mode,
490
+ # traceback in production.
491
+ if fut.cancelled():
492
+ return
493
+ exc = fut.exception()
494
+ if exc is None or isinstance(exc, asyncio.CancelledError):
495
+ return
496
+ if not diagnostics.report_error(exc, phase="async effect"):
497
+ import traceback
498
+
499
+ traceback.print_exception(type(exc), exc, exc.__traceback__)
500
+
501
+ future.add_done_callback(_observe)
502
+
503
+ def _cleanup() -> None:
504
+ if future.cancelled():
505
+ return
506
+ if future.done():
507
+ if future.exception() is None:
508
+ returned = future.result()
509
+ if callable(returned):
510
+ try:
511
+ returned()
512
+ except Exception:
513
+ pass
514
+ return
515
+ future.cancel()
516
+
517
+ return _cleanup
518
+
519
+
520
+ def _notify_state_changed(ctx: HookState) -> None:
521
+ """Mark ``ctx``'s component dirty and schedule a render after a state change.
522
+
523
+ Enqueuing the owning node in the reconciler's dirty set is what
524
+ makes the subsequent render *local*: the host's trigger flushes
525
+ only the components marked here rather than the whole app. The
526
+ dirty mark is eager (so several setters coalesce), while the render
527
+ request respects [`batch_updates`][pythonnative.scheduler.batch_updates] and
528
+ defers to a later loop turn inside a transition (see
529
+ [`use_transition`][pythonnative.use_transition]).
530
+ """
531
+ ctx._dirty = True
532
+ owner = ctx.owner
533
+ if owner is None:
534
+ return
535
+ if ctx.vnode is not None:
536
+ owner.mark_dirty(ctx.vnode)
537
+ if in_transition():
538
+ owner.transitions.defer(owner.request_render)
539
+ else:
540
+ schedule_trigger(owner.request_render)
541
+
542
+
543
+ # ======================================================================
544
+ # State hooks
545
+ # ======================================================================
546
+
547
+
548
+ @overload
549
+ def use_state() -> Tuple[Optional[Any], StateSetter[Any]]: ...
550
+
551
+
552
+ @overload
553
+ def use_state(initial: Callable[[], T]) -> Tuple[T, StateSetter[T]]: ...
554
+
555
+
556
+ @overload
557
+ def use_state(initial: T) -> Tuple[T, StateSetter[T]]: ...
558
+
559
+
560
+ def use_state(initial: Any = None) -> Tuple[Any, StateSetter[Any]]:
561
+ """Return ``(value, setter)`` for component-local state.
562
+
563
+ State persists across re-renders of the same component instance.
564
+ The setter accepts a value or a ``current -> new`` callable; calling
565
+ it with an unchanged value is a no-op (no re-render).
566
+
567
+ Args:
568
+ initial: Initial state value. If callable, it is invoked once on
569
+ the first render (lazy initialization).
570
+
571
+ Returns:
572
+ A 2-tuple ``(value, setter)`` where ``value`` is the current
573
+ state and ``setter`` updates it (and triggers a re-render).
574
+
575
+ Raises:
576
+ RuntimeError: If called outside a ``@component`` function.
577
+
578
+ Example:
579
+ ```python
580
+ import pythonnative as pn
581
+
582
+ @pn.component
583
+ def Counter():
584
+ count, set_count = pn.use_state(0)
585
+ return pn.Button(
586
+ f"Count: {count}",
587
+ on_press=lambda: set_count(count + 1),
588
+ )
589
+ ```
590
+ """
591
+ ctx = _require_hook_state("use_state")
592
+ ctx.record_hook("use_state")
593
+
594
+ idx = ctx.state_index
595
+ ctx.state_index += 1
596
+
597
+ if idx >= len(ctx.states):
598
+ val = initial() if callable(initial) else initial
599
+ ctx.states.append(val)
600
+
601
+ current = ctx.states[idx]
602
+
603
+ def setter(new_value: Any) -> None:
604
+ def apply() -> None:
605
+ if ctx.task_scope.closed:
606
+ return
607
+ value = new_value(ctx.states[idx]) if callable(new_value) else new_value
608
+ from .equality import equal
609
+
610
+ if not equal(ctx.states[idx], value):
611
+ ctx.states[idx] = value
612
+ _notify_state_changed(ctx)
613
+
614
+ call_on_application_thread(apply)
615
+
616
+ return current, setter
617
+
618
+
619
+ def use_reducer(
620
+ reducer: Callable[[T, Any], T], initial_state: Union[T, Callable[[], T]]
621
+ ) -> Tuple[T, Callable[[Any], None]]:
622
+ """Return ``(state, dispatch)`` for reducer-based state management.
623
+
624
+ A reducer is a pure function that takes the current state and an
625
+ action and returns the next state. Use it instead of
626
+ [`use_state`][pythonnative.use_state] when state transitions are
627
+ complex enough that centralizing them in one function aids
628
+ readability and testing.
629
+
630
+ Args:
631
+ reducer: ``reducer(current_state, action) -> new_state``.
632
+ The component re-renders only when ``reducer`` returns a
633
+ value different from the current state.
634
+ initial_state: Initial state value, or a callable invoked once
635
+ on the first render.
636
+
637
+ Returns:
638
+ A 2-tuple ``(state, dispatch)`` where ``dispatch`` runs the
639
+ reducer with the supplied action.
640
+
641
+ Raises:
642
+ RuntimeError: If called outside a ``@component`` function.
643
+ """
644
+ ctx = _require_hook_state("use_reducer")
645
+ ctx.record_hook("use_reducer")
646
+
647
+ idx = ctx.state_index
648
+ ctx.state_index += 1
649
+
650
+ if idx >= len(ctx.states):
651
+ val = initial_state() if callable(initial_state) else initial_state
652
+ ctx.states.append(val)
653
+
654
+ current = ctx.states[idx]
655
+
656
+ def dispatch(action: Any) -> None:
657
+ new_state = reducer(ctx.states[idx], action)
658
+ if ctx.states[idx] is not new_state and ctx.states[idx] != new_state:
659
+ ctx.states[idx] = new_state
660
+ _notify_state_changed(ctx)
661
+
662
+ return current, dispatch
663
+
664
+
665
+ # ======================================================================
666
+ # Effect hooks
667
+ # ======================================================================
668
+
669
+
670
+ def use_effect(effect: Callable[[], Any], deps: Optional[list] = None) -> None:
671
+ """Schedule a side effect to run after the native commit.
672
+
673
+ Effects are queued during the render pass and flushed once the
674
+ reconciler has finished applying all native-view mutations, which
675
+ means effect callbacks can safely measure layout or interact with
676
+ committed native views.
677
+
678
+ The ``deps`` argument controls when the effect re-runs:
679
+
680
+ - ``None``: every render.
681
+ - ``[]``: mount only.
682
+ - ``[a, b]``: when ``a`` or ``b`` change (compared by identity, then ``==``).
683
+
684
+ A synchronous ``effect`` may return a cleanup callable; the previous
685
+ cleanup runs before the next effect (and on unmount).
686
+
687
+ An **async** ``effect`` (an ``async def``) runs as a task on the
688
+ framework loop. When ``deps`` change or the component unmounts, the
689
+ in-flight task is cancelled (:class:`asyncio.CancelledError` is
690
+ raised at its current ``await``), giving async effects structured
691
+ cancellation for free. If the coroutine finishes and returns a
692
+ callable, that callable runs as the cleanup instead.
693
+
694
+ Args:
695
+ effect: A zero-arg callable invoked after commit: either a
696
+ synchronous function (optionally returning a cleanup
697
+ callable) or an ``async def``.
698
+ deps: Dependency list, or ``None`` to run on every render.
699
+
700
+ Raises:
701
+ RuntimeError: If called outside a ``@component`` function.
702
+
703
+ Example:
704
+ ```python
705
+ import asyncio
706
+ import time
707
+
708
+ import pythonnative as pn
709
+
710
+ @pn.component
711
+ def Clock():
712
+ now, set_now = pn.use_state("")
713
+
714
+ async def tick():
715
+ while True:
716
+ set_now(time.strftime("%H:%M:%S"))
717
+ await asyncio.sleep(1)
718
+
719
+ pn.use_effect(tick, [])
720
+ return pn.Text(now)
721
+ ```
722
+ """
723
+ ctx = _require_hook_state("use_effect")
724
+ ctx.record_hook("use_effect")
725
+
726
+ idx = ctx.effect_index
727
+ ctx.effect_index += 1
728
+
729
+ if idx >= len(ctx.effects):
730
+ ctx.effects.append((_SENTINEL, None))
731
+ ctx._pending_effects.append((idx, effect, deps))
732
+ return
733
+
734
+ prev_deps, _prev_cleanup = ctx.effects[idx]
735
+ if _deps_changed(prev_deps, deps):
736
+ ctx._pending_effects.append((idx, effect, deps))
737
+
738
+
739
+ def use_layout_effect(effect: Callable[[], Any], deps: Optional[list] = None) -> None:
740
+ """Schedule a side effect that runs synchronously inside the commit.
741
+
742
+ Like [`use_effect`][pythonnative.use_effect], but the callback
743
+ fires *before* passive effects, immediately after native mutations
744
+ and the layout pass are applied. Use it when you need to measure a
745
+ committed frame (via a [`Ref`][pythonnative.Ref]) or issue an
746
+ imperative view command before the user sees the new frame, for
747
+ example scrolling a list into position on mount.
748
+
749
+ Prefer ``use_effect`` for everything else; layout effects block the
750
+ commit, so heavy work here delays the frame.
751
+
752
+ Args:
753
+ effect: A zero-arg callable invoked during commit. Optionally
754
+ returns a cleanup callable.
755
+ deps: Dependency list, or ``None`` to run on every render.
756
+
757
+ Raises:
758
+ RuntimeError: If called outside a ``@component`` function.
759
+ """
760
+ ctx = _require_hook_state("use_layout_effect")
761
+ ctx.record_hook("use_layout_effect")
762
+
763
+ idx = ctx.layout_effect_index
764
+ ctx.layout_effect_index += 1
765
+
766
+ if idx >= len(ctx.layout_effects):
767
+ ctx.layout_effects.append((_SENTINEL, None))
768
+ ctx._pending_layout_effects.append((idx, effect, deps))
769
+ return
770
+
771
+ prev_deps, _prev_cleanup = ctx.layout_effects[idx]
772
+ if _deps_changed(prev_deps, deps):
773
+ ctx._pending_layout_effects.append((idx, effect, deps))
774
+
775
+
776
+ # ======================================================================
777
+ # Memoization hooks
778
+ # ======================================================================
779
+
780
+
781
+ def use_memo(factory: Callable[[], T], deps: list) -> T:
782
+ """Return a memoized value that is recomputed only when ``deps`` change.
783
+
784
+ Use this for expensive computations whose inputs change rarely. For
785
+ cheap computations, plain inline code is faster (memoization itself
786
+ has overhead).
787
+
788
+ Args:
789
+ factory: Zero-arg callable returning the value.
790
+ deps: Dependency list. The value is recomputed when any element
791
+ differs from the previous render.
792
+
793
+ Returns:
794
+ The cached or freshly computed value.
795
+
796
+ Raises:
797
+ RuntimeError: If called outside a ``@component`` function.
798
+ """
799
+ ctx = _require_hook_state("use_memo")
800
+ ctx.record_hook("use_memo")
801
+
802
+ idx = ctx.memo_index
803
+ ctx.memo_index += 1
804
+
805
+ if idx >= len(ctx.memos):
806
+ value = factory()
807
+ ctx.memos.append((list(deps), value))
808
+ return value
809
+
810
+ prev_deps, prev_value = ctx.memos[idx]
811
+ if not _deps_changed(prev_deps, deps):
812
+ return prev_value
813
+
814
+ value = factory()
815
+ ctx.memos[idx] = (list(deps), value)
816
+ return value
817
+
818
+
819
+ F = TypeVar("F", bound=Callable[..., Any])
820
+
821
+
822
+ def use_callback(callback: F, deps: list) -> F:
823
+ """Return a stable reference to ``callback``, refreshed when ``deps`` change.
824
+
825
+ Equivalent to ``use_memo(lambda: callback, deps)``. Useful when
826
+ passing a function as a prop to a memoized child component, so the
827
+ child doesn't see a fresh function identity on every render.
828
+
829
+ Args:
830
+ callback: The callable to memoize.
831
+ deps: Dependency list controlling when the reference refreshes.
832
+
833
+ Returns:
834
+ A callable with stable identity across renders (until ``deps`` change).
835
+ """
836
+ return use_memo(lambda: callback, deps)
837
+
838
+
839
+ def use_ref(initial: Optional[T] = None) -> Ref[T]:
840
+ """Return a [`Ref`][pythonnative.Ref] that persists across renders.
841
+
842
+ Refs are useful for storing values that must survive renders without
843
+ triggering them: timers, last-seen values, native handles, and so on.
844
+
845
+ ``ref.current`` is also populated by the reconciler with the
846
+ underlying native view when the ref is passed via the ``ref=`` prop
847
+ on a built-in element, and cleared to ``None`` when that element
848
+ unmounts. Composite components such as
849
+ [`FlatList`][pythonnative.FlatList] publish a typed controller
850
+ object instead (see
851
+ [`use_imperative_handle`][pythonnative.use_imperative_handle]).
852
+
853
+ Args:
854
+ initial: Value placed at ``ref.current`` on first render.
855
+
856
+ Returns:
857
+ A [`Ref`][pythonnative.Ref]. Mutations to ``ref.current`` do
858
+ *not* trigger re-renders.
859
+
860
+ Raises:
861
+ RuntimeError: If called outside a ``@component`` function.
862
+ """
863
+ ctx = _require_hook_state("use_ref")
864
+ ctx.record_hook("use_ref")
865
+
866
+ idx = ctx.ref_index
867
+ ctx.ref_index += 1
868
+
869
+ if idx >= len(ctx.refs):
870
+ ref: Ref[T] = Ref(initial)
871
+ ctx.refs.append(ref)
872
+ return ref
873
+
874
+ return ctx.refs[idx]
875
+
876
+
877
+ def use_imperative_handle(
878
+ ref: Optional[Ref[Any]],
879
+ factory: Callable[[], Any],
880
+ deps: Optional[list] = None,
881
+ ) -> None:
882
+ """Publish a controller object on ``ref.current``.
883
+
884
+ The composite-component counterpart to passing ``ref=`` to a
885
+ built-in element. Call it inside a component that accepts a
886
+ ``ref`` prop to expose a curated imperative API (rather than the
887
+ raw native view) to the parent. The handle is installed during the
888
+ commit's layout-effect phase and cleared back to ``None`` on
889
+ unmount.
890
+
891
+ Args:
892
+ ref: The [`Ref`][pythonnative.Ref] received via the component's
893
+ ``ref`` prop. ``None`` is allowed (the parent didn't
894
+ request a handle), in which case this is a no-op.
895
+ factory: Zero-arg callable returning the handle object.
896
+ deps: Dependency list controlling when the handle is rebuilt.
897
+ ``None`` rebuilds on every render, matching effects.
898
+
899
+ Raises:
900
+ RuntimeError: If called outside a ``@component`` function.
901
+
902
+ Example:
903
+ ```python
904
+ @pn.component
905
+ def VideoPlayer(source: str, ref: pn.Ref | None = None):
906
+ pn.use_imperative_handle(ref, lambda: PlayerController(...), [source])
907
+ return pn.View(...)
908
+ ```
909
+ """
910
+
911
+ def _install() -> Optional[Callable[[], None]]:
912
+ if ref is None:
913
+ return None
914
+ ref.current = factory()
915
+
916
+ def _clear() -> None:
917
+ ref.current = None
918
+
919
+ return _clear
920
+
921
+ use_layout_effect(_install, deps)
922
+
923
+
924
+ # ======================================================================
925
+ # Async hooks
926
+ # ======================================================================
927
+
928
+
929
+ def use_resource(fetcher: Callable[[], Any], deps: Optional[list] = None) -> Resource[Any]:
930
+ """Start an async fetch and cache it across renders.
931
+
932
+ The fetch starts immediately (during render, not after commit) and
933
+ the resulting [`Resource`][pythonnative.Resource] is cached until
934
+ ``deps`` change, at which point the old fetch is cancelled and a
935
+ new one starts. Because results are cached, re-renders resolve
936
+ instantly; only genuinely new data suspends.
937
+
938
+ Consume the resource with ``resource.read()`` (suspends the render
939
+ while pending; pair with a [`Suspense`][pythonnative.Suspense]
940
+ boundary) or ``await resource`` inside an ``async def`` component.
941
+ Errors raised by the fetcher re-raise at the read site, so an
942
+ enclosing [`ErrorBoundary`][pythonnative.ErrorBoundary] catches
943
+ failures declaratively.
944
+
945
+ Args:
946
+ fetcher: Zero-arg ``async def`` (or plain callable) producing
947
+ the value. Synchronous fetchers resolve immediately and
948
+ never suspend.
949
+ deps: Dependency list controlling when to refetch. Defaults to
950
+ ``[]`` (fetch once per component instance).
951
+
952
+ Returns:
953
+ The cached [`Resource`][pythonnative.Resource].
954
+
955
+ Raises:
956
+ RuntimeError: If called outside a ``@component`` function.
957
+
958
+ Example:
959
+ ```python
960
+ @pn.component
961
+ async def UserCard(user_id: str):
962
+ user = await pn.use_resource(lambda: api.get_user(user_id), [user_id])
963
+ return pn.Text(user["name"])
964
+ ```
965
+ """
966
+ from .suspense import start_resource
967
+
968
+ ctx = _require_hook_state("use_resource")
969
+ ctx.record_hook("use_resource")
970
+
971
+ idx = ctx.resource_index
972
+ ctx.resource_index += 1
973
+ deps = [] if deps is None else deps
974
+
975
+ if idx >= len(ctx.resources):
976
+ resource = start_resource(fetcher)
977
+ ctx.resources.append((list(deps), resource))
978
+ return resource
979
+
980
+ prev_deps, prev_resource = ctx.resources[idx]
981
+ if not _deps_changed(prev_deps, deps):
982
+ return prev_resource
983
+
984
+ prev_resource.cancel()
985
+ resource = start_resource(fetcher)
986
+ ctx.resources[idx] = (list(deps), resource)
987
+ return resource
988
+
989
+
990
+ def use_transition() -> Tuple[bool, Callable[[Callable[[], None]], None]]:
991
+ """Return ``(is_pending, start_transition)`` for low-priority updates.
992
+
993
+ State updates made inside ``start_transition(fn)`` are marked as
994
+ *transitions*: instead of re-rendering synchronously, their render
995
+ is deferred to a later turn of the framework loop, so urgent
996
+ updates (typing, presses) queued in the meantime render first.
997
+ ``is_pending`` is ``True`` from the moment ``start_transition`` is
998
+ called until the deferred render has committed, which is exactly
999
+ when to show a lightweight busy indicator.
1000
+
1001
+ Returns:
1002
+ A 2-tuple ``(is_pending, start_transition)``.
1003
+
1004
+ Raises:
1005
+ RuntimeError: If called outside a ``@component`` function.
1006
+
1007
+ Example:
1008
+ ```python
1009
+ @pn.component
1010
+ def Search():
1011
+ query, set_query = pn.use_state("")
1012
+ results_for, set_results_for = pn.use_state("")
1013
+ is_pending, start_transition = pn.use_transition()
1014
+
1015
+ def on_change(text):
1016
+ set_query(text) # urgent: keep the input responsive
1017
+ start_transition(lambda: set_results_for(text))
1018
+
1019
+ return pn.Column(
1020
+ pn.TextInput(value=query, on_change=on_change),
1021
+ pn.ActivityIndicator() if is_pending else Results(results_for),
1022
+ )
1023
+ ```
1024
+ """
1025
+ ctx = _require_hook_state("use_transition")
1026
+
1027
+ is_pending, set_pending = use_state(False)
1028
+
1029
+ def start_transition(fn: Callable[[], None]) -> None:
1030
+ owner = ctx.owner
1031
+ if owner is None:
1032
+ fn()
1033
+ return
1034
+ set_pending(True)
1035
+ run_in_transition(fn)
1036
+ owner.transitions.on_complete(lambda: set_pending(False))
1037
+
1038
+ start = use_callback(start_transition, [])
1039
+ return is_pending, start
1040
+
1041
+
1042
+ def use_deferred_value(value: T) -> T:
1043
+ """Return a copy of ``value`` that lags behind during fast updates.
1044
+
1045
+ The returned value updates in a deferred (transition-priority)
1046
+ render after the urgent render that changed ``value`` has
1047
+ committed. Pass the deferred value to expensive subtrees (a
1048
+ filtered list, a chart) so the urgent part of the UI stays
1049
+ responsive while the expensive part catches up a beat later.
1050
+
1051
+ Args:
1052
+ value: The latest value.
1053
+
1054
+ Returns:
1055
+ The previous value while a newer one is still being adopted,
1056
+ then the latest value.
1057
+
1058
+ Raises:
1059
+ RuntimeError: If called outside a ``@component`` function.
1060
+ """
1061
+ _require_hook_state("use_deferred_value")
1062
+
1063
+ deferred, set_deferred = use_state(value)
1064
+
1065
+ def _adopt() -> None:
1066
+ run_in_transition(lambda: set_deferred(value))
1067
+
1068
+ use_effect(_adopt, [value])
1069
+ return deferred
1070
+
1071
+
1072
+ @dataclass(frozen=True)
1073
+ class QueryResult(Generic[T]):
1074
+ """Snapshot of a [`use_query`][pythonnative.use_query] subscription.
1075
+
1076
+ Attributes:
1077
+ data: The most recent successful result, or the ``initial``
1078
+ value before the first fetch completes.
1079
+ loading: ``True`` while a fetch is in flight (including the
1080
+ initial fetch and any refetches).
1081
+ error: The exception raised by the most recent failed fetch,
1082
+ or ``None`` if no fetch has failed since the last success.
1083
+ refetch: A zero-arg callable that triggers a refetch. Stable
1084
+ across renders.
1085
+ """
1086
+
1087
+ data: Optional[T] = None
1088
+ loading: bool = True
1089
+ error: Optional[BaseException] = None
1090
+ refetch: Callable[[], None] = field(default=lambda: None)
1091
+
1092
+
1093
+ def use_query(
1094
+ fetcher: Callable[[], Awaitable[T]],
1095
+ deps: Optional[list] = None,
1096
+ *,
1097
+ initial: Optional[T] = None,
1098
+ key: Any = None,
1099
+ client: Any = None,
1100
+ ) -> QueryResult[T]:
1101
+ """Subscribe to an async fetcher and re-render when its result changes.
1102
+
1103
+ The fetcher is called on mount and any time ``deps`` change, with
1104
+ cancellation propagated when the component unmounts mid-fetch.
1105
+
1106
+ Args:
1107
+ fetcher: Zero-arg ``async`` callable that resolves to the
1108
+ current data.
1109
+ deps: Dependency list. Refetches whenever any entry changes.
1110
+ initial: Optional starting value for ``data`` before the
1111
+ first fetch completes.
1112
+ key: Explicit hashable key for sharing results across subscribers.
1113
+ Include every input that identifies the shared result. Without a
1114
+ key, the query belongs to this hook and changes with ``deps``.
1115
+ client: QueryClient owning the shared cache. Defaults to the
1116
+ application's client.
1117
+
1118
+ Returns:
1119
+ A frozen [`QueryResult`][pythonnative.QueryResult] with
1120
+ ``data`` / ``loading`` / ``error`` / ``refetch``.
1121
+
1122
+ Raises:
1123
+ RuntimeError: If called outside a ``@component`` function.
1124
+
1125
+ Example:
1126
+ ```python
1127
+ @pn.component
1128
+ def UserCard(user_id: str):
1129
+ q = pn.use_query(lambda: api.get_user(user_id), [user_id])
1130
+ if q.loading:
1131
+ return pn.Text("Loading...")
1132
+ if q.error:
1133
+ return pn.Text(f"Error: {q.error}")
1134
+ return pn.Text(q.data["name"])
1135
+ ```
1136
+ """
1137
+ from .query import default_client
1138
+
1139
+ cache = client or default_client()
1140
+ local_key = use_memo(object, deps or [])
1141
+ if key is None:
1142
+ key = local_key
1143
+ snapshot = use_subscription(
1144
+ use_callback(lambda notify: cache.subscribe(key, fetcher, notify), [cache, key]),
1145
+ use_callback(lambda: cache.snapshot(key, initial), [cache, key]),
1146
+ )
1147
+ refetch = use_callback(lambda: cache.invalidate(key), [cache, key])
1148
+ return QueryResult(data=snapshot.data, loading=snapshot.loading, error=snapshot.error, refetch=refetch)
1149
+
1150
+
1151
+ @dataclass(frozen=True)
1152
+ class MutationState(Generic[T]):
1153
+ """Snapshot of a [`use_mutation`][pythonnative.use_mutation] subscription.
1154
+
1155
+ Attributes:
1156
+ data: The most recent successful return value of the mutator,
1157
+ or ``None`` if no mutation has succeeded yet.
1158
+ loading: ``True`` while a mutation is in flight.
1159
+ error: The exception raised by the most recent failed
1160
+ mutation, or ``None``.
1161
+ """
1162
+
1163
+ data: Optional[T] = None
1164
+ loading: bool = False
1165
+ error: Optional[BaseException] = None
1166
+
1167
+
1168
+ class MutationCall(Generic[T]):
1169
+ """Awaitable handle returned by a mutator trigger.
1170
+
1171
+ Returned by the second element of the
1172
+ [`use_mutation`][pythonnative.use_mutation] tuple. Awaiting the
1173
+ handle resolves to the mutator's return value (or re-raises its
1174
+ exception); discarding the handle is safe. Python won't warn
1175
+ about an unawaited coroutine because this is a plain object.
1176
+
1177
+ Example:
1178
+ ```python
1179
+ # Fire-and-forget:
1180
+ save_button.on_press = lambda: mutate(post)
1181
+
1182
+ # Or await for the result:
1183
+ async def submit():
1184
+ try:
1185
+ created = await mutate(post)
1186
+ except ApiError as exc:
1187
+ await pn.Alert.show(title="Save failed", message=str(exc))
1188
+ ```
1189
+ """
1190
+
1191
+ __slots__ = ("_future",)
1192
+
1193
+ def __init__(self, future: Any) -> None:
1194
+ self._future = future
1195
+
1196
+ def __await__(self) -> Any:
1197
+ future = self._future
1198
+ if isinstance(future, asyncio.Future):
1199
+ return future.__await__()
1200
+ return asyncio.wrap_future(future).__await__()
1201
+
1202
+ def cancel(self) -> bool:
1203
+ """Cancel the underlying mutation. Returns whether cancellation succeeded."""
1204
+ return self._future.cancel()
1205
+
1206
+ def done(self) -> bool:
1207
+ """Whether the underlying mutation has finished."""
1208
+ return self._future.done()
1209
+
1210
+
1211
+ def use_mutation(
1212
+ mutator: Callable[..., Awaitable[T]],
1213
+ ) -> Tuple[MutationState[T], Callable[..., MutationCall[T]]]:
1214
+ """Wrap an async mutator with loading/error state and a trigger.
1215
+
1216
+ Returns ``(state, mutate)``. Call ``mutate(*args, **kwargs)`` to
1217
+ invoke the mutator; ``state`` reflects loading/error/data and
1218
+ re-renders on each transition. ``mutate`` returns a
1219
+ [`MutationCall`][pythonnative.MutationCall] you can ``await`` for
1220
+ the result, or discard for fire-and-forget.
1221
+
1222
+ Args:
1223
+ mutator: An ``async`` callable that performs the side effect
1224
+ and returns the resulting data.
1225
+
1226
+ Returns:
1227
+ A 2-tuple ``(state, mutate)``.
1228
+
1229
+ Example:
1230
+ ```python
1231
+ @pn.component
1232
+ def NewPostForm():
1233
+ state, save = pn.use_mutation(api.create_post)
1234
+
1235
+ return pn.Column(
1236
+ pn.Button("Save", on_press=lambda: save(post)),
1237
+ state.loading and pn.Text("Saving..."),
1238
+ state.error and pn.Text(str(state.error)),
1239
+ )
1240
+ ```
1241
+ """
1242
+ from .runtime import run_async
1243
+
1244
+ state, set_state = use_state(lambda: MutationState[T]())
1245
+ # The trigger is identity-stable across renders (like ``set_state``)
1246
+ # so it can sit in effect deps or be passed to memoized children;
1247
+ # it always calls the latest ``mutator``.
1248
+ latest = use_ref(mutator)
1249
+ latest.current = mutator
1250
+
1251
+ def _make_mutate() -> Callable[..., MutationCall[T]]:
1252
+ def mutate(*args: Any, **kwargs: Any) -> MutationCall[T]:
1253
+ set_state(lambda s: replace(s, loading=True, error=None))
1254
+ fn = latest.current
1255
+
1256
+ async def _runner() -> T:
1257
+ try:
1258
+ data = await fn(*args, **kwargs)
1259
+ set_state(lambda s: replace(s, data=data, loading=False, error=None))
1260
+ return data
1261
+ except asyncio.CancelledError:
1262
+ set_state(lambda s: replace(s, loading=False))
1263
+ raise
1264
+ except BaseException as exc:
1265
+ failure = exc
1266
+ set_state(lambda s: replace(s, loading=False, error=failure))
1267
+ raise
1268
+
1269
+ future = run_async(_runner())
1270
+ return MutationCall[T](future)
1271
+
1272
+ return mutate
1273
+
1274
+ mutate_ref: Ref[Optional[Callable[..., MutationCall[T]]]] = use_ref(None)
1275
+ if mutate_ref.current is None:
1276
+ mutate_ref.current = _make_mutate()
1277
+ return state, mutate_ref.current
1278
+
1279
+
1280
+ # ======================================================================
1281
+ # External subscriptions
1282
+ # ======================================================================
1283
+
1284
+
1285
+ def use_subscription(subscribe: Callable[[Callable[[], None]], Callable[[], None]], get_snapshot: Callable[[], T]) -> T:
1286
+ """Subscribe to an external store and re-render when its snapshot changes.
1287
+
1288
+ The Pythonic counterpart of React's ``useSyncExternalStore``: the
1289
+ platform-metric hooks below are built on it, and it's the right
1290
+ primitive for app-level stores that live outside the component
1291
+ tree.
1292
+
1293
+ Args:
1294
+ subscribe: ``subscribe(on_change) -> unsubscribe``. Called once
1295
+ on mount; ``on_change`` must be invoked whenever the store
1296
+ changes.
1297
+ get_snapshot: Zero-arg callable returning the current value.
1298
+ Re-read on every render.
1299
+
1300
+ Returns:
1301
+ The current snapshot.
1302
+
1303
+ Raises:
1304
+ RuntimeError: If called outside a ``@component`` function.
1305
+ """
1306
+ from .equality import equal
1307
+
1308
+ _require_hook_state("use_subscription")
1309
+ _, set_tick = use_state(0)
1310
+ snapshot = get_snapshot()
1311
+ observed = use_ref(snapshot)
1312
+ getter = use_ref(get_snapshot)
1313
+ observed.current = snapshot
1314
+ getter.current = get_snapshot
1315
+
1316
+ def _subscribe() -> Callable[[], None]:
1317
+ def changed() -> None:
1318
+ current = getter.current()
1319
+ if not equal(observed.current, current):
1320
+ observed.current = current
1321
+ set_tick(lambda n: n + 1)
1322
+
1323
+ remove = subscribe(changed)
1324
+ # Account for a store change between rendering and subscribing.
1325
+ changed()
1326
+ return remove
1327
+
1328
+ use_effect(_subscribe, [subscribe])
1329
+ return snapshot
1330
+
1331
+
1332
+ def use_window_dimensions() -> WindowDimensions:
1333
+ """Return the current viewport size and re-render when it changes.
1334
+
1335
+ Equivalent to React Native's ``useWindowDimensions``. The values
1336
+ are pushed by the screen host whenever the platform reports a new
1337
+ size (initial layout, rotation, multitasking split-view).
1338
+
1339
+ Returns:
1340
+ A [`WindowDimensions`][pythonnative.platform_metrics.WindowDimensions]
1341
+ named tuple with ``width`` and ``height`` floats in layout
1342
+ units (pt on iOS, dp on Android). Both are ``0.0`` until the
1343
+ screen host has run its first layout pass. Being a tuple, it
1344
+ unpacks (``width, height = pn.use_window_dimensions()``) and
1345
+ compares by value.
1346
+
1347
+ Raises:
1348
+ RuntimeError: If called outside a ``@component`` function.
1349
+ """
1350
+ from . import platform_metrics
1351
+
1352
+ return use_subscription(platform_metrics.subscribe, platform_metrics.get_window_dimensions)
1353
+
1354
+
1355
+ def use_safe_area_insets() -> SafeAreaInsets:
1356
+ """Return the current safe-area insets and re-render on change.
1357
+
1358
+ Mirrors ``react-native-safe-area-context``'s ``useSafeAreaInsets``.
1359
+
1360
+ Returns:
1361
+ A [`SafeAreaInsets`][pythonnative.platform_metrics.SafeAreaInsets]
1362
+ named tuple with ``top``, ``left``, ``bottom``, and ``right``
1363
+ floats in layout units (pt on iOS, dp on Android).
1364
+
1365
+ Raises:
1366
+ RuntimeError: If called outside a ``@component`` function.
1367
+ """
1368
+ from . import platform_metrics
1369
+
1370
+ return use_subscription(platform_metrics.subscribe, platform_metrics.get_safe_area_insets)
1371
+
1372
+
1373
+ def use_keyboard_height() -> float:
1374
+ """Return the on-screen keyboard height (or 0) and re-render on change.
1375
+
1376
+ Useful for custom layout that needs to react to keyboard
1377
+ show/hide events. Most apps should use
1378
+ [`KeyboardAvoidingView`][pythonnative.KeyboardAvoidingView] instead
1379
+ of reading this directly.
1380
+
1381
+ Raises:
1382
+ RuntimeError: If called outside a ``@component`` function.
1383
+ """
1384
+ from . import platform_metrics
1385
+
1386
+ return use_subscription(platform_metrics.subscribe, platform_metrics.get_keyboard_height)
1387
+
1388
+
1389
+ def use_color_scheme() -> str:
1390
+ """Return the effective color scheme and re-render when it changes.
1391
+
1392
+ Equivalent to React Native's ``useColorScheme``. The system value
1393
+ is published by the screen host; an app-level override set through
1394
+ [`appearance.set_color_scheme`][pythonnative.appearance.set_color_scheme]
1395
+ takes precedence.
1396
+
1397
+ Returns:
1398
+ ``"light"`` or ``"dark"``.
1399
+
1400
+ Raises:
1401
+ RuntimeError: If called outside a ``@component`` function.
1402
+ """
1403
+ from . import appearance
1404
+
1405
+ return use_subscription(appearance.subscribe, appearance.get_color_scheme)
1406
+
1407
+
1408
+ # ======================================================================
1409
+ # Context
1410
+ # ======================================================================
1411
+
1412
+
1413
+ class Context(Generic[T]):
1414
+ """A value shared with a subtree, created by [`create_context`][pythonnative.create_context].
1415
+
1416
+ Provide a value with [`Provider`][pythonnative.hooks.Context.Provider]
1417
+ and read it with [`use_context`][pythonnative.use_context]. A
1418
+ ``Context`` is itself an element type: ``ctx.Provider(value, ...)``
1419
+ returns an element whose ``type`` is ``ctx``.
1420
+
1421
+ Context is *reactive*: when a Provider's value changes, every
1422
+ component that read the context on its last render re-renders,
1423
+ even if a memoized ancestor skipped its own re-render.
1424
+
1425
+ Attributes:
1426
+ default: The value returned when no Provider ancestor exists.
1427
+ name: Optional label for diagnostics.
1428
+ """
1429
+
1430
+ __slots__ = ("default", "name", "_stack")
1431
+
1432
+ def __init__(self, default: T, name: Optional[str] = None) -> None:
1433
+ self.default = default
1434
+ self.name = name
1435
+ self._stack: List[T] = []
1436
+
1437
+ def Provider(self, value: T, *children: Node, key: Optional[str] = None) -> Element:
1438
+ """Provide ``value`` to every descendant of ``children``.
1439
+
1440
+ A Provider contributes no native view of its own; its children
1441
+ mount directly into the surrounding native parent.
1442
+
1443
+ When ``value`` differs from the previous render (identity, then
1444
+ ``==``), every descendant that read the context re-renders,
1445
+ including descendants of memoized components that skipped.
1446
+
1447
+ Args:
1448
+ value: Value made available to descendants.
1449
+ *children: Subtree(s) under which the provider applies.
1450
+ key: Stable identity for keyed reconciliation.
1451
+
1452
+ Example:
1453
+ ```python
1454
+ Theme = pn.create_context({"primary": "#007AFF"})
1455
+
1456
+ @pn.component
1457
+ def App():
1458
+ return Theme.Provider({"primary": "#FF0000"}, Header(), Body())
1459
+ ```
1460
+ """
1461
+ return Element(self, {"value": value}, children, key=key)
1462
+
1463
+ def current(self) -> T:
1464
+ """Return the innermost provided value, or ``default``."""
1465
+ return self._stack[-1] if self._stack else self.default
1466
+
1467
+ def __repr__(self) -> str:
1468
+ return f"<Context {self.name or id(self):x}>" if self.name is None else f"<Context {self.name}>"
1469
+
1470
+ # Rendering support: the reconciler pushes/pops provided values
1471
+ # while it walks a Provider's subtree.
1472
+
1473
+ def _push(self, value: T) -> None:
1474
+ self._stack.append(value)
1475
+
1476
+ def _pop(self) -> None:
1477
+ self._stack.pop()
1478
+
1479
+
1480
+ def create_context(default: T = None, *, name: Optional[str] = None) -> Context[T]: # type: ignore[assignment,unused-ignore]
1481
+ """Create a new context with an optional default value.
1482
+
1483
+ Args:
1484
+ default: Returned by [`use_context`][pythonnative.use_context]
1485
+ when there is no enclosing Provider.
1486
+ name: Optional label shown in diagnostics.
1487
+
1488
+ Returns:
1489
+ A fresh [`Context`][pythonnative.Context].
1490
+
1491
+ Example:
1492
+ ```python
1493
+ Theme = pn.create_context({"primary": "#007AFF"}, name="Theme")
1494
+ ```
1495
+ """
1496
+ return Context(default, name=name)
1497
+
1498
+
1499
+ def use_context(context: Context[T]) -> T:
1500
+ """Read the current value of ``context`` from the nearest Provider.
1501
+
1502
+ If no enclosing Provider exists, returns the context's default.
1503
+ The component is registered as a subscriber: when the nearest
1504
+ Provider's value changes, the component re-renders even if a
1505
+ memoized ancestor skipped.
1506
+
1507
+ Args:
1508
+ context: The [`Context`][pythonnative.Context] to read from.
1509
+
1510
+ Returns:
1511
+ The current value for ``context``.
1512
+
1513
+ Raises:
1514
+ RuntimeError: If called outside a ``@component`` function.
1515
+ """
1516
+ ctx = _require_hook_state("use_context")
1517
+ ctx.record_hook("use_context")
1518
+ value = context.current()
1519
+ ctx.context_deps[id(context)] = value
1520
+ return value
1521
+
1522
+
1523
+ # ======================================================================
1524
+ # System back button
1525
+ # ======================================================================
1526
+
1527
+
1528
+ def use_back_handler(handler: Callable[[], bool]) -> None:
1529
+ """Intercept the system back action for this screen.
1530
+
1531
+ On Android this handles the hardware back button and predictive
1532
+ back gesture; in the browser preview it handles the Escape key.
1533
+ iOS has no system back button, so the handler never fires there
1534
+ (swipe-back is controlled by the navigation stack instead).
1535
+
1536
+ Handlers registered later run first, so a component mounted on top
1537
+ of existing content (a modal, a confirmation sheet) takes priority
1538
+ over handlers that were already mounted. Return ``True`` to consume
1539
+ the event and stop both remaining handlers and the platform's
1540
+ default behavior (popping the screen); return ``False`` to pass it
1541
+ along.
1542
+
1543
+ The latest ``handler`` closure from the most recent render is
1544
+ always the one invoked; registration order is fixed at mount, so
1545
+ re-renders never change priority.
1546
+
1547
+ Args:
1548
+ handler: Zero-arg callable returning ``True`` if it consumed
1549
+ the back action.
1550
+
1551
+ Raises:
1552
+ RuntimeError: If called outside a ``@component`` function.
1553
+
1554
+ Example:
1555
+ ```python
1556
+ @pn.component
1557
+ def Editor():
1558
+ dirty, set_dirty = pn.use_state(False)
1559
+ pn.use_back_handler(lambda: dirty) # block back while dirty
1560
+ ...
1561
+ ```
1562
+ """
1563
+ ctx = _require_hook_state("use_back_handler")
1564
+
1565
+ latest: Ref[Callable[[], bool]] = use_ref(handler)
1566
+ latest.current = handler
1567
+
1568
+ def _register() -> Optional[Callable[[], None]]:
1569
+ owner = ctx.owner
1570
+ if owner is None:
1571
+ return None
1572
+
1573
+ def _trampoline() -> bool:
1574
+ fn = latest.current
1575
+ if fn is None:
1576
+ return False
1577
+ try:
1578
+ return bool(fn())
1579
+ except Exception as exc:
1580
+ if not diagnostics.report_error(exc, phase="back handler"):
1581
+ raise
1582
+ return True
1583
+
1584
+ return owner.register_back_handler(_trampoline)
1585
+
1586
+ use_effect(_register, [])
1587
+
1588
+
1589
+ __all__ = [
1590
+ "Context",
1591
+ "HookState",
1592
+ "MutationCall",
1593
+ "MutationState",
1594
+ "QueryResult",
1595
+ "Ref",
1596
+ "RenderOwner",
1597
+ "create_context",
1598
+ "current_hook_state",
1599
+ "install_hook_state",
1600
+ "restore_hook_state",
1601
+ "use_back_handler",
1602
+ "use_callback",
1603
+ "use_color_scheme",
1604
+ "use_context",
1605
+ "use_deferred_value",
1606
+ "use_effect",
1607
+ "use_imperative_handle",
1608
+ "use_keyboard_height",
1609
+ "use_layout_effect",
1610
+ "use_memo",
1611
+ "use_mutation",
1612
+ "use_query",
1613
+ "use_reducer",
1614
+ "use_ref",
1615
+ "use_resource",
1616
+ "use_safe_area_insets",
1617
+ "use_state",
1618
+ "use_subscription",
1619
+ "use_transition",
1620
+ "use_window_dimensions",
1621
+ ]