@use-voltra/ios-client 2.2.0 → 2.3.1

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 (289) hide show
  1. package/Voltra.podspec +1 -0
  2. package/build/commonjs/events.js +23 -0
  3. package/build/commonjs/events.js.map +1 -1
  4. package/build/commonjs/index.js +59 -0
  5. package/build/commonjs/index.js.map +1 -1
  6. package/build/commonjs/live-activity/api.js +3 -1
  7. package/build/commonjs/live-activity/api.js.map +1 -1
  8. package/build/commonjs/live-activity/dynamic-api.js +173 -0
  9. package/build/commonjs/live-activity/dynamic-api.js.map +1 -0
  10. package/build/commonjs/native/NativeVoltra.js.map +1 -1
  11. package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js +47 -0
  12. package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
  13. package/build/commonjs/utils/enableWidgetHotReload.js +8 -11
  14. package/build/commonjs/utils/enableWidgetHotReload.js.map +1 -1
  15. package/build/commonjs/utils/useUpdateOnHMR.js +3 -7
  16. package/build/commonjs/utils/useUpdateOnHMR.js.map +1 -1
  17. package/build/commonjs/widgets/server-credentials.js +9 -0
  18. package/build/commonjs/widgets/server-credentials.js.map +1 -1
  19. package/build/commonjs/widgets/server-update.js +78 -0
  20. package/build/commonjs/widgets/server-update.js.map +1 -0
  21. package/build/module/events.js +23 -0
  22. package/build/module/events.js.map +1 -1
  23. package/build/module/index.js +3 -0
  24. package/build/module/index.js.map +1 -1
  25. package/build/module/live-activity/api.js +2 -2
  26. package/build/module/live-activity/api.js.map +1 -1
  27. package/build/module/live-activity/dynamic-api.js +167 -0
  28. package/build/module/live-activity/dynamic-api.js.map +1 -0
  29. package/build/module/native/NativeVoltra.js.map +1 -1
  30. package/build/module/utils/enableDynamicLiveActivityHotReload.js +43 -0
  31. package/build/module/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
  32. package/build/module/utils/enableWidgetHotReload.js +9 -11
  33. package/build/module/utils/enableWidgetHotReload.js.map +1 -1
  34. package/build/module/utils/useUpdateOnHMR.js +3 -7
  35. package/build/module/utils/useUpdateOnHMR.js.map +1 -1
  36. package/build/module/widgets/server-credentials.js +9 -0
  37. package/build/module/widgets/server-credentials.js.map +1 -1
  38. package/build/module/widgets/server-update.js +72 -0
  39. package/build/module/widgets/server-update.js.map +1 -0
  40. package/build/typescript/commonjs/events.d.ts +7 -0
  41. package/build/typescript/commonjs/events.d.ts.map +1 -1
  42. package/build/typescript/commonjs/index.d.ts +3 -0
  43. package/build/typescript/commonjs/index.d.ts.map +1 -1
  44. package/build/typescript/commonjs/live-activity/api.d.ts +7 -0
  45. package/build/typescript/commonjs/live-activity/api.d.ts.map +1 -1
  46. package/build/typescript/commonjs/live-activity/dynamic-api.d.ts +54 -0
  47. package/build/typescript/commonjs/live-activity/dynamic-api.d.ts.map +1 -0
  48. package/build/typescript/commonjs/native/NativeVoltra.d.ts +20 -0
  49. package/build/typescript/commonjs/native/NativeVoltra.d.ts.map +1 -1
  50. package/build/typescript/commonjs/types.d.ts +1 -1
  51. package/build/typescript/commonjs/types.d.ts.map +1 -1
  52. package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
  53. package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
  54. package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts +5 -7
  55. package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts.map +1 -1
  56. package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts +0 -3
  57. package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts.map +1 -1
  58. package/build/typescript/commonjs/widgets/server-credentials.d.ts +8 -0
  59. package/build/typescript/commonjs/widgets/server-credentials.d.ts.map +1 -1
  60. package/build/typescript/commonjs/widgets/server-update.d.ts +64 -0
  61. package/build/typescript/commonjs/widgets/server-update.d.ts.map +1 -0
  62. package/build/typescript/module/events.d.ts +7 -0
  63. package/build/typescript/module/events.d.ts.map +1 -1
  64. package/build/typescript/module/index.d.ts +3 -0
  65. package/build/typescript/module/index.d.ts.map +1 -1
  66. package/build/typescript/module/live-activity/api.d.ts +7 -0
  67. package/build/typescript/module/live-activity/api.d.ts.map +1 -1
  68. package/build/typescript/module/live-activity/dynamic-api.d.ts +54 -0
  69. package/build/typescript/module/live-activity/dynamic-api.d.ts.map +1 -0
  70. package/build/typescript/module/native/NativeVoltra.d.ts +20 -0
  71. package/build/typescript/module/native/NativeVoltra.d.ts.map +1 -1
  72. package/build/typescript/module/types.d.ts +1 -1
  73. package/build/typescript/module/types.d.ts.map +1 -1
  74. package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
  75. package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
  76. package/build/typescript/module/utils/enableWidgetHotReload.d.ts +5 -7
  77. package/build/typescript/module/utils/enableWidgetHotReload.d.ts.map +1 -1
  78. package/build/typescript/module/utils/useUpdateOnHMR.d.ts +0 -3
  79. package/build/typescript/module/utils/useUpdateOnHMR.d.ts.map +1 -1
  80. package/build/typescript/module/widgets/server-credentials.d.ts +8 -0
  81. package/build/typescript/module/widgets/server-credentials.d.ts.map +1 -1
  82. package/build/typescript/module/widgets/server-update.d.ts +64 -0
  83. package/build/typescript/module/widgets/server-update.d.ts.map +1 -0
  84. package/expo-plugin/build/cjs/constants.js +20 -1
  85. package/expo-plugin/build/cjs/constants.js.map +1 -1
  86. package/expo-plugin/build/cjs/index.js +6 -0
  87. package/expo-plugin/build/cjs/index.js.map +1 -1
  88. package/expo-plugin/build/cjs/ios/index.js +1 -0
  89. package/expo-plugin/build/cjs/ios/index.js.map +1 -1
  90. package/expo-plugin/build/cjs/ios/infoPlist.js +28 -5
  91. package/expo-plugin/build/cjs/ios/infoPlist.js.map +1 -1
  92. package/expo-plugin/build/cjs/ios/serverUpdate.js +25 -0
  93. package/expo-plugin/build/cjs/ios/serverUpdate.js.map +1 -0
  94. package/expo-plugin/build/cjs/ios-widget/clientRendered.js +4 -4
  95. package/expo-plugin/build/cjs/ios-widget/clientRendered.js.map +1 -1
  96. package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js +5 -5
  97. package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js.map +1 -1
  98. package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js +130 -0
  99. package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js.map +1 -0
  100. package/expo-plugin/build/cjs/ios-widget/files/index.js +9 -2
  101. package/expo-plugin/build/cjs/ios-widget/files/index.js.map +1 -1
  102. package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js +6 -3
  103. package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js.map +1 -1
  104. package/expo-plugin/build/cjs/ios-widget/files/manifest.js +24 -0
  105. package/expo-plugin/build/cjs/ios-widget/files/manifest.js.map +1 -1
  106. package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js +7 -0
  107. package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js.map +1 -0
  108. package/expo-plugin/build/cjs/ios-widget/files/swift.js +56 -26
  109. package/expo-plugin/build/cjs/ios-widget/files/swift.js.map +1 -1
  110. package/expo-plugin/build/cjs/ios-widget/index.js +9 -3
  111. package/expo-plugin/build/cjs/ios-widget/index.js.map +1 -1
  112. package/expo-plugin/build/cjs/ios-widget/widgetPlist.js +34 -22
  113. package/expo-plugin/build/cjs/ios-widget/widgetPlist.js.map +1 -1
  114. package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js +51 -11
  115. package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js.map +1 -1
  116. package/expo-plugin/build/cjs/ios-widget/xcode/index.js +21 -5
  117. package/expo-plugin/build/cjs/ios-widget/xcode/index.js.map +1 -1
  118. package/expo-plugin/build/cjs/utils/urlScheme.js +36 -14
  119. package/expo-plugin/build/cjs/utils/urlScheme.js.map +1 -1
  120. package/expo-plugin/build/cjs/validation.js +54 -0
  121. package/expo-plugin/build/cjs/validation.js.map +1 -1
  122. package/expo-plugin/build/esm/constants.js +17 -0
  123. package/expo-plugin/build/esm/constants.js.map +1 -1
  124. package/expo-plugin/build/esm/index.js +6 -0
  125. package/expo-plugin/build/esm/index.js.map +1 -1
  126. package/expo-plugin/build/esm/ios/index.js +1 -0
  127. package/expo-plugin/build/esm/ios/index.js.map +1 -1
  128. package/expo-plugin/build/esm/ios/infoPlist.js +28 -5
  129. package/expo-plugin/build/esm/ios/infoPlist.js.map +1 -1
  130. package/expo-plugin/build/esm/ios/serverUpdate.js +21 -0
  131. package/expo-plugin/build/esm/ios/serverUpdate.js.map +1 -0
  132. package/expo-plugin/build/esm/ios-widget/clientRendered.js +5 -5
  133. package/expo-plugin/build/esm/ios-widget/clientRendered.js.map +1 -1
  134. package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js +6 -6
  135. package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js.map +1 -1
  136. package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js +122 -0
  137. package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js.map +1 -0
  138. package/expo-plugin/build/esm/ios-widget/files/index.js +10 -3
  139. package/expo-plugin/build/esm/ios-widget/files/index.js.map +1 -1
  140. package/expo-plugin/build/esm/ios-widget/files/infoPlist.js +6 -3
  141. package/expo-plugin/build/esm/ios-widget/files/infoPlist.js.map +1 -1
  142. package/expo-plugin/build/esm/ios-widget/files/manifest.js +23 -1
  143. package/expo-plugin/build/esm/ios-widget/files/manifest.js.map +1 -1
  144. package/expo-plugin/build/esm/ios-widget/files/swift-utils.js +4 -0
  145. package/expo-plugin/build/esm/ios-widget/files/swift-utils.js.map +1 -0
  146. package/expo-plugin/build/esm/ios-widget/files/swift.js +50 -20
  147. package/expo-plugin/build/esm/ios-widget/files/swift.js.map +1 -1
  148. package/expo-plugin/build/esm/ios-widget/index.js +9 -3
  149. package/expo-plugin/build/esm/ios-widget/index.js.map +1 -1
  150. package/expo-plugin/build/esm/ios-widget/widgetPlist.js +34 -22
  151. package/expo-plugin/build/esm/ios-widget/widgetPlist.js.map +1 -1
  152. package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js +51 -11
  153. package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js.map +1 -1
  154. package/expo-plugin/build/esm/ios-widget/xcode/index.js +21 -5
  155. package/expo-plugin/build/esm/ios-widget/xcode/index.js.map +1 -1
  156. package/expo-plugin/build/esm/utils/urlScheme.js +34 -14
  157. package/expo-plugin/build/esm/utils/urlScheme.js.map +1 -1
  158. package/expo-plugin/build/esm/validation.js +54 -1
  159. package/expo-plugin/build/esm/validation.js.map +1 -1
  160. package/expo-plugin/build/types/constants.d.ts +10 -1
  161. package/expo-plugin/build/types/constants.d.ts.map +1 -1
  162. package/expo-plugin/build/types/index.d.ts +1 -1
  163. package/expo-plugin/build/types/index.d.ts.map +1 -1
  164. package/expo-plugin/build/types/ios/index.d.ts +2 -0
  165. package/expo-plugin/build/types/ios/index.d.ts.map +1 -1
  166. package/expo-plugin/build/types/ios/infoPlist.d.ts +2 -0
  167. package/expo-plugin/build/types/ios/infoPlist.d.ts.map +1 -1
  168. package/expo-plugin/build/types/ios/serverUpdate.d.ts +11 -0
  169. package/expo-plugin/build/types/ios/serverUpdate.d.ts.map +1 -0
  170. package/expo-plugin/build/types/ios-widget/clientRendered.d.ts.map +1 -1
  171. package/expo-plugin/build/types/ios-widget/clientRenderedPrerender.d.ts.map +1 -1
  172. package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts +7 -0
  173. package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts.map +1 -0
  174. package/expo-plugin/build/types/ios-widget/files/index.d.ts +3 -1
  175. package/expo-plugin/build/types/ios-widget/files/index.d.ts.map +1 -1
  176. package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts +2 -1
  177. package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts.map +1 -1
  178. package/expo-plugin/build/types/ios-widget/files/manifest.d.ts +9 -2
  179. package/expo-plugin/build/types/ios-widget/files/manifest.d.ts.map +1 -1
  180. package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts +2 -0
  181. package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts.map +1 -0
  182. package/expo-plugin/build/types/ios-widget/files/swift.d.ts +6 -2
  183. package/expo-plugin/build/types/ios-widget/files/swift.d.ts.map +1 -1
  184. package/expo-plugin/build/types/ios-widget/index.d.ts +3 -1
  185. package/expo-plugin/build/types/ios-widget/index.d.ts.map +1 -1
  186. package/expo-plugin/build/types/ios-widget/widgetPlist.d.ts.map +1 -1
  187. package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts +3 -1
  188. package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts.map +1 -1
  189. package/expo-plugin/build/types/ios-widget/xcode/index.d.ts +2 -1
  190. package/expo-plugin/build/types/ios-widget/xcode/index.d.ts.map +1 -1
  191. package/expo-plugin/build/types/types.d.ts +23 -4
  192. package/expo-plugin/build/types/types.d.ts.map +1 -1
  193. package/expo-plugin/build/types/utils/urlScheme.d.ts +16 -0
  194. package/expo-plugin/build/types/utils/urlScheme.d.ts.map +1 -1
  195. package/expo-plugin/build/types/validation.d.ts +2 -1
  196. package/expo-plugin/build/types/validation.d.ts.map +1 -1
  197. package/expo-plugin/src/constants.ts +24 -1
  198. package/expo-plugin/src/index.ts +9 -0
  199. package/expo-plugin/src/ios/index.ts +3 -0
  200. package/expo-plugin/src/ios/infoPlist.ts +33 -5
  201. package/expo-plugin/src/ios/serverUpdate.ts +29 -0
  202. package/expo-plugin/src/ios-widget/clientRendered.ts +9 -5
  203. package/expo-plugin/src/ios-widget/clientRenderedPrerender.ts +11 -6
  204. package/expo-plugin/src/ios-widget/dynamic-live-activity/swift.ts +138 -0
  205. package/expo-plugin/src/ios-widget/files/index.ts +15 -4
  206. package/expo-plugin/src/ios-widget/files/infoPlist.ts +17 -3
  207. package/expo-plugin/src/ios-widget/files/manifest.ts +42 -2
  208. package/expo-plugin/src/ios-widget/files/swift-utils.ts +3 -0
  209. package/expo-plugin/src/ios-widget/files/swift.ts +69 -22
  210. package/expo-plugin/src/ios-widget/index.ts +13 -3
  211. package/expo-plugin/src/ios-widget/widgetPlist.ts +37 -23
  212. package/expo-plugin/src/ios-widget/xcode/__snapshots__/applyXcodeChanges.node.test.ts.snap +34 -28
  213. package/expo-plugin/src/ios-widget/xcode/buildPhases.ts +59 -12
  214. package/expo-plugin/src/ios-widget/xcode/index.ts +23 -6
  215. package/expo-plugin/src/types.ts +28 -4
  216. package/expo-plugin/src/utils/urlScheme.ts +47 -17
  217. package/expo-plugin/src/validation.ts +76 -1
  218. package/ios/Package.swift +32 -3
  219. package/ios/VoltraWidget.podspec +1 -0
  220. package/ios/app/NativeVoltra.mm +107 -7
  221. package/ios/app/VoltraLiveActivityManager.swift +30 -0
  222. package/ios/app/VoltraLiveActivityService.swift +77 -5
  223. package/ios/app/VoltraModule.swift +99 -1
  224. package/ios/app/VoltraModuleImpl.swift +100 -2
  225. package/ios/app/VoltraViewComponentView.mm +3 -3
  226. package/ios/app/VoltraWidgetService.swift +147 -12
  227. package/ios/app/dynamic-live-activity/VoltraDynamicLiveActivityService.swift +97 -0
  228. package/ios/app/dynamic-live-activity/VoltraLiveActivityChronology.swift +68 -0
  229. package/ios/shared/DynamicWidgetPropsStore.swift +73 -6
  230. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerFetchResolver.swift +34 -0
  231. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerProps.swift +55 -0
  232. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerPropsStore.swift +146 -0
  233. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerUpdateRunner.swift +193 -0
  234. package/ios/shared/ServerWidgetContentResolver.swift +60 -0
  235. package/ios/shared/ServerWidgetResponseStore.swift +25 -0
  236. package/ios/shared/ShortNames.swift +6 -0
  237. package/ios/shared/VoltraConfig.swift +41 -0
  238. package/ios/shared/VoltraConstants.swift +15 -0
  239. package/ios/shared/VoltraElement.swift +127 -43
  240. package/ios/shared/VoltraEventBus.swift +52 -7
  241. package/ios/shared/VoltraJSRenderer.swift +90 -22
  242. package/ios/shared/VoltraNode.swift +1 -109
  243. package/ios/shared/VoltraRefreshIntent.swift +1 -1
  244. package/ios/shared/VoltraWidgetKind.swift +41 -0
  245. package/ios/shared/VoltraWidgetServerFetcher.swift +52 -107
  246. package/ios/shared/WidgetServer/VoltraWidgetAppearance.swift +17 -0
  247. package/ios/shared/WidgetServer/VoltraWidgetServer.swift +138 -0
  248. package/ios/shared/WidgetServer/WidgetCanonicalConfiguration.swift +77 -0
  249. package/ios/shared/WidgetServer/WidgetScope.swift +55 -0
  250. package/ios/shared/WidgetServer/WidgetServerEtagStore.swift +55 -0
  251. package/ios/shared/WidgetServer/WidgetServerFetcher.swift +179 -0
  252. package/ios/shared/WidgetServer/WidgetServerRequestBuilder.swift +114 -0
  253. package/ios/shared/WidgetServer/WidgetServerSettingsCodec.swift +47 -0
  254. package/ios/shared/WidgetServer/WidgetServerSettingsResolver.swift +129 -0
  255. package/ios/shared/WidgetServer/WidgetServerSettingsStore.swift +170 -0
  256. package/ios/shared/WidgetServer/WidgetServerSettingsValidator.swift +73 -0
  257. package/ios/shared/WidgetServer/WidgetServerUpdateSettings.swift +123 -0
  258. package/ios/shared/WidgetServer/WidgetServerUpdateSettingsJson.swift +115 -0
  259. package/ios/shared/dynamic-live-activity/DynamicLiveActivityPropsCodec.swift +13 -0
  260. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityBundleSource.swift +156 -0
  261. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityObserver.swift +97 -0
  262. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityOperations.swift +105 -0
  263. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityPayloadValidator.swift +55 -0
  264. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRegistry.swift +143 -0
  265. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureQueue.swift +158 -0
  266. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureReporter.swift +120 -0
  267. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderer.swift +370 -0
  268. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityTypes.swift +87 -0
  269. package/ios/shared/dynamic-live-activity/VoltraLiveActivityOrder.swift +19 -0
  270. package/ios/target/VoltraClientWidgetRuntime.swift +44 -6
  271. package/ios/target/VoltraDynamicWidgetServerUpdateProvider.swift +204 -0
  272. package/ios/target/VoltraHomeWidget.swift +85 -90
  273. package/ios/ui/Extensions/VoltraNode+View.swift +113 -0
  274. package/ios/ui/Generated/Parameters/ChartParameters.swift +3 -0
  275. package/ios/ui/Helpers/VoltraDeepLinkResolver.swift +2 -2
  276. package/ios/ui/Views/VoltraChart.swift +108 -0
  277. package/package.json +4 -4
  278. package/src/events.ts +34 -0
  279. package/src/index.ts +22 -0
  280. package/src/live-activity/api.ts +2 -2
  281. package/src/live-activity/dynamic-api.ts +240 -0
  282. package/src/native/NativeVoltra.ts +21 -0
  283. package/src/types.ts +4 -0
  284. package/src/utils/enableDynamicLiveActivityHotReload.ts +50 -0
  285. package/src/utils/enableWidgetHotReload.ts +9 -16
  286. package/src/utils/useUpdateOnHMR.ts +3 -12
  287. package/src/widgets/server-credentials.ts +8 -0
  288. package/src/widgets/server-update.ts +84 -0
  289. /package/ios/shared/{VoltraKeychainHelper.swift → WidgetServer/VoltraKeychainHelper.swift} +0 -0
@@ -161,6 +161,103 @@ public struct VoltraChart: VoltraView {
161
161
  }
162
162
  }
163
163
 
164
+ // MARK: - Y scale
165
+
166
+ /// Y-axis bounds pinned by the `yScale` prop; either side may be left to the data.
167
+ private struct YScale {
168
+ let min: Double?
169
+ let max: Double?
170
+ }
171
+
172
+ private func parseYScale(from raw: String?) -> YScale? {
173
+ guard let raw,
174
+ let data = raw.data(using: .utf8),
175
+ let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
176
+ else { return nil }
177
+
178
+ let props = dict.compactMapValues { jsonValue(from: $0) }
179
+ let min = (props["mn"] ?? props["min"]).flatMap(num)
180
+ let max = (props["mx"] ?? props["max"]).flatMap(num)
181
+ if min == nil, max == nil {
182
+ return nil
183
+ }
184
+ return YScale(min: min, max: max)
185
+ }
186
+
187
+ /// Domain to hand SwiftUI when `yScale` pins a bound. The open side follows the data exactly as
188
+ /// `computeYDomain` in Android's ChartBitmapRenderer.kt frames it, so both platforms agree.
189
+ private func yScaleDomain(_ marks: [WireMark], pinned: YScale) -> ClosedRange<Double> {
190
+ let auto = autoYDomain(marks)
191
+ var lower = pinned.min ?? auto.lowerBound
192
+ var upper = pinned.max ?? auto.upperBound
193
+
194
+ if upper <= lower {
195
+ // A pin that inverts the domain still has to leave something to draw in: keep the span the
196
+ // data asked for on the side the caller left open, and ignore a pair that cannot be drawn.
197
+ let span = auto.upperBound - auto.lowerBound
198
+ if pinned.max == nil {
199
+ upper = lower + span
200
+ } else if pinned.min == nil {
201
+ lower = upper - span
202
+ } else {
203
+ return auto
204
+ }
205
+ if upper <= lower {
206
+ upper = lower + 1
207
+ }
208
+ }
209
+
210
+ return lower ... upper
211
+ }
212
+
213
+ private func autoYDomain(_ marks: [WireMark]) -> ClosedRange<Double> {
214
+ var values = marks.flatMap { mark -> [Double] in
215
+ guard mark.type != "sector" else { return [] }
216
+ return (mark.data ?? []).map { xy($0).y }
217
+ }
218
+ values += marks.compactMap { $0.type == "rule" ? $0.props["yv"].flatMap(num) : nil }
219
+
220
+ let finite = values.filter(\.isFinite)
221
+ guard let dataMin = finite.min(), let dataMax = finite.max() else { return 0 ... 1 }
222
+
223
+ // Bars and areas measure their value against the baseline, so zero stays in range.
224
+ if marks.contains(where: { $0.type == "bar" || $0.type == "area" }) {
225
+ let lower = Swift.min(dataMin, 0)
226
+ let upper = Swift.max(dataMax, 0)
227
+ return upper > lower ? lower ... upper : lower ... (lower + 1)
228
+ }
229
+
230
+ guard dataMax > dataMin else {
231
+ let padding = dataMin == 0 ? 1 : abs(dataMin) * Self.yDomainPaddingRatio
232
+ return (dataMin - padding) ... (dataMax + padding)
233
+ }
234
+
235
+ let padding = (dataMax - dataMin) * Self.yDomainPaddingRatio
236
+ var lower = dataMin - padding
237
+ var upper = dataMax + padding
238
+
239
+ // Padding must not invent values on the other side of the baseline, and data that almost
240
+ // reaches zero keeps it in view.
241
+ if dataMin >= 0, lower < 0 {
242
+ lower = 0
243
+ }
244
+ if dataMax <= 0, upper > 0 {
245
+ upper = 0
246
+ }
247
+ let span = upper - lower
248
+ if lower > 0, lower < span * Self.zeroSnapRatio {
249
+ lower = 0
250
+ }
251
+ if upper < 0, -upper < span * Self.zeroSnapRatio {
252
+ upper = 0
253
+ }
254
+
255
+ return lower ... upper
256
+ }
257
+
258
+ private static let yDomainPaddingRatio = 0.1
259
+ private static let zeroSnapRatio = 0.25
260
+
164
261
  // MARK: - body
165
262
 
166
263
  public var body: some View {
@@ -171,10 +268,12 @@ public struct VoltraChart: VoltraView {
171
268
  let yAxisGridStyle = parseAxisGridStyle(from: params.yAxisGridStyle)
172
269
  let legendVis = visibility(params.legendVisibility)
173
270
  let legendItems = parseLegendItems(from: params.foregroundStyleScale)
271
+ let yDomain = parseYScale(from: params.yScale).map { yScaleDomain(wireMarks, pinned: $0) }
174
272
 
175
273
  Chart {
176
274
  buildAll(wireMarks)
177
275
  }
276
+ .applyChartYScale(domain: yDomain)
178
277
  .applyForegroundStyleScale(params.foregroundStyleScale)
179
278
  .applyChartStyle(
180
279
  element.style,
@@ -734,6 +833,15 @@ private extension View {
734
833
  }
735
834
  }
736
835
 
836
+ @ViewBuilder
837
+ func applyChartYScale(domain: ClosedRange<Double>?) -> some View {
838
+ if let domain {
839
+ chartYScale(domain: domain)
840
+ } else {
841
+ self
842
+ }
843
+ }
844
+
737
845
  @ViewBuilder
738
846
  func applyChartClipping(decoration: DecorationStyle, hasClipping: Bool) -> some View {
739
847
  if hasClipping {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@use-voltra/ios-client",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "Client-only Voltra APIs for iOS",
5
5
  "main": "./build/commonjs/index.js",
6
6
  "module": "./build/module/index.js",
@@ -39,9 +39,9 @@
39
39
  "@expo/plist": "^0.3.5",
40
40
  "dedent": "^1.7.1",
41
41
  "xcode": "^3.0.1",
42
- "@use-voltra/compiler": "^2.2.0",
43
- "@use-voltra/expo-plugin": "^2.2.0",
44
- "@use-voltra/ios": "^2.2.0"
42
+ "@use-voltra/compiler": "^2.3.1",
43
+ "@use-voltra/expo-plugin": "^2.3.1",
44
+ "@use-voltra/ios": "^2.3.1"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "expo": "*",
package/src/events.ts CHANGED
@@ -30,15 +30,28 @@ export type VoltraInteractionEvent = BasicVoltraEvent & {
30
30
  payload: string
31
31
  }
32
32
 
33
+ export type VoltraDynamicLiveActivityRenderFailedEvent = BasicVoltraEvent & {
34
+ type: 'dynamicLiveActivityRenderFailed'
35
+ activityName: string
36
+ definitionId: string
37
+ message: string
38
+ }
39
+
33
40
  const noopSubscription: EventSubscription = {
34
41
  remove: () => {},
35
42
  }
36
43
 
44
+ // Codegen EventEmitter subscriptions do not tell native code when their last
45
+ // JavaScript listener goes away. Keep this count at the public API boundary so
46
+ // the App Group queue is drained only while it can actually be delivered.
47
+ let dynamicRenderFailureListenerCount = 0
48
+
37
49
  export type VoltraEventMap = {
38
50
  activityTokenReceived: VoltraActivityTokenReceivedEvent
39
51
  activityPushToStartTokenReceived: VoltraActivityPushToStartTokenReceivedEvent
40
52
  stateChange: VoltraActivityUpdateEvent
41
53
  interaction: VoltraInteractionEvent
54
+ dynamicLiveActivityRenderFailed: VoltraDynamicLiveActivityRenderFailedEvent
42
55
  }
43
56
 
44
57
  export function addVoltraListener<K extends keyof VoltraEventMap>(
@@ -63,6 +76,27 @@ export function addVoltraListener<K extends keyof VoltraEventMap>(
63
76
  return voltraModule.onStateChanged(listener as (arg: VoltraActivityUpdateEvent) => void)
64
77
  case 'interaction':
65
78
  return voltraModule.onInteraction(listener as (arg: VoltraInteractionEvent) => void)
79
+ case 'dynamicLiveActivityRenderFailed':
80
+ const subscription = voltraModule.onDynamicLiveActivityRenderFailed(
81
+ listener as (arg: VoltraDynamicLiveActivityRenderFailedEvent) => void
82
+ )
83
+ dynamicRenderFailureListenerCount += 1
84
+ if (dynamicRenderFailureListenerCount === 1) {
85
+ voltraModule.setDynamicLiveActivityRenderFailureListenerActive(true)
86
+ }
87
+
88
+ let removed = false
89
+ return {
90
+ remove: () => {
91
+ if (removed) return
92
+ removed = true
93
+ subscription.remove()
94
+ dynamicRenderFailureListenerCount -= 1
95
+ if (dynamicRenderFailureListenerCount === 0) {
96
+ voltraModule.setDynamicLiveActivityRenderFailureListenerActive(false)
97
+ }
98
+ },
99
+ }
66
100
  default:
67
101
  console.warn(`[Voltra] Event '${event}' is not supported. Returning no-op subscription.`)
68
102
  return noopSubscription
package/src/index.ts CHANGED
@@ -23,6 +23,18 @@ export {
23
23
  type UseLiveActivityOptions,
24
24
  type UseLiveActivityResult,
25
25
  } from './live-activity/api.js'
26
+ export {
27
+ getDynamicLiveActivityDefinitionIds,
28
+ startDynamicLiveActivity,
29
+ type StartDynamicLiveActivityOptions,
30
+ updateDynamicLiveActivity,
31
+ type UpdateDynamicLiveActivityOptions,
32
+ useDynamicLiveActivity,
33
+ type UseDynamicLiveActivityOptions,
34
+ type UseDynamicLiveActivityResult,
35
+ type DynamicLiveActivityProps,
36
+ type DynamicLiveActivityPropsValue,
37
+ } from './live-activity/dynamic-api.js'
26
38
  export type { DismissalPolicy, LiveActivityVariants } from '@use-voltra/ios'
27
39
  export {
28
40
  clearPreloadedImages,
@@ -33,9 +45,19 @@ export {
33
45
  } from './preload.js'
34
46
  export { assertRunningOnApple } from './utils/assertRunningOnApple.js'
35
47
  export { enableWidgetHotReload } from './utils/enableWidgetHotReload.js'
48
+ export { enableDynamicLiveActivityHotReload } from './utils/enableDynamicLiveActivityHotReload.js'
36
49
  export { useUpdateOnHMR } from './utils/useUpdateOnHMR.js'
37
50
  export * from './utils/helpers.js'
38
51
  export type { VoltraElementJson, VoltraNodeJson } from './types.js'
52
+ export {
53
+ clearWidgetServerUpdate,
54
+ getWidgetServerUpdate,
55
+ setWidgetServerUpdate,
56
+ type WidgetServerUpdateBody,
57
+ type WidgetServerUpdateOptions,
58
+ type WidgetServerUpdateSettings,
59
+ type WidgetServerUpdateSnapshot,
60
+ } from './widgets/server-update.js'
39
61
  export {
40
62
  clearWidgetServerCredentials,
41
63
  setWidgetServerCredentials,
@@ -40,7 +40,7 @@ export type UseLiveActivityResult = {
40
40
  isActive: boolean
41
41
  }
42
42
 
43
- const normalizeSharedLiveActivityOptions = (
43
+ export const normalizeSharedLiveActivityOptions = (
44
44
  options?: SharedLiveActivityOptions
45
45
  ): SharedLiveActivityOptions | undefined => {
46
46
  if (!options) return undefined
@@ -66,7 +66,7 @@ const normalizeSharedLiveActivityOptions = (
66
66
  return Object.keys(normalizedOptions).length > 0 ? normalizedOptions : undefined
67
67
  }
68
68
 
69
- const normalizeEndLiveActivityOptions = (
69
+ export const normalizeEndLiveActivityOptions = (
70
70
  options?: EndLiveActivityOptions
71
71
  ): { dismissalPolicy?: { type: 'immediate' | 'after'; date?: number } } | undefined => {
72
72
  if (!options?.dismissalPolicy) return undefined
@@ -0,0 +1,240 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+
3
+ import { addVoltraListener } from '../events.js'
4
+ import { assertRunningOnApple, useUpdateOnHMR } from '../utils/index.js'
5
+ import { getNativeVoltra } from '../VoltraModule.js'
6
+ import {
7
+ isLiveActivityActive,
8
+ normalizeSharedLiveActivityOptions,
9
+ stopLiveActivity,
10
+ type EndLiveActivityOptions,
11
+ type SharedLiveActivityOptions,
12
+ } from './api.js'
13
+
14
+ /** @experimental A JSON-compatible value accepted as Dynamic Live Activity props. */
15
+ export type DynamicLiveActivityPropsValue =
16
+ | string
17
+ | number
18
+ | boolean
19
+ | null
20
+ | ReadonlyArray<DynamicLiveActivityPropsValue>
21
+ | Readonly<{ [key: string]: DynamicLiveActivityPropsValue }>
22
+
23
+ /** @experimental A complete JSON-compatible props record for a Dynamic Live Activity update. */
24
+ export type DynamicLiveActivityProps = Readonly<{ [key: string]: DynamicLiveActivityPropsValue }>
25
+
26
+ /** @experimental Options used to start a Dynamic Live Activity. */
27
+ export type StartDynamicLiveActivityOptions = {
28
+ activityName?: string
29
+ deepLinkUrl?: string
30
+ channelId?: string
31
+ } & SharedLiveActivityOptions
32
+
33
+ /** @experimental Options used to update a Dynamic Live Activity. */
34
+ export type UpdateDynamicLiveActivityOptions = SharedLiveActivityOptions
35
+
36
+ /** @experimental Options for `useDynamicLiveActivity`. */
37
+ export type UseDynamicLiveActivityOptions = StartDynamicLiveActivityOptions & {
38
+ autoStart?: boolean
39
+ autoUpdate?: boolean
40
+ }
41
+
42
+ /** @experimental Lifecycle controls returned by `useDynamicLiveActivity`. */
43
+ export type UseDynamicLiveActivityResult = {
44
+ start: (options?: StartDynamicLiveActivityOptions) => Promise<void>
45
+ update: (options?: UpdateDynamicLiveActivityOptions) => Promise<void>
46
+ end: (options?: EndLiveActivityOptions) => Promise<void>
47
+ isActive: boolean
48
+ }
49
+
50
+ function serializeDynamicLiveActivityProps(props: DynamicLiveActivityProps): string {
51
+ const ancestors = new Set<object>()
52
+
53
+ const validate = (value: unknown, path: string): void => {
54
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return
55
+
56
+ if (typeof value === 'number') {
57
+ if (!Number.isFinite(value)) {
58
+ throw new TypeError(`Dynamic Live Activity props must be JSON-compatible; ${path} is not a finite number.`)
59
+ }
60
+ return
61
+ }
62
+
63
+ if (Array.isArray(value)) {
64
+ if (ancestors.has(value)) {
65
+ throw new TypeError(
66
+ `Dynamic Live Activity props must be JSON-compatible; ${path} contains a circular reference.`
67
+ )
68
+ }
69
+ ancestors.add(value)
70
+ value.forEach((item, index) => validate(item, `${path}[${index}]`))
71
+ ancestors.delete(value)
72
+ return
73
+ }
74
+
75
+ if (typeof value === 'object') {
76
+ const prototype = Object.getPrototypeOf(value)
77
+ if (prototype !== Object.prototype && prototype !== null) {
78
+ throw new TypeError(`Dynamic Live Activity props must be JSON-compatible; ${path} must be a plain object.`)
79
+ }
80
+ if (ancestors.has(value)) {
81
+ throw new TypeError(
82
+ `Dynamic Live Activity props must be JSON-compatible; ${path} contains a circular reference.`
83
+ )
84
+ }
85
+ ancestors.add(value)
86
+ for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
87
+ validate(item, `${path}.${key}`)
88
+ }
89
+ ancestors.delete(value)
90
+ return
91
+ }
92
+
93
+ throw new TypeError(
94
+ `Dynamic Live Activity props must be JSON-compatible; ${path} has unsupported type ${typeof value}.`
95
+ )
96
+ }
97
+
98
+ if (props === null || Array.isArray(props) || typeof props !== 'object') {
99
+ throw new TypeError('Dynamic Live Activity props must be a JSON-compatible record.')
100
+ }
101
+
102
+ validate(props, 'props')
103
+ return JSON.stringify(props)
104
+ }
105
+
106
+ /**
107
+ * Starts a bundled Dynamic Live Activity with a complete opaque props record.
108
+ * @experimental
109
+ */
110
+ export async function startDynamicLiveActivity(
111
+ definitionId: string,
112
+ props: DynamicLiveActivityProps,
113
+ options?: StartDynamicLiveActivityOptions
114
+ ): Promise<string> {
115
+ if (!assertRunningOnApple()) return ''
116
+
117
+ const propsJson = serializeDynamicLiveActivityProps(props)
118
+ const normalizedSharedOptions = normalizeSharedLiveActivityOptions(options)
119
+ return getNativeVoltra().startDynamicLiveActivity(definitionId, propsJson, {
120
+ target: 'liveActivity',
121
+ deepLinkUrl: options?.deepLinkUrl,
122
+ activityName: options?.activityName,
123
+ channelId: options?.channelId,
124
+ ...normalizedSharedOptions,
125
+ })
126
+ }
127
+
128
+ /**
129
+ * Replaces a Dynamic Live Activity's complete opaque props record.
130
+ * @experimental
131
+ */
132
+ export async function updateDynamicLiveActivity(
133
+ activityName: string,
134
+ props: DynamicLiveActivityProps,
135
+ options?: UpdateDynamicLiveActivityOptions
136
+ ): Promise<void> {
137
+ if (!assertRunningOnApple()) return
138
+
139
+ const propsJson = serializeDynamicLiveActivityProps(props)
140
+ return getNativeVoltra().updateDynamicLiveActivity(
141
+ activityName,
142
+ propsJson,
143
+ normalizeSharedLiveActivityOptions(options) ?? {}
144
+ )
145
+ }
146
+
147
+ /**
148
+ * Returns the Dynamic Live Activity definition IDs bundled in the installed app.
149
+ * @experimental
150
+ */
151
+ export async function getDynamicLiveActivityDefinitionIds(): Promise<string[]> {
152
+ if (!assertRunningOnApple()) return []
153
+ return getNativeVoltra().getDynamicLiveActivityDefinitionIds()
154
+ }
155
+
156
+ /**
157
+ * Manages the lifecycle of one Dynamic Live Activity definition.
158
+ *
159
+ * Complete props are sent on every update. In development, call
160
+ * `enableDynamicLiveActivityHotReload()` once in the app host to reload only
161
+ * the definition changed by Fast Refresh.
162
+ * @experimental
163
+ */
164
+ export function useDynamicLiveActivity<Props extends DynamicLiveActivityProps = DynamicLiveActivityProps>(
165
+ definitionId: string,
166
+ props: Props,
167
+ options?: UseDynamicLiveActivityOptions
168
+ ): UseDynamicLiveActivityResult {
169
+ const [activityName, setActivityName] = useState<string | null>(() => {
170
+ if (options?.activityName) {
171
+ return isLiveActivityActive(options.activityName) ? options.activityName : null
172
+ }
173
+ return null
174
+ })
175
+ const propsRef = useRef(props)
176
+ const optionsRef = useRef(options)
177
+ const lastUpdateOptionsRef = useRef<UpdateDynamicLiveActivityOptions | undefined>(undefined)
178
+
179
+ useEffect(() => {
180
+ propsRef.current = props
181
+ }, [props])
182
+
183
+ useEffect(() => {
184
+ optionsRef.current = options
185
+ }, [options])
186
+
187
+ useUpdateOnHMR()
188
+
189
+ const start = useCallback(
190
+ async (startOptions?: StartDynamicLiveActivityOptions) => {
191
+ const id = await startDynamicLiveActivity(definitionId, propsRef.current, {
192
+ ...optionsRef.current,
193
+ ...startOptions,
194
+ })
195
+ setActivityName(id)
196
+ },
197
+ [definitionId]
198
+ )
199
+
200
+ const update = useCallback(
201
+ async (updateOptions?: UpdateDynamicLiveActivityOptions) => {
202
+ if (!activityName) return
203
+
204
+ const mergedOptions = { ...optionsRef.current, ...updateOptions }
205
+ lastUpdateOptionsRef.current = mergedOptions
206
+ await updateDynamicLiveActivity(activityName, propsRef.current, mergedOptions)
207
+ },
208
+ [activityName]
209
+ )
210
+
211
+ const end = useCallback(
212
+ async (endOptions?: EndLiveActivityOptions) => {
213
+ if (!activityName) return
214
+
215
+ await stopLiveActivity(activityName, endOptions)
216
+ setActivityName(null)
217
+ },
218
+ [activityName]
219
+ )
220
+
221
+ useEffect(() => {
222
+ if (options?.autoStart) void start()
223
+ }, [options?.autoStart, start])
224
+
225
+ useEffect(() => {
226
+ if (options?.autoUpdate) void update(lastUpdateOptionsRef.current)
227
+ }, [options?.autoUpdate, props, update])
228
+
229
+ useEffect(() => {
230
+ if (!activityName) return
231
+
232
+ const subscription = addVoltraListener('stateChange', (event) => {
233
+ if (event.activityName !== activityName) return
234
+ if (event.activityState === 'dismissed' || event.activityState === 'ended') setActivityName(null)
235
+ })
236
+ return () => subscription.remove()
237
+ }, [activityName])
238
+
239
+ return { start, update, end, isActive: activityName !== null }
240
+ }
@@ -32,6 +32,15 @@ type VoltraInteractionEvent = Readonly<{
32
32
  payload: string
33
33
  }>
34
34
 
35
+ type VoltraDynamicLiveActivityRenderFailedEvent = Readonly<{
36
+ source: string
37
+ timestamp: number
38
+ type: 'dynamicLiveActivityRenderFailed'
39
+ activityName: string
40
+ definitionId: string
41
+ message: string
42
+ }>
43
+
35
44
  type StartVoltraOptions = Readonly<{
36
45
  target?: string
37
46
  deepLinkUrl?: string
@@ -86,19 +95,26 @@ type WidgetServerCredentials = Readonly<{
86
95
 
87
96
  export interface Spec extends TurboModule {
88
97
  readonly onInteraction: CodegenTypes.EventEmitter<VoltraInteractionEvent>
98
+ readonly onDynamicLiveActivityRenderFailed: CodegenTypes.EventEmitter<VoltraDynamicLiveActivityRenderFailedEvent>
89
99
  readonly onStateChanged: CodegenTypes.EventEmitter<VoltraActivityUpdateEvent>
90
100
  readonly onActivityTokenReceived: CodegenTypes.EventEmitter<VoltraActivityTokenReceivedEvent>
91
101
  readonly onActivityPushToStartTokenReceived: CodegenTypes.EventEmitter<VoltraActivityPushToStartTokenReceivedEvent>
102
+ drainDynamicLiveActivityRenderFailures(): void
103
+ setDynamicLiveActivityRenderFailureListenerActive(active: boolean): void
92
104
  startLiveActivity(jsonString: string, options: StartVoltraOptions): Promise<string>
93
105
  updateLiveActivity(activityId: string, jsonString: string, options: UpdateVoltraOptions): Promise<void>
106
+ startDynamicLiveActivity(definitionId: string, propsJson: string, options: StartVoltraOptions): Promise<string>
107
+ updateDynamicLiveActivity(activityId: string, propsJson: string, options: UpdateVoltraOptions): Promise<void>
94
108
  endLiveActivity(activityId: string, options: EndVoltraOptions): Promise<void>
95
109
  endAllLiveActivities(): Promise<void>
96
110
  getLatestVoltraActivityId(): Promise<string | null>
97
111
  listVoltraActivityIds(): Promise<string[]>
112
+ getDynamicLiveActivityDefinitionIds(): Promise<string[]>
98
113
  isLiveActivityActive(activityName: string): boolean
99
114
  isHeadless(): boolean
100
115
  preloadImages(images: PreloadImageOptions[]): Promise<PreloadImagesResult>
101
116
  reloadLiveActivities(activityNames?: string[] | null): Promise<void>
117
+ reloadDynamicLiveActivities(definitionIds?: string[] | null): Promise<void>
102
118
  clearPreloadedImages(keys?: string[] | null): Promise<void>
103
119
  updateDynamicWidget(dynamicWidgetId: string, dynamicWidgetPropsJson: string): Promise<void>
104
120
  updateWidget(widgetId: string, jsonString: string, options: UpdateWidgetOptions): Promise<void>
@@ -107,6 +123,11 @@ export interface Spec extends TurboModule {
107
123
  clearWidget(widgetId: string): Promise<void>
108
124
  clearAllWidgets(): Promise<void>
109
125
  getActiveWidgets<T = unknown>(): Promise<T[]>
126
+ /** Settings are passed as JSON so an arbitrary `body` survives the bridge unchanged. */
127
+ setWidgetServerUpdate(settingsJson: string, widgetId?: string | null): Promise<void>
128
+ clearWidgetServerUpdate(widgetId?: string | null): Promise<void>
129
+ /** Result is JSON so an arbitrary `body` survives the bridge unchanged, or null. */
130
+ getWidgetServerUpdate(widgetId?: string | null): Promise<string | null>
110
131
  setWidgetServerCredentials(credentials: WidgetServerCredentials): Promise<void>
111
132
  clearWidgetServerCredentials(): Promise<void>
112
133
  }
package/src/types.ts CHANGED
@@ -9,4 +9,8 @@ export type {
9
9
  VoltraNodeJson,
10
10
  VoltraPropValue,
11
11
  WidgetServerCredentials,
12
+ WidgetServerUpdateBody,
13
+ WidgetServerUpdateOptions,
14
+ WidgetServerUpdateSettings,
15
+ WidgetServerUpdateSnapshot,
12
16
  } from '@use-voltra/ios'
@@ -0,0 +1,50 @@
1
+ import { getFastRefreshHub } from '@use-voltra/ios'
2
+
3
+ import { getNativeVoltra } from '../VoltraModule.js'
4
+
5
+ declare global {
6
+ var __voltraDynamicLiveActivityDefinitionUpdated: ((definitionId: string) => void) | undefined
7
+ }
8
+
9
+ /**
10
+ * Reload Dynamic Live Activities after a Metro Fast Refresh patch. When Metro
11
+ * re-evaluates a generated definition entry, only that definition is reloaded.
12
+ * Component-only refresh boundaries do not re-evaluate their importer, so those
13
+ * patches fall back to reloading every Dynamic Live Activity definition.
14
+ * Legacy activities and Dynamic Widgets are unaffected.
15
+ *
16
+ * Subscribes to the shared {@link getFastRefreshHub} so multiple `__accept`
17
+ * calls from one save collapse into a single native reload.
18
+ */
19
+ export function enableDynamicLiveActivityHotReload(): () => void {
20
+ if (!__DEV__) return () => {}
21
+
22
+ const hub = getFastRefreshHub()
23
+ const previousDefinitionUpdated = global.__voltraDynamicLiveActivityDefinitionUpdated
24
+ const updatedDefinitionIds = new Set<string>()
25
+
26
+ global.__voltraDynamicLiveActivityDefinitionUpdated = (definitionId) => {
27
+ // A generated entry re-evaluated inside a patch: batch it so the debounced
28
+ // flush reloads each changed definition exactly once. A signal that arrives
29
+ // outside a patch (the first require of a definition) has no flush coming,
30
+ // so reload it immediately.
31
+ if (hub.isAccepting) {
32
+ updatedDefinitionIds.add(definitionId)
33
+ return
34
+ }
35
+ void getNativeVoltra().reloadDynamicLiveActivities([definitionId])
36
+ }
37
+
38
+ const unsubscribe = hub.onPatch(() => {
39
+ const definitionIds = Array.from(updatedDefinitionIds)
40
+ updatedDefinitionIds.clear()
41
+ // No definition re-evaluated during this patch: a component-only boundary
42
+ // changed, so fall back to reloading every definition (null).
43
+ void getNativeVoltra().reloadDynamicLiveActivities(definitionIds.length > 0 ? definitionIds : null)
44
+ })
45
+
46
+ return () => {
47
+ unsubscribe()
48
+ global.__voltraDynamicLiveActivityDefinitionUpdated = previousDefinitionUpdated
49
+ }
50
+ }
@@ -1,14 +1,13 @@
1
- import { reloadWidgets } from '../widgets/widget-api.js'
1
+ import { getFastRefreshHub } from '@use-voltra/ios'
2
2
 
3
- declare global {
4
- var __accept: (...args: unknown[]) => void
5
- }
3
+ import { reloadWidgets } from '../widgets/widget-api.js'
6
4
 
7
5
  /**
8
6
  * Trigger `reloadWidgets()` on every Metro Fast Refresh patch (DEV only).
9
7
  *
10
- * Hooks the global `__accept` callback Metro fires when a Fast Refresh patch
11
- * lands in the host app's JS runtime. When fired, calls
8
+ * Subscribes to the shared {@link getFastRefreshHub}, which wraps the global
9
+ * `__accept` callback Metro fires when a Fast Refresh patch lands in the host
10
+ * app's JS runtime. On each debounced patch it calls
12
11
  * `WidgetCenter.shared.reloadAllTimelines()` so WidgetKit re-invokes each widget
13
12
  * Provider, which re-fetches the freshest bundle from Metro and renders the
14
13
  * updated UI.
@@ -19,21 +18,15 @@ declare global {
19
18
  * workflow the dev still relies on WidgetKit's natural lifecycle refresh on
20
19
  * app foreground.
21
20
  *
22
- * Call once at app startup. Returns `dispose()` to restore the prior
23
- * `__accept` (rarely needed in practice). No-op in release builds.
21
+ * Call once at app startup. Returns `dispose()` to unsubscribe (rarely needed
22
+ * in practice). No-op in release builds.
24
23
  */
25
24
  export function enableWidgetHotReload(): () => void {
26
25
  if (!__DEV__) {
27
26
  return () => {}
28
27
  }
29
28
 
30
- const oldAccept = global['__accept']
31
- global['__accept'] = (...args) => {
29
+ return getFastRefreshHub().onPatch(() => {
32
30
  void reloadWidgets()
33
- oldAccept?.(...args)
34
- }
35
-
36
- return () => {
37
- global['__accept'] = oldAccept
38
- }
31
+ })
39
32
  }