@pushframe/sdk 0.1.5 → 0.1.9

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 (288) hide show
  1. package/lib/commonjs/PushframeProvider.js +69 -0
  2. package/lib/commonjs/PushframeProvider.js.map +1 -0
  3. package/lib/commonjs/PushframeScreen.js +69 -0
  4. package/lib/commonjs/PushframeScreen.js.map +1 -0
  5. package/lib/commonjs/bindings.js +73 -0
  6. package/lib/commonjs/bindings.js.map +1 -0
  7. package/lib/commonjs/components/ButtonComponent.js +72 -0
  8. package/lib/commonjs/components/ButtonComponent.js.map +1 -0
  9. package/lib/commonjs/components/FlatListComponent.js +73 -0
  10. package/lib/commonjs/components/FlatListComponent.js.map +1 -0
  11. package/lib/commonjs/components/ImageComponent.js +67 -0
  12. package/lib/commonjs/components/ImageComponent.js.map +1 -0
  13. package/lib/commonjs/components/PushFrameComponent.js +179 -0
  14. package/lib/commonjs/components/PushFrameComponent.js.map +1 -0
  15. package/lib/commonjs/components/PushFrameProvider.js +115 -0
  16. package/lib/commonjs/components/PushFrameProvider.js.map +1 -0
  17. package/lib/commonjs/components/PushFrameScreen.js +39 -0
  18. package/lib/commonjs/components/PushFrameScreen.js.map +1 -0
  19. package/lib/commonjs/components/ScrollViewComponent.js +64 -0
  20. package/lib/commonjs/components/ScrollViewComponent.js.map +1 -0
  21. package/lib/commonjs/components/StackComponent.js +61 -0
  22. package/lib/commonjs/components/StackComponent.js.map +1 -0
  23. package/lib/commonjs/components/TextComponent.js +62 -0
  24. package/lib/commonjs/components/TextComponent.js.map +1 -0
  25. package/lib/commonjs/conditions.js +44 -0
  26. package/lib/commonjs/conditions.js.map +1 -0
  27. package/lib/commonjs/context/PushFrameContext.js +33 -0
  28. package/lib/commonjs/context/PushFrameContext.js.map +1 -0
  29. package/lib/commonjs/index.js +200 -0
  30. package/lib/commonjs/index.js.map +1 -0
  31. package/lib/commonjs/overlays/BottomSheetHost.js +144 -0
  32. package/lib/commonjs/overlays/BottomSheetHost.js.map +1 -0
  33. package/lib/commonjs/overlays/ToastHost.js +135 -0
  34. package/lib/commonjs/overlays/ToastHost.js.map +1 -0
  35. package/lib/commonjs/package.json +1 -0
  36. package/lib/commonjs/primitives/ActivityIndicator.js +24 -0
  37. package/lib/commonjs/primitives/ActivityIndicator.js.map +1 -0
  38. package/lib/commonjs/primitives/FlatList.js +34 -0
  39. package/lib/commonjs/primitives/FlatList.js.map +1 -0
  40. package/lib/commonjs/primitives/Image.js +33 -0
  41. package/lib/commonjs/primitives/Image.js.map +1 -0
  42. package/lib/commonjs/primitives/KeyboardAvoidingView.js +24 -0
  43. package/lib/commonjs/primitives/KeyboardAvoidingView.js.map +1 -0
  44. package/lib/commonjs/primitives/Modal.js +24 -0
  45. package/lib/commonjs/primitives/Modal.js.map +1 -0
  46. package/lib/commonjs/primitives/Pressable.js +26 -0
  47. package/lib/commonjs/primitives/Pressable.js.map +1 -0
  48. package/lib/commonjs/primitives/SafeAreaView.js +38 -0
  49. package/lib/commonjs/primitives/SafeAreaView.js.map +1 -0
  50. package/lib/commonjs/primitives/ScrollView.js +26 -0
  51. package/lib/commonjs/primitives/ScrollView.js.map +1 -0
  52. package/lib/commonjs/primitives/StatusBar.js +24 -0
  53. package/lib/commonjs/primitives/StatusBar.js.map +1 -0
  54. package/lib/commonjs/primitives/Switch.js +28 -0
  55. package/lib/commonjs/primitives/Switch.js.map +1 -0
  56. package/lib/commonjs/primitives/Text.js +37 -0
  57. package/lib/commonjs/primitives/Text.js.map +1 -0
  58. package/lib/commonjs/primitives/TextInput.js +31 -0
  59. package/lib/commonjs/primitives/TextInput.js.map +1 -0
  60. package/lib/commonjs/primitives/View.js +24 -0
  61. package/lib/commonjs/primitives/View.js.map +1 -0
  62. package/lib/commonjs/primitives/index.js +97 -0
  63. package/lib/commonjs/primitives/index.js.map +1 -0
  64. package/lib/commonjs/registry/ComponentRegistry.js +70 -0
  65. package/lib/commonjs/registry/ComponentRegistry.js.map +1 -0
  66. package/lib/commonjs/registry.js +94 -0
  67. package/lib/commonjs/registry.js.map +1 -0
  68. package/lib/commonjs/renderer/RecursiveRenderer.js +202 -0
  69. package/lib/commonjs/renderer/RecursiveRenderer.js.map +1 -0
  70. package/lib/commonjs/renderer/bindingResolver.js +98 -0
  71. package/lib/commonjs/renderer/bindingResolver.js.map +1 -0
  72. package/lib/commonjs/renderer/conditionalEvaluator.js +31 -0
  73. package/lib/commonjs/renderer/conditionalEvaluator.js.map +1 -0
  74. package/lib/commonjs/renderer.js +107 -0
  75. package/lib/commonjs/renderer.js.map +1 -0
  76. package/lib/commonjs/schema.js +79 -0
  77. package/lib/commonjs/schema.js.map +1 -0
  78. package/lib/commonjs/transformer/index.js +1055 -0
  79. package/lib/commonjs/transformer/index.js.map +1 -0
  80. package/lib/commonjs/transport.js +86 -0
  81. package/lib/commonjs/transport.js.map +1 -0
  82. package/lib/module/PushframeProvider.js +62 -0
  83. package/lib/module/PushframeProvider.js.map +1 -0
  84. package/lib/module/PushframeScreen.js +65 -0
  85. package/lib/module/PushframeScreen.js.map +1 -0
  86. package/lib/module/bindings.js +68 -0
  87. package/lib/module/bindings.js.map +1 -0
  88. package/lib/module/components/ButtonComponent.js +67 -0
  89. package/lib/module/components/ButtonComponent.js.map +1 -0
  90. package/lib/module/components/FlatListComponent.js +68 -0
  91. package/lib/module/components/FlatListComponent.js.map +1 -0
  92. package/lib/module/components/ImageComponent.js +62 -0
  93. package/lib/module/components/ImageComponent.js.map +1 -0
  94. package/lib/module/components/PushFrameComponent.js +174 -0
  95. package/lib/module/components/PushFrameComponent.js.map +1 -0
  96. package/lib/module/components/PushFrameProvider.js +110 -0
  97. package/lib/module/components/PushFrameProvider.js.map +1 -0
  98. package/lib/module/components/PushFrameScreen.js +34 -0
  99. package/lib/module/components/PushFrameScreen.js.map +1 -0
  100. package/lib/module/components/ScrollViewComponent.js +59 -0
  101. package/lib/module/components/ScrollViewComponent.js.map +1 -0
  102. package/lib/module/components/StackComponent.js +56 -0
  103. package/lib/module/components/StackComponent.js.map +1 -0
  104. package/lib/module/components/TextComponent.js +57 -0
  105. package/lib/module/components/TextComponent.js.map +1 -0
  106. package/lib/module/conditions.js +40 -0
  107. package/lib/module/conditions.js.map +1 -0
  108. package/lib/module/context/PushFrameContext.js +29 -0
  109. package/lib/module/context/PushFrameContext.js.map +1 -0
  110. package/lib/module/index.js +99 -0
  111. package/lib/module/index.js.map +1 -0
  112. package/lib/module/overlays/BottomSheetHost.js +139 -0
  113. package/lib/module/overlays/BottomSheetHost.js.map +1 -0
  114. package/lib/module/overlays/ToastHost.js +130 -0
  115. package/lib/module/overlays/ToastHost.js.map +1 -0
  116. package/lib/module/primitives/ActivityIndicator.js +19 -0
  117. package/lib/module/primitives/ActivityIndicator.js.map +1 -0
  118. package/lib/module/primitives/FlatList.js +29 -0
  119. package/lib/module/primitives/FlatList.js.map +1 -0
  120. package/lib/module/primitives/Image.js +28 -0
  121. package/lib/module/primitives/Image.js.map +1 -0
  122. package/lib/module/primitives/KeyboardAvoidingView.js +19 -0
  123. package/lib/module/primitives/KeyboardAvoidingView.js.map +1 -0
  124. package/lib/module/primitives/Modal.js +19 -0
  125. package/lib/module/primitives/Modal.js.map +1 -0
  126. package/lib/module/primitives/Pressable.js +21 -0
  127. package/lib/module/primitives/Pressable.js.map +1 -0
  128. package/lib/module/primitives/SafeAreaView.js +33 -0
  129. package/lib/module/primitives/SafeAreaView.js.map +1 -0
  130. package/lib/module/primitives/ScrollView.js +21 -0
  131. package/lib/module/primitives/ScrollView.js.map +1 -0
  132. package/lib/module/primitives/StatusBar.js +19 -0
  133. package/lib/module/primitives/StatusBar.js.map +1 -0
  134. package/lib/module/primitives/Switch.js +23 -0
  135. package/lib/module/primitives/Switch.js.map +1 -0
  136. package/lib/module/primitives/Text.js +32 -0
  137. package/lib/module/primitives/Text.js.map +1 -0
  138. package/lib/module/primitives/TextInput.js +26 -0
  139. package/lib/module/primitives/TextInput.js.map +1 -0
  140. package/lib/module/primitives/View.js +19 -0
  141. package/lib/module/primitives/View.js.map +1 -0
  142. package/lib/module/primitives/index.js +16 -0
  143. package/lib/module/primitives/index.js.map +1 -0
  144. package/lib/module/registry/ComponentRegistry.js +66 -0
  145. package/lib/module/registry/ComponentRegistry.js.map +1 -0
  146. package/lib/module/registry.js +88 -0
  147. package/lib/module/registry.js.map +1 -0
  148. package/lib/module/renderer/RecursiveRenderer.js +197 -0
  149. package/lib/module/renderer/RecursiveRenderer.js.map +1 -0
  150. package/lib/module/renderer/bindingResolver.js +92 -0
  151. package/lib/module/renderer/bindingResolver.js.map +1 -0
  152. package/lib/module/renderer/conditionalEvaluator.js +28 -0
  153. package/lib/module/renderer/conditionalEvaluator.js.map +1 -0
  154. package/lib/module/renderer.js +103 -0
  155. package/lib/module/renderer.js.map +1 -0
  156. package/lib/module/schema.js +74 -0
  157. package/lib/module/schema.js.map +1 -0
  158. package/lib/module/transformer/index.js +1051 -0
  159. package/lib/module/transformer/index.js.map +1 -0
  160. package/lib/module/transport.js +82 -0
  161. package/lib/module/transport.js.map +1 -0
  162. package/lib/typescript/PushframeProvider.d.ts +58 -0
  163. package/lib/typescript/PushframeProvider.d.ts.map +1 -0
  164. package/lib/typescript/PushframeScreen.d.ts +36 -0
  165. package/lib/typescript/PushframeScreen.d.ts.map +1 -0
  166. package/lib/typescript/bindings.d.ts +29 -0
  167. package/lib/typescript/bindings.d.ts.map +1 -0
  168. package/lib/typescript/components/ButtonComponent.d.ts +11 -0
  169. package/lib/typescript/components/ButtonComponent.d.ts.map +1 -0
  170. package/lib/typescript/components/FlatListComponent.d.ts +28 -0
  171. package/lib/typescript/components/FlatListComponent.d.ts.map +1 -0
  172. package/lib/typescript/components/ImageComponent.d.ts +12 -0
  173. package/lib/typescript/components/ImageComponent.d.ts.map +1 -0
  174. package/lib/typescript/components/PushFrameComponent.d.ts +48 -0
  175. package/lib/typescript/components/PushFrameComponent.d.ts.map +1 -0
  176. package/lib/typescript/components/PushFrameProvider.d.ts +51 -0
  177. package/lib/typescript/components/PushFrameProvider.d.ts.map +1 -0
  178. package/lib/typescript/components/PushFrameScreen.d.ts +15 -0
  179. package/lib/typescript/components/PushFrameScreen.d.ts.map +1 -0
  180. package/lib/typescript/components/ScrollViewComponent.d.ts +19 -0
  181. package/lib/typescript/components/ScrollViewComponent.d.ts.map +1 -0
  182. package/lib/typescript/components/StackComponent.d.ts +16 -0
  183. package/lib/typescript/components/StackComponent.d.ts.map +1 -0
  184. package/lib/typescript/components/TextComponent.d.ts +13 -0
  185. package/lib/typescript/components/TextComponent.d.ts.map +1 -0
  186. package/lib/typescript/conditions.d.ts +12 -0
  187. package/lib/typescript/conditions.d.ts.map +1 -0
  188. package/lib/typescript/context/PushFrameContext.d.ts +57 -0
  189. package/lib/typescript/context/PushFrameContext.d.ts.map +1 -0
  190. package/lib/typescript/index.d.ts +74 -0
  191. package/lib/typescript/index.d.ts.map +1 -0
  192. package/lib/typescript/overlays/BottomSheetHost.d.ts +21 -0
  193. package/lib/typescript/overlays/BottomSheetHost.d.ts.map +1 -0
  194. package/lib/typescript/overlays/ToastHost.d.ts +12 -0
  195. package/lib/typescript/overlays/ToastHost.d.ts.map +1 -0
  196. package/lib/typescript/primitives/ActivityIndicator.d.ts +12 -0
  197. package/lib/typescript/primitives/ActivityIndicator.d.ts.map +1 -0
  198. package/lib/typescript/primitives/FlatList.d.ts +29 -0
  199. package/lib/typescript/primitives/FlatList.d.ts.map +1 -0
  200. package/lib/typescript/primitives/Image.d.ts +20 -0
  201. package/lib/typescript/primitives/Image.d.ts.map +1 -0
  202. package/lib/typescript/primitives/KeyboardAvoidingView.d.ts +12 -0
  203. package/lib/typescript/primitives/KeyboardAvoidingView.d.ts.map +1 -0
  204. package/lib/typescript/primitives/Modal.d.ts +12 -0
  205. package/lib/typescript/primitives/Modal.d.ts.map +1 -0
  206. package/lib/typescript/primitives/Pressable.d.ts +14 -0
  207. package/lib/typescript/primitives/Pressable.d.ts.map +1 -0
  208. package/lib/typescript/primitives/SafeAreaView.d.ts +20 -0
  209. package/lib/typescript/primitives/SafeAreaView.d.ts.map +1 -0
  210. package/lib/typescript/primitives/ScrollView.d.ts +15 -0
  211. package/lib/typescript/primitives/ScrollView.d.ts.map +1 -0
  212. package/lib/typescript/primitives/StatusBar.d.ts +12 -0
  213. package/lib/typescript/primitives/StatusBar.d.ts.map +1 -0
  214. package/lib/typescript/primitives/Switch.d.ts +19 -0
  215. package/lib/typescript/primitives/Switch.d.ts.map +1 -0
  216. package/lib/typescript/primitives/Text.d.ts +25 -0
  217. package/lib/typescript/primitives/Text.d.ts.map +1 -0
  218. package/lib/typescript/primitives/TextInput.d.ts +25 -0
  219. package/lib/typescript/primitives/TextInput.d.ts.map +1 -0
  220. package/lib/typescript/primitives/View.d.ts +12 -0
  221. package/lib/typescript/primitives/View.d.ts.map +1 -0
  222. package/lib/typescript/primitives/index.d.ts +27 -0
  223. package/lib/typescript/primitives/index.d.ts.map +1 -0
  224. package/lib/typescript/registry/ComponentRegistry.d.ts +21 -0
  225. package/lib/typescript/registry/ComponentRegistry.d.ts.map +1 -0
  226. package/lib/typescript/registry.d.ts +57 -0
  227. package/lib/typescript/registry.d.ts.map +1 -0
  228. package/lib/typescript/renderer/RecursiveRenderer.d.ts +32 -0
  229. package/lib/typescript/renderer/RecursiveRenderer.d.ts.map +1 -0
  230. package/lib/typescript/renderer/bindingResolver.d.ts +26 -0
  231. package/lib/typescript/renderer/bindingResolver.d.ts.map +1 -0
  232. package/lib/typescript/renderer/conditionalEvaluator.d.ts +15 -0
  233. package/lib/typescript/renderer/conditionalEvaluator.d.ts.map +1 -0
  234. package/lib/typescript/renderer.d.ts +29 -0
  235. package/lib/typescript/renderer.d.ts.map +1 -0
  236. package/lib/typescript/schema.d.ts +84 -0
  237. package/lib/typescript/schema.d.ts.map +1 -0
  238. package/lib/typescript/transformer/index.d.ts +49 -0
  239. package/lib/typescript/transformer/index.d.ts.map +1 -0
  240. package/lib/typescript/transport.d.ts +19 -0
  241. package/lib/typescript/transport.d.ts.map +1 -0
  242. package/package.json +20 -18
  243. package/src/PushframeProvider.tsx +119 -0
  244. package/src/PushframeScreen.tsx +107 -0
  245. package/src/bindings.ts +72 -0
  246. package/src/components/ButtonComponent.tsx +87 -0
  247. package/src/components/FlatListComponent.tsx +86 -0
  248. package/src/components/ImageComponent.tsx +70 -0
  249. package/src/components/PushFrameComponent.tsx +221 -0
  250. package/src/components/PushFrameProvider.tsx +177 -0
  251. package/src/components/PushFrameScreen.tsx +30 -0
  252. package/src/components/ScrollViewComponent.tsx +65 -0
  253. package/src/components/StackComponent.tsx +69 -0
  254. package/src/components/TextComponent.tsx +60 -0
  255. package/src/conditions.ts +46 -0
  256. package/src/context/PushFrameContext.ts +89 -0
  257. package/src/index.ts +119 -0
  258. package/src/overlays/BottomSheetHost.tsx +175 -0
  259. package/src/overlays/ToastHost.tsx +147 -0
  260. package/src/primitives/ActivityIndicator.tsx +21 -0
  261. package/src/primitives/FlatList.tsx +49 -0
  262. package/src/primitives/Image.tsx +26 -0
  263. package/src/primitives/KeyboardAvoidingView.tsx +21 -0
  264. package/src/primitives/Modal.tsx +17 -0
  265. package/src/primitives/Pressable.tsx +19 -0
  266. package/src/primitives/SafeAreaView.tsx +42 -0
  267. package/src/primitives/ScrollView.tsx +21 -0
  268. package/src/primitives/StatusBar.tsx +17 -0
  269. package/src/primitives/Switch.tsx +24 -0
  270. package/src/primitives/Text.tsx +43 -0
  271. package/src/primitives/TextInput.tsx +42 -0
  272. package/src/primitives/View.tsx +17 -0
  273. package/src/primitives/index.ts +38 -0
  274. package/src/registry/ComponentRegistry.ts +99 -0
  275. package/src/registry.ts +99 -0
  276. package/src/renderer/RecursiveRenderer.tsx +242 -0
  277. package/src/renderer/bindingResolver.ts +94 -0
  278. package/src/renderer/conditionalEvaluator.ts +29 -0
  279. package/src/renderer.tsx +124 -0
  280. package/src/schema.ts +132 -0
  281. package/src/transformer/index.ts +1016 -0
  282. package/src/transport.ts +104 -0
  283. package/dist/index.d.mts +0 -534
  284. package/dist/index.d.ts +0 -534
  285. package/dist/index.js +0 -1572
  286. package/dist/index.js.map +0 -1
  287. package/dist/index.mjs +0 -1541
  288. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,99 @@
1
+ import type { ComponentType, ReactElement, ReactNode } from 'react';
2
+ import type { PushframeNode } from './schema';
3
+
4
+ /**
5
+ * Props passed to every Pushframe component.
6
+ *
7
+ * `props` — fully-resolved (all $bind references substituted) props map.
8
+ * `children` — pre-rendered child nodes (if any).
9
+ * `onAction` — callback for emitting an action event back to the host app.
10
+ * `rawChildren` — raw schema child nodes, available for data-driven components
11
+ * (e.g. FlatList) that need to render the same template
12
+ * multiple times with different context.
13
+ * `renderChild` — render a schema node with an optional context override that
14
+ * gets merged into the parent context. Use this together with
15
+ * `rawChildren` to implement per-item rendering.
16
+ */
17
+ export interface PushframeComponentProps<TProps = Record<string, unknown>> {
18
+ props: TProps;
19
+ children?: ReactNode;
20
+ onAction?: (eventName: string, payload: unknown) => void;
21
+ rawChildren?: readonly PushframeNode[];
22
+ renderChild?: (
23
+ node: PushframeNode,
24
+ contextOverride?: Record<string, unknown>,
25
+ ) => ReactElement | null;
26
+ }
27
+
28
+ export type PushframeComponent<TProps = Record<string, unknown>> =
29
+ ComponentType<PushframeComponentProps<TProps>>;
30
+
31
+ /**
32
+ * ComponentRegistry maps a component type string (e.g. "stack") to its
33
+ * React component implementation.
34
+ *
35
+ * Use `createRegistry()` to construct one, then call `register()` to add
36
+ * custom components on top.
37
+ */
38
+ export class ComponentRegistry {
39
+ private readonly components = new Map<string, PushframeComponent>();
40
+
41
+ /**
42
+ * Register a component under the given type string.
43
+ * Calling register() with an existing type overwrites the previous entry,
44
+ * allowing host apps to override built-ins.
45
+ */
46
+ register<TProps>(
47
+ type: string,
48
+ component: PushframeComponent<TProps>,
49
+ ): this {
50
+ this.components.set(type, component as PushframeComponent);
51
+ return this;
52
+ }
53
+
54
+ /**
55
+ * Look up a component by type string.
56
+ * Returns undefined if no component is registered for that type.
57
+ */
58
+ get(type: string): PushframeComponent | undefined {
59
+ return this.components.get(type);
60
+ }
61
+
62
+ /**
63
+ * Returns true if the registry contains an entry for the given type.
64
+ */
65
+ has(type: string): boolean {
66
+ return this.components.has(type);
67
+ }
68
+
69
+ /**
70
+ * Returns all registered type strings (useful for debugging).
71
+ */
72
+ types(): string[] {
73
+ return Array.from(this.components.keys());
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Build the default registry pre-loaded with all built-in components.
79
+ */
80
+ export function createDefaultRegistry(): ComponentRegistry {
81
+ // Lazy-import to keep tree-shaking effective when consumers only need
82
+ // a subset of built-ins.
83
+ const { StackComponent } = require('./components/StackComponent');
84
+ const { TextComponent } = require('./components/TextComponent');
85
+ const { ButtonComponent } = require('./components/ButtonComponent');
86
+ const { ImageComponent } = require('./components/ImageComponent');
87
+ const { ScrollViewComponent } = require('./components/ScrollViewComponent');
88
+ const { FlatListComponent } = require('./components/FlatListComponent');
89
+
90
+ return new ComponentRegistry()
91
+ .register('stack', StackComponent)
92
+ // 'screen' is a common server-side root container alias for stack
93
+ .register('screen', StackComponent)
94
+ .register('text', TextComponent)
95
+ .register('button', ButtonComponent)
96
+ .register('image', ImageComponent)
97
+ .register('scrollview', ScrollViewComponent)
98
+ .register('flatlist', FlatListComponent);
99
+ }
@@ -0,0 +1,242 @@
1
+ import React from 'react';
2
+ import type { ReactElement } from 'react';
3
+ import type { SchemaNode, FlatListSchemaNode, Action } from '../context/PushFrameContext';
4
+ import type { ComponentRegistry } from '../registry/ComponentRegistry';
5
+ import { resolveProps, resolveDeep, resolveValue } from './bindingResolver';
6
+ import { evaluateIf } from './conditionalEvaluator';
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Types
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export interface DispatchAction {
13
+ (actionName: string, payload?: Record<string, unknown>): void;
14
+ }
15
+
16
+ export interface RecursiveRendererProps {
17
+ node: SchemaNode;
18
+ /** Current merged data context for binding resolution. */
19
+ context: Record<string, unknown>;
20
+ registry: ComponentRegistry;
21
+ /** Handles all actions — built-ins and host-app bubbling. Provided by the slot component. */
22
+ dispatchAction: DispatchAction;
23
+ /** Shown when a component type is not found in the registry. */
24
+ fallbackComponent?: React.ReactNode;
25
+ }
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Action wiring helpers
29
+ // ---------------------------------------------------------------------------
30
+
31
+ /**
32
+ * Group an actions array by trigger name and produce a prop map where each
33
+ * trigger key maps to a callback that fires all actions for that trigger.
34
+ */
35
+ function buildActionProps(
36
+ actions: Action[],
37
+ context: Record<string, unknown>,
38
+ dispatchAction: DispatchAction,
39
+ ): Record<string, (...args: unknown[]) => void> {
40
+ // Group by trigger
41
+ const grouped = new Map<string, Action[]>();
42
+ for (const action of actions) {
43
+ const existing = grouped.get(action.trigger);
44
+ if (existing) {
45
+ existing.push(action);
46
+ } else {
47
+ grouped.set(action.trigger, [action]);
48
+ }
49
+ }
50
+
51
+ const result: Record<string, (...args: unknown[]) => void> = {};
52
+ for (const [trigger, triggerActions] of grouped) {
53
+ result[trigger] = (..._args: unknown[]) => {
54
+ for (const action of triggerActions) {
55
+ const resolvedPayload =
56
+ action.payload !== undefined
57
+ ? (resolveDeep(action.payload, context) as Record<string, unknown>)
58
+ : undefined;
59
+ dispatchAction(action.action, resolvedPayload);
60
+ }
61
+ };
62
+ }
63
+ return result;
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // FlatList special handling
68
+ // ---------------------------------------------------------------------------
69
+
70
+ function renderFlatListNode(
71
+ node: FlatListSchemaNode,
72
+ context: Record<string, unknown>,
73
+ registry: ComponentRegistry,
74
+ dispatchAction: DispatchAction,
75
+ fallbackComponent: React.ReactNode,
76
+ ): ReactElement | null {
77
+ // Resolve all props including the data/items binding
78
+ const resolvedProps = resolveProps(node.props, context);
79
+
80
+ // Build action props for the FlatList container itself
81
+ const actionProps = node.actions ? buildActionProps(node.actions, context, dispatchAction) : {};
82
+
83
+ // Support both `data` (transformer output) and `items` (legacy) for the array source.
84
+ // Extract and resolve; the primitive receives it as `items`.
85
+ const {
86
+ data: resolvedData,
87
+ items: resolvedItems,
88
+ keyExtractor: keyExtractorExpr,
89
+ direction,
90
+ numColumns,
91
+ visible: _visible,
92
+ _propValues: _pv,
93
+ ...restProps
94
+ } = resolvedProps;
95
+
96
+ const items = Array.isArray(resolvedData)
97
+ ? (resolvedData as unknown[])
98
+ : Array.isArray(resolvedItems)
99
+ ? (resolvedItems as unknown[])
100
+ : [];
101
+
102
+ // Build a keyExtractor function if specified in schema
103
+ let keyExtractorFn: ((item: unknown, index: number) => string) | undefined;
104
+ if (typeof keyExtractorExpr === 'string') {
105
+ const extractorExpr = keyExtractorExpr;
106
+ keyExtractorFn = (item: unknown, index: number) => {
107
+ const itemContext = { ...context, item, index };
108
+ const resolved = resolveValue(extractorExpr, itemContext);
109
+ return resolved !== undefined && resolved !== null ? String(resolved) : String(index);
110
+ };
111
+ }
112
+
113
+ // itemTemplate: per-item context merges { item, index } into current context
114
+ const renderItemFn = ({ item, index }: { item: unknown; index: number }): ReactElement | null => {
115
+ const itemContext = { ...context, item, index };
116
+ return (
117
+ <RecursiveRenderer
118
+ node={node.itemTemplate}
119
+ context={itemContext}
120
+ registry={registry}
121
+ dispatchAction={dispatchAction}
122
+ fallbackComponent={fallbackComponent}
123
+ key={keyExtractorFn ? keyExtractorFn(item, index) : index}
124
+ />
125
+ );
126
+ };
127
+
128
+ const FlatListComponent = registry.resolve('flatlist');
129
+ if (!FlatListComponent) {
130
+ console.warn('[PushFrame] RecursiveRenderer: "flatlist" not found in registry.');
131
+ return fallbackComponent ? (fallbackComponent as ReactElement) : null;
132
+ }
133
+
134
+ return React.createElement(FlatListComponent, {
135
+ ...restProps,
136
+ ...actionProps,
137
+ items,
138
+ direction,
139
+ numColumns,
140
+ keyExtractor: keyExtractorFn,
141
+ renderItem: renderItemFn,
142
+ });
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // RecursiveRenderer
147
+ // ---------------------------------------------------------------------------
148
+
149
+ /**
150
+ * Stateless recursive renderer. Evaluates schema nodes and produces React elements.
151
+ *
152
+ * - Evaluates `if` expression (string or non-string); returns null when falsy
153
+ * - Handles `visible` prop: returns null when visible resolves to false
154
+ * - Handles FlatList nodes with per-item context injection via `itemTemplate`
155
+ * - Supports `data` (transformer) and `items` (legacy) as FlatList data source
156
+ * - Resolves all prop bindings against current context
157
+ * - Merges `_propValues` into child context (custom component prop injection)
158
+ * - Wires actions to component event handler props
159
+ * - Looks up component in registry; renders fallback or null when not found
160
+ * - Recurses into children
161
+ */
162
+ export function RecursiveRenderer({
163
+ node,
164
+ context,
165
+ registry,
166
+ dispatchAction,
167
+ fallbackComponent,
168
+ }: RecursiveRendererProps): ReactElement | null {
169
+ // 1. Evaluate conditional — skip entire subtree if falsy
170
+ if (!evaluateIf(node.if, context)) {
171
+ return null;
172
+ }
173
+
174
+ // 2. FlatList special case
175
+ if (node.type === 'flatlist') {
176
+ return renderFlatListNode(
177
+ node as FlatListSchemaNode,
178
+ context,
179
+ registry,
180
+ dispatchAction,
181
+ fallbackComponent,
182
+ );
183
+ }
184
+
185
+ // 3. Resolve props
186
+ const resolvedProps = resolveProps(node.props, context);
187
+
188
+ // 4. Handle `visible` prop — transformer sets visible:false when a node is hidden.
189
+ // A missing or true value means visible; anything falsy hides the node.
190
+ if (resolvedProps.visible !== undefined && !resolvedProps.visible) {
191
+ return null;
192
+ }
193
+
194
+ // 5. Extract SDK-internal props that must not be forwarded to RN components.
195
+ // `_propValues` carries custom-component instance overrides → merge into
196
+ // child context so nested bindings resolve against them.
197
+ const {
198
+ visible: _visible,
199
+ _propValues: propValuesRaw,
200
+ ...propsToForward
201
+ } = resolvedProps;
202
+
203
+ const propValues = propValuesRaw as Record<string, unknown> | undefined;
204
+ const childContext =
205
+ propValues && Object.keys(propValues).length > 0
206
+ ? { ...context, ...propValues }
207
+ : context;
208
+
209
+ // 6. Wire actions → inject as trigger-named props
210
+ const actionProps = node.actions ? buildActionProps(node.actions, context, dispatchAction) : {};
211
+
212
+ // 7. Resolve component from registry
213
+ const Component = registry.resolve(node.type);
214
+ if (!Component) {
215
+ console.warn(
216
+ `[PushFrame] RecursiveRenderer: Unknown component type "${node.type}". ` +
217
+ `Registered types: ${registry.types().join(', ')}`,
218
+ );
219
+ return fallbackComponent ? (fallbackComponent as ReactElement) : null;
220
+ }
221
+
222
+ // 8. Render children recursively (using childContext for _propValues injection)
223
+ const children =
224
+ node.children && node.children.length > 0
225
+ ? node.children.map((child, index) => (
226
+ <RecursiveRenderer
227
+ key={`${child.type}-${index}`}
228
+ node={child}
229
+ context={childContext}
230
+ registry={registry}
231
+ dispatchAction={dispatchAction}
232
+ fallbackComponent={fallbackComponent}
233
+ />
234
+ ))
235
+ : undefined;
236
+
237
+ return React.createElement(Component, {
238
+ ...propsToForward,
239
+ ...actionProps,
240
+ children,
241
+ });
242
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Binding resolver for PushFrame {{expression}} syntax.
3
+ *
4
+ * Rules:
5
+ * - A value that is entirely "{{expr}}" resolves to the bound value (any type)
6
+ * - A value with inline bindings "Hello {{user.name}}" resolves to a string
7
+ * - Unresolved paths return undefined — never throws
8
+ * - Non-string values are returned as-is
9
+ */
10
+
11
+ const FULL_BINDING_RE = /^\{\{(.+)\}\}$/;
12
+ const INLINE_BINDING_RE = /\{\{([^}]+)\}\}/g;
13
+
14
+ /**
15
+ * Evaluate a binding expression string against the provided context.
16
+ * Uses Function constructor for safe, sandboxed expression evaluation.
17
+ * Returns undefined on any error.
18
+ */
19
+ function evaluateExpression(expr: string, context: Record<string, unknown>): unknown {
20
+ try {
21
+ const keys = Object.keys(context);
22
+ const values = keys.map((k) => context[k]);
23
+ // eslint-disable-next-line no-new-func
24
+ const fn = new Function(...keys, `"use strict"; try { return (${expr}); } catch(e) { return undefined; }`) as (
25
+ ...args: unknown[]
26
+ ) => unknown;
27
+ return fn(...values);
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Resolve a single prop value against context.
35
+ *
36
+ * - String values: check for full binding "{{expr}}" or inline bindings "Hello {{name}}"
37
+ * - Non-string values: returned as-is
38
+ */
39
+ export function resolveValue(value: unknown, context: Record<string, unknown>): unknown {
40
+ if (typeof value !== 'string') {
41
+ return value;
42
+ }
43
+
44
+ // Full binding: entire string is "{{expr}}" — return the actual resolved value
45
+ const fullMatch = FULL_BINDING_RE.exec(value);
46
+ if (fullMatch?.[1] !== undefined) {
47
+ return evaluateExpression(fullMatch[1].trim(), context);
48
+ }
49
+
50
+ // Inline binding: replace each "{{expr}}" occurrence within the string
51
+ if (INLINE_BINDING_RE.test(value)) {
52
+ INLINE_BINDING_RE.lastIndex = 0;
53
+ return value.replace(INLINE_BINDING_RE, (_, expr: string) => {
54
+ const resolved = evaluateExpression(expr.trim(), context);
55
+ return resolved !== undefined && resolved !== null ? String(resolved) : '';
56
+ });
57
+ }
58
+
59
+ // No bindings — return as-is
60
+ return value;
61
+ }
62
+
63
+ /**
64
+ * Resolve all values in a props map against the context.
65
+ * Recursively resolves nested objects and arrays.
66
+ */
67
+ export function resolveProps(
68
+ props: Record<string, unknown> | undefined,
69
+ context: Record<string, unknown>,
70
+ ): Record<string, unknown> {
71
+ if (!props) return {};
72
+ const resolved: Record<string, unknown> = {};
73
+ for (const [key, value] of Object.entries(props)) {
74
+ resolved[key] = resolveDeep(value, context);
75
+ }
76
+ return resolved;
77
+ }
78
+
79
+ /**
80
+ * Recursively resolve binding expressions in a value (including nested objects/arrays).
81
+ */
82
+ export function resolveDeep(value: unknown, context: Record<string, unknown>): unknown {
83
+ if (Array.isArray(value)) {
84
+ return value.map((item) => resolveDeep(item, context));
85
+ }
86
+ if (typeof value === 'object' && value !== null) {
87
+ const result: Record<string, unknown> = {};
88
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
89
+ result[k] = resolveDeep(v, context);
90
+ }
91
+ return result;
92
+ }
93
+ return resolveValue(value, context);
94
+ }
@@ -0,0 +1,29 @@
1
+ import { resolveValue } from './bindingResolver';
2
+
3
+ /**
4
+ * Evaluate the `if` expression of a SchemaNode against the current context.
5
+ *
6
+ * Returns true → node should render
7
+ * Returns false → node (and its entire subtree) should be skipped
8
+ *
9
+ * Falsy values: false, null, undefined, 0, "" all hide the node.
10
+ * When `ifExpr` is undefined/null the node always renders.
11
+ *
12
+ * Accepts `unknown` to match the transformer's `if?: unknown` output:
13
+ * - String values are resolved as {{}} binding expressions
14
+ * - Non-string values (boolean, number, null) are coerced directly
15
+ */
16
+ export function evaluateIf(
17
+ ifExpr: unknown,
18
+ context: Record<string, unknown>,
19
+ ): boolean {
20
+ if (ifExpr === undefined || ifExpr === null) return true;
21
+
22
+ // Non-string: treat the value itself as the condition (e.g. `if: false`)
23
+ if (typeof ifExpr !== 'string') return Boolean(ifExpr);
24
+
25
+ const resolved = resolveValue(ifExpr, context);
26
+
27
+ // Standard falsy check — any falsy value hides the node
28
+ return Boolean(resolved);
29
+ }
@@ -0,0 +1,124 @@
1
+ import React from 'react';
2
+ import type { ReactElement } from 'react';
3
+ import type { PushframeNode, ActionsMap } from './schema';
4
+ import type { ComponentRegistry } from './registry';
5
+ import type { PushframeContext } from './bindings';
6
+ import { resolveProps } from './bindings';
7
+ import { evaluateCondition } from './conditions';
8
+ import type { PushframeAction } from './schema';
9
+
10
+ /**
11
+ * Called by the host app whenever an action is triggered.
12
+ * `eventName` is the prop key (e.g. "onPress").
13
+ * `action` is the resolved action descriptor from the schema.
14
+ */
15
+ export type ActionHandler = (eventName: string, action: PushframeAction) => void;
16
+
17
+ interface RenderNodeOptions {
18
+ node: PushframeNode;
19
+ context: PushframeContext;
20
+ registry: ComponentRegistry;
21
+ onAction?: ActionHandler;
22
+ /** Key to help React reconcile siblings. */
23
+ key?: string;
24
+ }
25
+
26
+ /**
27
+ * Recursively render a single PushframeNode.
28
+ *
29
+ * Returns null when:
30
+ * - The node's condition evaluates to false
31
+ * - The node's type is not found in the registry
32
+ */
33
+ export function renderNode({
34
+ node,
35
+ context,
36
+ registry,
37
+ onAction,
38
+ key,
39
+ }: RenderNodeOptions): ReactElement | null {
40
+ // 1. Evaluate conditions — skip if false.
41
+ if (!evaluateCondition(node.conditions, context)) {
42
+ return null;
43
+ }
44
+
45
+ // 2. Look up the component.
46
+ const Component = registry.get(node.type);
47
+ if (!Component) {
48
+ console.warn(
49
+ `[Pushframe] No component registered for type "${node.type}". ` +
50
+ `Registered types: ${registry.types().join(', ')}`,
51
+ );
52
+ return null;
53
+ }
54
+
55
+ // 3. Resolve props.
56
+ const resolvedProps = resolveProps(node.props, context);
57
+
58
+ // 4. Build action callbacks and merge into props.
59
+ if (node.actions) {
60
+ mergeActionCallbacks(resolvedProps, node.actions, onAction);
61
+ }
62
+
63
+ // 5. Render children recursively.
64
+ const childOptions = onAction
65
+ ? { context, registry, onAction }
66
+ : { context, registry };
67
+
68
+ const children =
69
+ node.children && node.children.length > 0
70
+ ? node.children.map((child, index) =>
71
+ renderNode({
72
+ node: child,
73
+ key: `${child.type}-${index}`,
74
+ ...childOptions,
75
+ }),
76
+ )
77
+ : undefined;
78
+
79
+ // Helper for data-driven components (e.g. FlatList) to render a template
80
+ // node multiple times with different context (e.g. one render per list item).
81
+ const renderChild = (
82
+ childNode: PushframeNode,
83
+ contextOverride?: Record<string, unknown>,
84
+ ) =>
85
+ renderNode({
86
+ node: childNode,
87
+ context: contextOverride ? { ...context, ...contextOverride } : context,
88
+ registry,
89
+ onAction,
90
+ });
91
+
92
+ // 6. Build component props — only include onAction when defined
93
+ // (required by exactOptionalPropertyTypes).
94
+ const componentProps = {
95
+ props: resolvedProps as Record<string, unknown>,
96
+ children,
97
+ rawChildren: node.children,
98
+ renderChild,
99
+ };
100
+
101
+ return React.createElement(Component, { key, ...componentProps });
102
+ }
103
+
104
+ /**
105
+ * For each entry in the actions map, inject a callback function into resolvedProps
106
+ * so the component can call e.g. `props.onPress()` without coupling to the schema.
107
+ */
108
+ function mergeActionCallbacks(
109
+ resolvedProps: Record<string, unknown>,
110
+ actions: ActionsMap,
111
+ onAction?: ActionHandler,
112
+ ): void {
113
+ for (const [eventName, action] of Object.entries(actions)) {
114
+ resolvedProps[eventName] = () => {
115
+ if (onAction) {
116
+ onAction(eventName, action);
117
+ } else {
118
+ console.warn(
119
+ `[Pushframe] Action "${eventName}" fired but no onAction handler was provided.`,
120
+ );
121
+ }
122
+ };
123
+ }
124
+ }