pythonnative 0.40.0__cp314-cp314-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.cp314-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
@@ -0,0 +1,1165 @@
1
+ """The [`Reconciler`][pythonnative.reconciler.core.Reconciler]: element trees in, native mutations out.
2
+
3
+ One reconciler owns one mounted tree (a screen, a list row, a test
4
+ render). Each pass runs the same phases in order:
5
+
6
+ 1. **Render**: component bodies run for the subtrees that need it,
7
+ producing [`Element`][pythonnative.Element] descriptions. Hook state
8
+ is installed for the duration of each body.
9
+ 2. **Diff and stage**: elements are compared with the mounted
10
+ [`VNode`][pythonnative.reconciler.VNode] in their slot. Creates,
11
+ prop updates, attach/detach, and destroys are *staged* into an op
12
+ list instead of being applied immediately.
13
+ 3. **Commit**: the staged ops go to the backend in one batch, refs are
14
+ populated, the layout pass runs (once a viewport is known), then
15
+ effects flush: ``use_layout_effect`` first, ``use_effect`` after.
16
+
17
+ A render requested while a pass is in flight (an effect setting
18
+ state, a boundary reset) is queued and drained before the call
19
+ returns, bounded so runaway update loops surface as an error rather
20
+ than a hang. Hosts never observe a half-committed tree.
21
+
22
+ The class is assembled from mixins to keep each concern readable:
23
+ this module holds the render/diff/commit pipeline,
24
+ ``boundaries`` handles
25
+ ``ErrorBoundary`` and ``Suspense``, and
26
+ ``layout_pass`` runs flexbox.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import os
32
+ from contextlib import contextmanager
33
+ from functools import partial
34
+ from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple
35
+
36
+ from .. import diagnostics
37
+ from ..component import Component
38
+ from ..element import ERROR_BOUNDARY, FRAGMENT, SUSPENSE, Element
39
+ from ..events import extract_events, get_event_registry
40
+ from ..hooks import Context, HookState, install_hook_state, restore_hook_state
41
+ from ..mutations import CreateOp, DestroyOp, InsertOp, Mutation, UpdateOp
42
+ from ..profiling import profiled
43
+ from ..runtime import _scope
44
+ from ..scheduler import TransitionQueue, schedule_trigger
45
+ from ..suspense import Suspend
46
+ from .boundaries import BoundaryMixin, HydrationMap
47
+ from .children import plan_child_moves
48
+ from .layout_pass import LayoutMixin, affects_layout
49
+ from .vnode import VNode, next_tag, normalize_children, shallow_equal_props
50
+
51
+ __all__ = ["Reconciler"]
52
+
53
+ # Props the reconciler consumes itself and never forwards to the
54
+ # native handler. ``ref`` is populated with the native view after
55
+ # commit, mirroring React's ``ref`` semantics.
56
+ _RECONCILER_OWNED_PROPS = frozenset({"ref"})
57
+
58
+ # Hard ceiling on renders drained inside one host call, so a component
59
+ # that sets state unconditionally in an effect surfaces as an error
60
+ # instead of hanging the UI thread.
61
+ _MAX_RENDERS_PER_PASS = 25
62
+
63
+ _MISSING = object()
64
+
65
+
66
+ class Reconciler(BoundaryMixin, LayoutMixin):
67
+ """Owns one mounted tree and translates element diffs into native mutations.
68
+
69
+ Args:
70
+ backend: An object implementing the registry protocol
71
+ (``apply_mutations``, ``resolve_view``, ``measure_intrinsic``,
72
+ ``command``). PythonNative ships Android, iOS, and browser
73
+ registries; tests use
74
+ [`FakeBackend`][pythonnative.testing.FakeBackend].
75
+
76
+ Attributes:
77
+ backend: The backend passed at construction.
78
+ on_render_requested: Optional callback hosts set to be told a
79
+ re-render is wanted (to hop onto the UI thread or guard
80
+ with a red box). When ``None`` the reconciler re-renders
81
+ inline. Whatever the callback does, it should end up
82
+ calling [`flush_dirty`][pythonnative.reconciler.core.Reconciler.flush_dirty].
83
+ on_back_registered: Optional callback fired when the first
84
+ [`use_back_handler`][pythonnative.use_back_handler]
85
+ registers, so hosts can enable hardware back interception.
86
+ transitions: Deferred-render queue behind
87
+ [`use_transition`][pythonnative.use_transition]; owned per
88
+ reconciler so screens never delay each other.
89
+ """
90
+
91
+ def __init__(self, backend: Any) -> None:
92
+ self.backend = backend
93
+ self.root: Optional[VNode] = None
94
+ self._tag_nodes: Dict[int, VNode] = {}
95
+ self._effect_states: Dict[int, HookState] = {}
96
+ self._native_children: Dict[int, List[int]] = {}
97
+ if hasattr(backend, "on_layout"):
98
+ backend.on_layout = self._accept_native_layout
99
+ self.on_render_requested: Optional[Callable[[], None]] = None
100
+ self.on_back_registered: Optional[Callable[[], None]] = None
101
+ self.transitions = TransitionQueue()
102
+
103
+ self._events = get_event_registry()
104
+ self._ops: List[Mutation] = []
105
+ self._created: List[VNode] = []
106
+ self._rendering = False
107
+ self._render_queued = False
108
+ # Component nodes whose own state changed since the last flush,
109
+ # keyed by ``id`` to dedupe while keeping a strong reference.
110
+ self._dirty_nodes: Dict[int, VNode] = {}
111
+ # Error boundaries whose ``reset`` was called.
112
+ self._dirty_boundaries: Dict[int, VNode] = {}
113
+ # Suspense nodes whose awaited work completed.
114
+ self._dirty_suspense: Dict[int, VNode] = {}
115
+ # Hydration map active while a Suspense boundary retries its
116
+ # content, and hook states salvaged while a Suspend unwinds.
117
+ self._hydration: Optional[HydrationMap] = None
118
+ self._suspense_salvage: Optional[HydrationMap] = None
119
+ # Tags destroyed during the current pass; tags are never
120
+ # reused, so stale entries can never alias a live view.
121
+ self._destroyed_tags: Set[int] = set()
122
+ # ``use_back_handler`` registrations, oldest first.
123
+ self._back_handlers: List[Callable[[], bool]] = []
124
+ self._init_layout_state()
125
+
126
+ # ------------------------------------------------------------------
127
+ # Public API
128
+ # ------------------------------------------------------------------
129
+
130
+ @property
131
+ def root_tag(self) -> Optional[int]:
132
+ """Tag of the root native view, or ``None`` when nothing is mounted."""
133
+ return self.root.tag if self.root is not None else None
134
+
135
+ def root_view(self) -> Any:
136
+ """The root native view object, or ``None`` before mount."""
137
+ return self.root.native_view if self.root is not None else None
138
+
139
+ def mount(self, element: Element) -> Any:
140
+ """Build native views for ``element`` and return the root native view.
141
+
142
+ Any previously mounted tree is unmounted first.
143
+ """
144
+ if self.root is not None:
145
+ self.unmount()
146
+ self._log(f"mount: {element!r}")
147
+ with self._pass():
148
+ self._destroyed_tags.clear()
149
+ try:
150
+ self.root = self._create_tree(element)
151
+ except Suspend as signal:
152
+ self._discard_salvage()
153
+ raise self._missing_suspense_error(signal) from None
154
+ self._drain_dirty()
155
+ self._commit()
156
+ self._warn_on_multiple_roots()
157
+ return self.root_view()
158
+
159
+ def reconcile(self, element: Element) -> Any:
160
+ """Diff ``element`` against the mounted tree and patch native views.
161
+
162
+ Pending state updates are drained as part of the pass, so hosts
163
+ only call this when the root element itself changed (new props
164
+ from outside the tree).
165
+ """
166
+ if self.root is None:
167
+ return self.mount(element)
168
+ with self._pass():
169
+ # A full pass covers every dirty component; reactive context
170
+ # may re-add entries during it, which the drain picks up.
171
+ self._dirty_nodes.clear()
172
+ self._destroyed_tags.clear()
173
+ try:
174
+ self.root = self._reconcile_node(self.root, element)
175
+ except Suspend as signal:
176
+ self._discard_salvage()
177
+ raise self._missing_suspense_error(signal) from None
178
+ self._drain_dirty()
179
+ self._commit()
180
+ self._warn_on_multiple_roots()
181
+ return self.root_view()
182
+
183
+ @profiled("render")
184
+ def flush_dirty(self) -> Any:
185
+ """Re-render only the components whose state changed, then commit.
186
+
187
+ This is the hot path for state-driven updates: each dirty
188
+ component re-runs its own body and reconciles its subtree in
189
+ place; the batch commits as one native transaction. Returns the
190
+ (possibly replaced) root native view.
191
+ """
192
+ if self.root is None:
193
+ return None
194
+ if self._rendering:
195
+ self._render_queued = True
196
+ return self.root_view()
197
+ if not self._has_dirty_work():
198
+ return self.root_view()
199
+ with self._pass():
200
+ self._destroyed_tags.clear()
201
+ self._drain_dirty()
202
+ self._commit()
203
+ return self.root_view()
204
+
205
+ def unmount(self) -> None:
206
+ """Tear down the mounted tree, running effect cleanups and destroying native views."""
207
+ root = self.root
208
+ if root is None:
209
+ return
210
+ self._destroy_tree(root)
211
+ self.root = None
212
+ self._dirty_nodes.clear()
213
+ self._dirty_boundaries.clear()
214
+ self._dirty_suspense.clear()
215
+ self._back_handlers.clear()
216
+ self.transitions.clear()
217
+ self._flush_ops()
218
+
219
+ def dispatch_command(self, tag: Optional[int], name: str, args: Optional[Dict[str, Any]] = None) -> Any:
220
+ """Run an imperative command against the view registered under ``tag``."""
221
+ if tag is None:
222
+ return None
223
+ return self.backend.command(tag, name, args or {})
224
+
225
+ def dispatch_back_press(self) -> bool:
226
+ """Offer the system back action to registered handlers, newest first.
227
+
228
+ Returns ``True`` if a handler consumed the event (the platform
229
+ should not run its default behavior).
230
+ """
231
+ for handler in reversed(list(self._back_handlers)):
232
+ if handler():
233
+ return True
234
+ return False
235
+
236
+ def walk(self) -> Iterator[VNode]:
237
+ """Yield every mounted node in depth-first, document order."""
238
+ stack = [self.root] if self.root is not None else []
239
+ while stack:
240
+ node = stack.pop()
241
+ yield node
242
+ stack.extend(reversed(node.children))
243
+
244
+ # ------------------------------------------------------------------
245
+ # RenderOwner protocol (called by hooks)
246
+ # ------------------------------------------------------------------
247
+
248
+ def mark_dirty(self, vnode: VNode) -> None:
249
+ """Queue ``vnode`` (a component) for a local re-render on the next flush."""
250
+ if vnode is None or vnode.hook_state is None or not vnode.mounted:
251
+ return
252
+ self._dirty_nodes[id(vnode)] = vnode
253
+
254
+ def request_render(self) -> None:
255
+ """Ask for a flush: via the host callback when set, inline otherwise."""
256
+ if self._rendering:
257
+ self._render_queued = True
258
+ return
259
+ if self.on_render_requested is not None:
260
+ self.on_render_requested()
261
+ else:
262
+ self.flush_dirty()
263
+
264
+ def register_back_handler(self, handler: Callable[[], bool]) -> Callable[[], None]:
265
+ """Register a back-press handler; returns an unregister callable."""
266
+ self._back_handlers.append(handler)
267
+ if len(self._back_handlers) == 1 and self.on_back_registered is not None:
268
+ self.on_back_registered()
269
+
270
+ def unregister() -> None:
271
+ try:
272
+ self._back_handlers.remove(handler)
273
+ except ValueError:
274
+ pass
275
+
276
+ return unregister
277
+
278
+ # ------------------------------------------------------------------
279
+ # Pass bookkeeping
280
+ # ------------------------------------------------------------------
281
+
282
+ @contextmanager
283
+ def _pass(self) -> Iterator[None]:
284
+ """Mark a pass in flight and drain renders requested during it."""
285
+ if self._rendering:
286
+ self._render_queued = True
287
+ yield
288
+ return
289
+ self._rendering = True
290
+ try:
291
+ yield
292
+ passes = 0
293
+ while self._render_queued and self.root is not None:
294
+ self._render_queued = False
295
+ if not self._has_dirty_work():
296
+ continue
297
+ passes += 1
298
+ if passes > _MAX_RENDERS_PER_PASS:
299
+ raise RuntimeError(
300
+ "Too many re-renders: a component keeps requesting updates while committing "
301
+ "(for example an effect that sets state unconditionally)."
302
+ )
303
+ self._destroyed_tags.clear()
304
+ self._drain_dirty()
305
+ self._commit()
306
+ finally:
307
+ self._rendering = False
308
+ self._render_queued = False
309
+
310
+ def _has_dirty_work(self) -> bool:
311
+ return bool(self._dirty_nodes or self._dirty_boundaries or self._dirty_suspense)
312
+
313
+ def _drain_dirty(self) -> None:
314
+ """Process dirty components and boundary retries until none remain."""
315
+ guard = 0
316
+ while self._has_dirty_work():
317
+ guard += 1
318
+ if guard > 100:
319
+ diagnostics.warn(
320
+ "Update loop did not settle after 100 iterations; a component is "
321
+ "likely setting state unconditionally during render or effects."
322
+ )
323
+ self._dirty_nodes.clear()
324
+ self._dirty_boundaries.clear()
325
+ self._dirty_suspense.clear()
326
+ return
327
+
328
+ boundaries = list(self._dirty_boundaries.values())
329
+ self._dirty_boundaries.clear()
330
+ for boundary in boundaries:
331
+ if boundary.mounted:
332
+ with self._providers_above(boundary):
333
+ self._local_update(
334
+ boundary, partial(self._reconcile_error_boundary, boundary, boundary.element)
335
+ )
336
+
337
+ suspended = list(self._dirty_suspense.values())
338
+ self._dirty_suspense.clear()
339
+ for node in suspended:
340
+ if node.mounted and node.suspense_showing_fallback:
341
+ with self._providers_above(node):
342
+ try:
343
+ self._local_update(node, partial(self._attempt_suspense_content, node))
344
+ except Exception as exc:
345
+ self._route_error(node, exc)
346
+
347
+ pending = sorted(self._dirty_nodes.values(), key=VNode.depth)
348
+ self._dirty_nodes.clear()
349
+ for vnode in pending:
350
+ hs = vnode.hook_state
351
+ if not vnode.mounted or hs is None or not hs._dirty:
352
+ continue # already covered by a dirty ancestor's pass
353
+ try:
354
+ self._update_component(vnode)
355
+ except Suspend as signal:
356
+ self._route_suspend(vnode, signal)
357
+ except Exception as exc:
358
+ self._route_error(vnode, exc)
359
+
360
+ def _commit(self) -> None:
361
+ """Apply the staged transaction and run the post-commit phases."""
362
+ self._flush_ops()
363
+ self._run_layout()
364
+ self._flush_ops()
365
+ self._dispatch_layout_events()
366
+ self._flush_layout_effects()
367
+ self._flush_ops()
368
+ self._flush_passive_effects()
369
+ self._flush_ops()
370
+
371
+ @profiled("commit")
372
+ def _flush_ops(self) -> None:
373
+ """Send pending ops to the backend and resolve created views."""
374
+ ops = self._ops
375
+ created = self._created
376
+ if ops:
377
+ self._ops = []
378
+ self._created = []
379
+ self.backend.apply_mutations(ops)
380
+ elif created:
381
+ self._created = []
382
+ for node in created:
383
+ if not node.mounted or node.tag is None:
384
+ continue
385
+ node.native_view = self.backend.resolve_view(node.tag)
386
+ self._attach_ref(node.element, node.native_view, node.tag)
387
+ ancestor = node.parent
388
+ scope = None
389
+ while ancestor is not None:
390
+ if scope is None and ancestor.hook_state is not None:
391
+ scope = ancestor.hook_state.task_scope
392
+ if not ancestor.is_native:
393
+ self._refresh_identity(ancestor)
394
+ ancestor = ancestor.parent
395
+ self._events.set_scope(node.tag, scope)
396
+
397
+ def _flush_layout_effects(self) -> None:
398
+ for state in sorted(
399
+ self._effect_states.values(), key=lambda hs: hs.vnode.depth() if hs.vnode else 0, reverse=True
400
+ ):
401
+ if state.vnode is not None and state.vnode.mounted:
402
+ state.flush_layout_effects()
403
+
404
+ def _flush_passive_effects(self) -> None:
405
+ states, self._effect_states = self._effect_states, {}
406
+ for state in sorted(states.values(), key=lambda hs: hs.vnode.depth() if hs.vnode else 0, reverse=True):
407
+ if state.vnode is not None and state.vnode.mounted:
408
+ state.flush_pending_effects()
409
+
410
+ # ------------------------------------------------------------------
411
+ # Tree creation
412
+ # ------------------------------------------------------------------
413
+
414
+ def _create_tree(self, element: Element) -> VNode:
415
+ type_obj = element.type
416
+ if isinstance(type_obj, str):
417
+ return self._create_native(element)
418
+ if isinstance(type_obj, Component):
419
+ return self._create_component(element)
420
+ if isinstance(type_obj, Context):
421
+ return self._create_provider(element)
422
+ if type_obj is ERROR_BOUNDARY:
423
+ return self._create_error_boundary(element)
424
+ if type_obj is SUSPENSE:
425
+ return self._create_suspense(element)
426
+ if type_obj is FRAGMENT:
427
+ return self._create_wrapper(element, "Fragment")
428
+ raise TypeError(
429
+ f"Unsupported element type {type_obj!r}. Element types are native view names, "
430
+ "@component functions, contexts (providers), or the structural types."
431
+ )
432
+
433
+ def _create_native(self, element: Element) -> VNode:
434
+ tag = next_tag()
435
+ clean_props, events = self._split_props(element.props)
436
+ node = VNode(element, [], tag=tag)
437
+ self._tag_nodes[tag] = node
438
+ node.clean_props = clean_props
439
+ if events:
440
+ self._events.set_events(tag, events)
441
+ self._ops.append(CreateOp(tag, element.type, clean_props))
442
+ self._created.append(node)
443
+
444
+ index = 0
445
+ try:
446
+ for child_el in normalize_children(element.children, owner=element.type):
447
+ child = self._create_tree(child_el)
448
+ child.parent = node
449
+ node.children.append(child)
450
+ for root in self._native_roots(child):
451
+ if root.tag is not None:
452
+ self._ops.append(InsertOp(tag, root.tag, index))
453
+ index += 1
454
+ except Suspend:
455
+ # Don't leak the container (and already-built siblings) when
456
+ # a child suspends; hook states are salvaged for the retry.
457
+ if self._suspense_salvage is None:
458
+ self._suspense_salvage = {}
459
+ self._destroy_tree(node, salvage=self._suspense_salvage)
460
+ raise
461
+ except Exception:
462
+ self._destroy_tree(node)
463
+ raise
464
+ self._native_children[tag] = self._flattened_child_tags(node)
465
+ return node
466
+
467
+ def _create_component(self, element: Element) -> VNode:
468
+ # A retrying Suspense boundary may have preserved this
469
+ # component's hook state from the attempt that suspended;
470
+ # reclaiming it keeps cached resources warm.
471
+ hook_state = self._take_hydrated_hook_state(element) or HookState()
472
+ rendered = self._render_component_body(hook_state, element)
473
+ try:
474
+ children = self._create_child_list(rendered)
475
+ except Suspend:
476
+ # The body rendered fine but a descendant suspended: salvage
477
+ # this component's own state too, so the retry re-adopts it.
478
+ if self._suspense_salvage is None:
479
+ self._suspense_salvage = {}
480
+ self._suspense_salvage.setdefault((id(element.type), element.key), []).append(hook_state)
481
+ raise
482
+ node = VNode(element, children)
483
+ for child in children:
484
+ child.parent = node
485
+ node.hook_state = hook_state
486
+ node.rendered = rendered
487
+ self._refresh_identity(node)
488
+ hook_state.vnode = node
489
+ hook_state.owner = self
490
+ return node
491
+
492
+ def _create_provider(self, element: Element) -> VNode:
493
+ context: Context = element.type
494
+ context._push(element.props.get("value"))
495
+ try:
496
+ children = self._create_child_list(
497
+ normalize_children(element.children, owner=self._provider_label(context))
498
+ )
499
+ finally:
500
+ context._pop()
501
+ node = VNode(element, children)
502
+ for child in children:
503
+ child.parent = node
504
+ self._refresh_identity(node)
505
+ return node
506
+
507
+ def _create_wrapper(self, element: Element, owner: str) -> VNode:
508
+ children = self._create_child_list(normalize_children(element.children, owner=owner))
509
+ node = VNode(element, children)
510
+ for child in children:
511
+ child.parent = node
512
+ self._refresh_identity(node)
513
+ return node
514
+
515
+ def _create_child_list(self, elements: List[Element]) -> List[VNode]:
516
+ """Create nodes for ``elements``, cleaning up on mid-list failure.
517
+
518
+ When the failure is a ``Suspend``, already-built siblings are
519
+ torn down with their hook states *salvaged*, so the boundary's
520
+ retry re-mounts them with their caches intact.
521
+ """
522
+ nodes: List[VNode] = []
523
+ try:
524
+ for el in elements:
525
+ nodes.append(self._create_tree(el))
526
+ except Suspend:
527
+ if self._suspense_salvage is None:
528
+ self._suspense_salvage = {}
529
+ for node in nodes:
530
+ self._destroy_tree(node, salvage=self._suspense_salvage)
531
+ raise
532
+ except Exception:
533
+ for node in nodes:
534
+ self._destroy_tree(node)
535
+ raise
536
+ return nodes
537
+
538
+ # ------------------------------------------------------------------
539
+ # Component bodies
540
+ # ------------------------------------------------------------------
541
+
542
+ @profiled("component")
543
+ def _render_component_body(self, hook_state: HookState, element: Element) -> List[Element]:
544
+ """Render synchronously or suspend on a real, component-owned task."""
545
+ from ..profiling import count
546
+
547
+ count("components.rendered")
548
+ self._effect_states[id(hook_state)] = hook_state
549
+ component: Component = element.type
550
+ label = component.display_name
551
+ hook_state.owner = self
552
+ if component.is_async:
553
+ previous = hook_state._async_task
554
+ inputs = (element.props, element.children)
555
+ from ..equality import equal
556
+
557
+ if previous is not None and not equal(hook_state._async_inputs, inputs):
558
+ previous.cancel()
559
+ previous = hook_state._async_task = None
560
+ if previous is None:
561
+
562
+ async def render_body() -> Any:
563
+ hook_state.begin_render(label)
564
+ token = install_hook_state(hook_state)
565
+ try:
566
+ result = await component.render(element)
567
+ hook_state.finish_render()
568
+ return result
569
+ except BaseException:
570
+ hook_state.abort_render()
571
+ raise
572
+ finally:
573
+ restore_hook_state(token)
574
+
575
+ previous = hook_state.task_scope.create_task(render_body())
576
+ hook_state._async_task = previous
577
+ hook_state._async_inputs = inputs
578
+ if not previous.done():
579
+ signal = Suspend(previous, hook_state=hook_state, label=label)
580
+ signal.key = (id(element.type), element.key)
581
+ raise signal
582
+ hook_state._async_task = None
583
+ hook_state._dirty = False
584
+ return normalize_children(previous.result(), owner=label)
585
+
586
+ hook_state.begin_render(label)
587
+ token = install_hook_state(hook_state)
588
+ scope_token = _scope.set(hook_state.task_scope)
589
+ try:
590
+ rendered = component.render(element)
591
+ hook_state.finish_render()
592
+ except Suspend as signal:
593
+ hook_state.abort_render()
594
+ if signal.hook_state is None:
595
+ signal.hook_state = hook_state
596
+ if signal.key is None:
597
+ signal.key = (id(element.type), element.key)
598
+ if not signal.label:
599
+ signal.label = label
600
+ raise
601
+ finally:
602
+ _scope.reset(scope_token)
603
+ restore_hook_state(token)
604
+ hook_state._dirty = False
605
+ return normalize_children(rendered, owner=label)
606
+
607
+ def _register_component_retry(self, vnode: VNode, signal: Suspend) -> None:
608
+ """Re-render ``vnode`` once the work it suspended on completes.
609
+
610
+ Update-time suspensions keep the previous content on screen (no
611
+ fallback flash) and re-run the body when the awaited work is
612
+ done, matching transition semantics.
613
+ """
614
+ hook_state = vnode.hook_state
615
+
616
+ def _on_done(_waitable: Any) -> None:
617
+ if not vnode.mounted or vnode.hook_state is not hook_state or hook_state is None:
618
+ return
619
+ hook_state._dirty = True
620
+ self.mark_dirty(vnode)
621
+ schedule_trigger(self.request_render)
622
+
623
+ signal.waitable.add_done_callback(_on_done)
624
+
625
+ def _update_component(self, vnode: VNode) -> None:
626
+ """Re-run one component's body and reconcile its subtree in place.
627
+
628
+ A local update starts in the middle of the tree, so the provider
629
+ chain above the component is re-established before the body
630
+ runs. When the body itself suspends, the previous subtree stays
631
+ on screen and the component re-renders once the awaited work
632
+ completes; suspensions from descendant mounts propagate to the
633
+ caller, which routes them to the nearest Suspense boundary.
634
+ """
635
+ hook_state = vnode.hook_state
636
+ if hook_state is None or not vnode.is_component:
637
+ return
638
+
639
+ def work() -> None:
640
+ try:
641
+ rendered = self._render_component_body(hook_state, vnode.element)
642
+ except Suspend as signal:
643
+ if signal.hook_state is hook_state:
644
+ self._register_component_retry(vnode, signal)
645
+ return
646
+ raise
647
+ children = self._reconcile_child_list(vnode.children, rendered)
648
+ for child in children:
649
+ child.parent = vnode
650
+ vnode.children = children
651
+ vnode.rendered = rendered
652
+ self._refresh_identity(vnode)
653
+ hook_state.vnode = vnode
654
+ hook_state.owner = self
655
+
656
+ with self._providers_above(vnode):
657
+ self._local_update(vnode, work)
658
+
659
+ def _local_update(self, node: VNode, work: Callable[[], Any]) -> None:
660
+ """Run ``work`` on ``node`` and repair the surrounding native structure.
661
+
662
+ Captures the nearest native container's child tags before the
663
+ work, then refreshes transparent ancestors' delegated identity
664
+ and re-syncs that container's children afterwards, so only the
665
+ moves the update actually caused reach the native side.
666
+ """
667
+ before_roots = [root.tag for root in self._native_roots(node)]
668
+ container = self._nearest_native_ancestor(node)
669
+ before = self._native_children.get(container.tag, []) if container is not None else []
670
+ work()
671
+ current = node.parent
672
+ while current is not None and current is not container:
673
+ self._refresh_identity(current)
674
+ current = current.parent
675
+ after_roots = [root.tag for root in self._native_roots(node)]
676
+ if before_roots == after_roots:
677
+ return
678
+ if container is not None and container.tag is not None:
679
+ if self._sync_native_children(container.tag, before, self._flattened_child_roots(container)):
680
+ self._mark_layout_dirty(container)
681
+
682
+ def _route_error(self, vnode: VNode, exc: BaseException) -> None:
683
+ """Route a local render failure to the nearest ``ErrorBoundary`` ancestor.
684
+
685
+ Without an enclosing boundary the exception propagates, exactly
686
+ as it would during a full render.
687
+ """
688
+ node = vnode.parent
689
+ while node is not None:
690
+ if node.is_error_boundary:
691
+ with self._providers_above(node):
692
+ self._local_update(node, partial(self._activate_boundary, node, exc))
693
+ return
694
+ node = node.parent
695
+ raise exc
696
+
697
+ def _route_suspend(self, vnode: VNode, signal: Suspend) -> None:
698
+ """Route a suspension from a local update to the nearest Suspense ancestor.
699
+
700
+ The child reconcile that suspended may have left the boundary's
701
+ content partially updated, so the whole content is torn down and
702
+ rebuilt through the fallback-and-retry path (hook states are
703
+ preserved for the retry).
704
+ """
705
+ node = vnode.parent
706
+ while node is not None:
707
+ if node.is_suspense:
708
+ with self._providers_above(node):
709
+
710
+ def work(n: VNode = node) -> None:
711
+ self._teardown_suspense_content(n)
712
+ self._suspend_boundary(n, signal)
713
+
714
+ self._local_update(node, work)
715
+ return
716
+ node = node.parent
717
+ self._discard_salvage()
718
+ raise self._missing_suspense_error(signal) from None
719
+
720
+ @contextmanager
721
+ def _providers_above(self, vnode: VNode) -> Iterator[None]:
722
+ """Push the provided values of every provider above ``vnode``, outermost first."""
723
+ chain: List[Tuple[Context, Any]] = []
724
+ node = vnode.parent
725
+ while node is not None:
726
+ if node.is_provider:
727
+ chain.append((node.element.type, node.element.props.get("value")))
728
+ node = node.parent
729
+ chain.reverse()
730
+ for context, value in chain:
731
+ context._push(value)
732
+ try:
733
+ yield
734
+ finally:
735
+ for context, _value in reversed(chain):
736
+ context._pop()
737
+
738
+ # ------------------------------------------------------------------
739
+ # Reconciliation
740
+ # ------------------------------------------------------------------
741
+
742
+ def _reconcile_node(self, old: VNode, new_el: Element) -> VNode:
743
+ if not self._same_type(old.element, new_el):
744
+ new_node = self._create_tree(new_el)
745
+ self._destroy_tree(old)
746
+ return new_node
747
+ if old.is_native:
748
+ from ..equality import equal
749
+ from ..sdk.schema import COMPONENTS
750
+
751
+ schema = COMPONENTS.get(new_el.type)
752
+ if schema is not None and any(
753
+ field.get("native", {}).get("recreate") and not equal(old.element.props.get(key), new_el.props.get(key))
754
+ for key, field in schema.props.items()
755
+ ):
756
+ new_node = self._create_tree(new_el)
757
+ self._destroy_tree(old)
758
+ return new_node
759
+ return self._reconcile_native(old, new_el)
760
+ if old.is_component:
761
+ return self._reconcile_component(old, new_el)
762
+ if old.is_provider:
763
+ return self._reconcile_provider(old, new_el)
764
+ if old.is_error_boundary:
765
+ return self._reconcile_error_boundary(old, new_el)
766
+ if old.is_suspense:
767
+ return self._reconcile_suspense(old, new_el)
768
+ return self._reconcile_wrapper(old, new_el, "Fragment")
769
+
770
+ def _reconcile_native(self, old: VNode, new_el: Element) -> VNode:
771
+ new_clean, events = self._split_props(new_el.props)
772
+ if old.tag is not None:
773
+ self._events.set_events(old.tag, events)
774
+ changed = self._diff_props(old.clean_props, new_clean)
775
+ if changed:
776
+ if old.tag is not None:
777
+ self._ops.append(UpdateOp(old.tag, changed))
778
+ old.measure_cache = None
779
+ if affects_layout(old.element.type, changed):
780
+ self._mark_layout_dirty(old)
781
+ old.clean_props = new_clean
782
+
783
+ old_ref = old.element.props.get("ref")
784
+ new_ref = new_el.props.get("ref")
785
+ if old_ref is not new_ref:
786
+ self._clear_ref(old_ref)
787
+ self._attach_ref(new_el, old.native_view, old.tag)
788
+
789
+ before = self._flattened_child_tags(old)
790
+ children = self._reconcile_child_list(old.children, normalize_children(new_el.children, owner=new_el.type))
791
+ old.children = children
792
+ for child in children:
793
+ child.parent = old
794
+ old.element = new_el
795
+ if old.tag is not None and self._sync_native_children(old.tag, before, self._flattened_child_roots(old)):
796
+ self._mark_layout_dirty(old)
797
+ return old
798
+
799
+ def _reconcile_component(self, old: VNode, new_el: Element) -> VNode:
800
+ if self._can_skip_memoized(old, new_el):
801
+ old.element = new_el
802
+ return old
803
+ hook_state = old.hook_state or HookState()
804
+ rendered = self._render_component_body(hook_state, new_el)
805
+ children = self._reconcile_child_list(old.children, rendered)
806
+ old.children = children
807
+ for child in children:
808
+ child.parent = old
809
+ old.element = new_el
810
+ old.hook_state = hook_state
811
+ old.rendered = rendered
812
+ self._refresh_identity(old)
813
+ hook_state.vnode = old
814
+ hook_state.owner = self
815
+ return old
816
+
817
+ def _reconcile_provider(self, old: VNode, new_el: Element) -> VNode:
818
+ context: Context = new_el.type
819
+ old_value = old.element.props.get("value", _MISSING)
820
+ new_value = new_el.props.get("value")
821
+ if self._value_changed(old_value, new_value):
822
+ self._mark_context_consumers(old, context)
823
+ context._push(new_value)
824
+ try:
825
+ children = self._reconcile_child_list(
826
+ old.children, normalize_children(new_el.children, owner=self._provider_label(context))
827
+ )
828
+ finally:
829
+ context._pop()
830
+ old.children = children
831
+ for child in children:
832
+ child.parent = old
833
+ old.element = new_el
834
+ self._refresh_identity(old)
835
+ return old
836
+
837
+ def _reconcile_wrapper(self, old: VNode, new_el: Element, owner: str) -> VNode:
838
+ children = self._reconcile_child_list(old.children, normalize_children(new_el.children, owner=owner))
839
+ old.children = children
840
+ for child in children:
841
+ child.parent = old
842
+ old.element = new_el
843
+ self._refresh_identity(old)
844
+ return old
845
+
846
+ def _mark_context_consumers(self, provider: VNode, context: Context) -> None:
847
+ """Mark every descendant that read ``context`` for re-render.
848
+
849
+ Descent is pruned at nested providers of the same context, since
850
+ their subtrees read the inner (unchanged) value.
851
+ """
852
+ target = id(context)
853
+
854
+ def walk(node: VNode) -> None:
855
+ for child in node.children:
856
+ if child.is_provider and child.element.type is context:
857
+ continue
858
+ hs = child.hook_state
859
+ if hs is not None and target in hs.context_deps:
860
+ hs._dirty = True
861
+ self._dirty_nodes[id(child)] = child
862
+ walk(child)
863
+
864
+ walk(provider)
865
+
866
+ @staticmethod
867
+ def _can_skip_memoized(old: VNode, new_el: Element) -> bool:
868
+ """Whether a memoized component can reuse its last render.
869
+
870
+ Requires the component to be memoized, to have rendered before,
871
+ to have no pending state change (``hook_state._dirty``), and
872
+ props that compare equal under its comparator (shallow equality
873
+ by default), children included.
874
+ """
875
+ component: Component = new_el.type
876
+ if not component.memoized or old.rendered is None or old.hook_state is None or old.hook_state._dirty:
877
+ return False
878
+ compare = component.props_equal or shallow_equal_props
879
+ try:
880
+ return bool(compare(old.element.props, new_el.props)) and old.element.children == new_el.children
881
+ except Exception:
882
+ return False
883
+
884
+ def _reconcile_child_list(self, old_children: List[VNode], new_children: List[Element]) -> List[VNode]:
885
+ """Match, reconcile, create, and destroy one level of children.
886
+
887
+ Keyed children match by key; unkeyed children match by position
888
+ among the unkeyed siblings. Type changes replace the node. This
889
+ pass is purely structural: native attachment order is derived
890
+ afterwards by the nearest native container via
891
+ [`_sync_native_children`][pythonnative.reconciler.core.Reconciler._sync_native_children].
892
+
893
+ On failure, any freshly created replacement nodes are destroyed
894
+ before the exception propagates so an enclosing boundary can
895
+ swap in its fallback without leaking native views.
896
+ """
897
+ old_by_key: Dict[Any, VNode] = {}
898
+ old_unkeyed: List[VNode] = []
899
+ for child in old_children:
900
+ if child.element.key is not None:
901
+ old_by_key[child.element.key] = child
902
+ else:
903
+ old_unkeyed.append(child)
904
+
905
+ result: List[VNode] = []
906
+ fresh: List[VNode] = []
907
+ used_keys: Set[Any] = set()
908
+ unkeyed_iter = iter(old_unkeyed)
909
+ try:
910
+ for new_el in new_children:
911
+ matched: Optional[VNode] = None
912
+ if new_el.key is not None:
913
+ matched = old_by_key.get(new_el.key)
914
+ if matched is not None:
915
+ used_keys.add(new_el.key)
916
+ else:
917
+ matched = next(unkeyed_iter, None)
918
+
919
+ if matched is None:
920
+ node = self._create_tree(new_el)
921
+ fresh.append(node)
922
+ result.append(node)
923
+ elif not self._same_type(matched.element, new_el):
924
+ node = self._create_tree(new_el)
925
+ self._destroy_tree(matched)
926
+ fresh.append(node)
927
+ result.append(node)
928
+ else:
929
+ result.append(self._reconcile_node(matched, new_el))
930
+ except Exception:
931
+ for node in fresh:
932
+ self._destroy_tree(node)
933
+ raise
934
+
935
+ for key, node in old_by_key.items():
936
+ if key not in used_keys:
937
+ self._destroy_tree(node)
938
+ for node in unkeyed_iter:
939
+ self._destroy_tree(node)
940
+ return result
941
+
942
+ # ------------------------------------------------------------------
943
+ # Destruction
944
+ # ------------------------------------------------------------------
945
+
946
+ def _destroy_tree(self, node: VNode, salvage: Optional[HydrationMap] = None) -> None:
947
+ """Tear down a subtree, destroying native views and cleaning hook state.
948
+
949
+ Args:
950
+ node: Root of the subtree to destroy.
951
+ salvage: When given (a Suspense boundary is unwinding),
952
+ component hook states move into this map keyed by
953
+ ``(component identity, element key)`` instead of being
954
+ cleaned up, so the boundary's retry can re-adopt them.
955
+ """
956
+ if not node.mounted:
957
+ return
958
+ node.mounted = False
959
+ self._dirty_nodes.pop(id(node), None)
960
+ self._dirty_boundaries.pop(id(node), None)
961
+ self._dirty_suspense.pop(id(node), None)
962
+ if node.suspense_hydration:
963
+ self._dispose_hydration(node.suspense_hydration)
964
+ node.suspense_hydration = None
965
+ hs = node.hook_state
966
+ if hs is not None:
967
+ self._effect_states.pop(id(hs), None)
968
+ if salvage is not None and node.is_component:
969
+ salvage.setdefault((id(node.element.type), node.element.key), []).append(hs)
970
+ else:
971
+ hs.cleanup_all_effects()
972
+ hs.detach()
973
+ self._clear_ref(node.element.props.get("ref"))
974
+ for child in node.children:
975
+ self._destroy_tree(child, salvage=salvage)
976
+ if node.is_native and node.tag is not None:
977
+ self._native_children.pop(node.tag, None)
978
+ self._tag_nodes.pop(node.tag, None)
979
+ self._events.clear(node.tag)
980
+ self._ops.append(DestroyOp(node.tag))
981
+ self._destroyed_tags.add(node.tag)
982
+ node.children = []
983
+ node.parent = None
984
+ node.layout_node = None
985
+
986
+ # ------------------------------------------------------------------
987
+ # Native structure helpers
988
+ # ------------------------------------------------------------------
989
+
990
+ def _native_roots(self, node: VNode) -> List[VNode]:
991
+ """The native views ``node`` contributes to its native parent, in order.
992
+
993
+ Native elements contribute themselves, except ``Portal``, whose
994
+ handler self-attaches to a top-level overlay. Transparent
995
+ wrappers contribute the concatenation of their children's roots.
996
+ """
997
+ if node.is_native:
998
+ return [] if node.element.type == "Portal" else [node]
999
+ roots: List[VNode] = []
1000
+ for child in node.children:
1001
+ roots.extend(self._native_roots(child))
1002
+ return roots
1003
+
1004
+ def _flattened_child_roots(self, node: VNode) -> List[VNode]:
1005
+ roots: List[VNode] = []
1006
+ for child in node.children:
1007
+ roots.extend(self._native_roots(child))
1008
+ return roots
1009
+
1010
+ def _flattened_child_tags(self, node: VNode) -> List[int]:
1011
+ return [root.tag for root in self._flattened_child_roots(node) if root.tag is not None]
1012
+
1013
+ @staticmethod
1014
+ def _nearest_native_ancestor(node: VNode) -> Optional[VNode]:
1015
+ current = node.parent
1016
+ while current is not None:
1017
+ if current.is_native:
1018
+ return current
1019
+ current = current.parent
1020
+ return None
1021
+
1022
+ def _refresh_identity(self, node: VNode) -> None:
1023
+ """Point a wrapper's ``tag`` / ``native_view`` at its first native root."""
1024
+ if node.is_native:
1025
+ return
1026
+ for root in self._native_roots(node):
1027
+ node.tag = root.tag
1028
+ node.native_view = root.native_view
1029
+ return
1030
+ node.tag = None
1031
+ node.native_view = None
1032
+
1033
+ def _sync_native_children(self, parent_tag: int, before_tags: List[int], after_roots: List[VNode]) -> bool:
1034
+ """Emit the inserts that turn the parent's native child list into ``after_roots``.
1035
+
1036
+ Children destroyed this pass have already had their views torn
1037
+ down, so they're dropped from the "before" picture; the
1038
+ remaining difference is expressed as the minimal move-aware
1039
+ insert sequence (longest-increasing-subsequence based, so a
1040
+ keyed reorder of *n* children costs at most *n* minus the
1041
+ length of the already-ordered run).
1042
+
1043
+ Returns whether the native child list changed at all.
1044
+ """
1045
+ self._native_children[parent_tag] = [root.tag for root in after_roots if root.tag is not None]
1046
+ surviving = [t for t in before_tags if t not in self._destroyed_tags]
1047
+ after_tags = [r.tag for r in after_roots if r.tag is not None]
1048
+ if surviving == after_tags:
1049
+ return len(surviving) != len(before_tags)
1050
+ for tag, index in plan_child_moves(surviving, after_tags):
1051
+ self._ops.append(InsertOp(parent_tag, tag, index))
1052
+ return True
1053
+
1054
+ def _warn_on_multiple_roots(self) -> None:
1055
+ if not diagnostics.is_dev() or self.root is None:
1056
+ return
1057
+ roots = self._native_roots(self.root)
1058
+ if len(roots) > 1:
1059
+ diagnostics.warn_once(
1060
+ f"The screen root rendered {len(roots)} native views; only the first "
1061
+ "is attached to the window. Wrap your root in a View/Column (Portals "
1062
+ "are exempt and may appear anywhere).",
1063
+ key=f"multi-root:{id(self)}",
1064
+ )
1065
+
1066
+ # ------------------------------------------------------------------
1067
+ # Props and refs
1068
+ # ------------------------------------------------------------------
1069
+
1070
+ @staticmethod
1071
+ def _split_props(props: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Callable[..., Any]]]:
1072
+ """Strip reconciler-owned keys, then split event callables from native props."""
1073
+ if not props:
1074
+ return {}, {}
1075
+ stripped = {key: value for key, value in props.items() if key not in _RECONCILER_OWNED_PROPS}
1076
+ clean, events = extract_events(stripped)
1077
+ from ..animated import AnimatedEvent
1078
+
1079
+ bindings = {
1080
+ name: {field: id(value) for field, value in callback._bindings.items()}
1081
+ for name, callback in events.items()
1082
+ if isinstance(callback, AnimatedEvent)
1083
+ }
1084
+ if bindings:
1085
+ clean["_pn_animated_events"] = bindings
1086
+ return clean, events
1087
+
1088
+ @staticmethod
1089
+ def _diff_props(old: Dict[str, Any], new: Dict[str, Any]) -> Dict[str, Any]:
1090
+ """Return only the props that changed between two clean prop dicts (removed props map to ``None``)."""
1091
+ changed: Dict[str, Any] = {}
1092
+ for key, new_val in new.items():
1093
+ if key not in old:
1094
+ changed[key] = new_val
1095
+ continue
1096
+ old_val = old[key]
1097
+ try:
1098
+ if callable(new_val) or callable(old_val):
1099
+ if old_val is not new_val:
1100
+ changed[key] = new_val
1101
+ elif old_val != new_val:
1102
+ changed[key] = new_val
1103
+ except Exception:
1104
+ changed[key] = new_val
1105
+ for key in old:
1106
+ if key not in new:
1107
+ changed[key] = None
1108
+ return changed
1109
+
1110
+ @staticmethod
1111
+ def _value_changed(old_value: Any, new_value: Any) -> bool:
1112
+ if old_value is _MISSING:
1113
+ return True
1114
+ if old_value is new_value:
1115
+ return False
1116
+ try:
1117
+ return bool(old_value != new_value)
1118
+ except Exception:
1119
+ return True
1120
+
1121
+ @staticmethod
1122
+ def _attach_ref(element: Element, native_view: Any, tag: Optional[int]) -> None:
1123
+ ref = element.props.get("ref")
1124
+ if ref is None:
1125
+ return
1126
+ if hasattr(ref, "current"):
1127
+ ref.current = native_view
1128
+ try:
1129
+ ref._pn_tag = tag
1130
+ except Exception:
1131
+ pass
1132
+ elif diagnostics.is_dev():
1133
+ diagnostics.warn_once(
1134
+ f"Ignoring ref of type {type(ref).__name__}; pass the Ref returned by use_ref().",
1135
+ key=f"badref:{type(ref).__name__}",
1136
+ )
1137
+
1138
+ @staticmethod
1139
+ def _clear_ref(ref: Any) -> None:
1140
+ if ref is None or not hasattr(ref, "current"):
1141
+ return
1142
+ try:
1143
+ ref.current = None
1144
+ ref._pn_tag = None
1145
+ except Exception:
1146
+ pass
1147
+
1148
+ @staticmethod
1149
+ def _same_type(old_el: Element, new_el: Element) -> bool:
1150
+ if isinstance(old_el.type, str):
1151
+ return old_el.type == new_el.type
1152
+ return old_el.type is new_el.type
1153
+
1154
+ @staticmethod
1155
+ def _provider_label(context: Context) -> str:
1156
+ return f"{context.name}.Provider" if context.name else "Provider"
1157
+
1158
+ @staticmethod
1159
+ def _log(msg: str) -> None:
1160
+ if os.environ.get("PYTHONNATIVE_DEBUG", "").lower() not in {"1", "true", "yes", "on"}:
1161
+ return
1162
+ try:
1163
+ print(f"[PN] reconciler: {msg}", flush=True)
1164
+ except Exception:
1165
+ pass