@symbiote-native/angular 0.6.1 → 0.7.0

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 (206) hide show
  1. package/README.md +1 -1
  2. package/bin/symbiote-angular-dev.cjs +20 -19
  3. package/build/angular/anchor-host-registry.js +2 -2
  4. package/build/angular/anchor-host-registry.js.map +1 -1
  5. package/build/angular/components/activity-indicator/index.android.d.ts +2 -1
  6. package/build/angular/components/activity-indicator/index.android.js +6 -3
  7. package/build/angular/components/activity-indicator/index.android.js.map +1 -1
  8. package/build/angular/components/activity-indicator/index.ios.d.ts +2 -1
  9. package/build/angular/components/activity-indicator/index.ios.js +6 -3
  10. package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
  11. package/build/angular/components/activity-indicator/shared.js +3 -3
  12. package/build/angular/components/button.d.ts +4 -2
  13. package/build/angular/components/button.js +28 -17
  14. package/build/angular/components/button.js.map +1 -1
  15. package/build/angular/components/flat-list/index.d.ts +5 -3
  16. package/build/angular/components/flat-list/index.js +35 -38
  17. package/build/angular/components/flat-list/index.js.map +1 -1
  18. package/build/angular/components/image/index.android.d.ts +8 -3
  19. package/build/angular/components/image/index.android.js +39 -9
  20. package/build/angular/components/image/index.android.js.map +1 -1
  21. package/build/angular/components/image/index.ios.d.ts +8 -3
  22. package/build/angular/components/image/index.ios.js +39 -9
  23. package/build/angular/components/image/index.ios.js.map +1 -1
  24. package/build/angular/components/image/shared.d.ts +4 -1
  25. package/build/angular/components/image/shared.js +33 -2
  26. package/build/angular/components/image/shared.js.map +1 -1
  27. package/build/angular/components/image-background.d.ts +2 -1
  28. package/build/angular/components/image-background.js +6 -4
  29. package/build/angular/components/image-background.js.map +1 -1
  30. package/build/angular/components/input-accessory-view/index.d.ts +9 -4
  31. package/build/angular/components/input-accessory-view/index.js +43 -9
  32. package/build/angular/components/input-accessory-view/index.js.map +1 -1
  33. package/build/angular/components/keyboard-avoiding-view/index.d.ts +2 -1
  34. package/build/angular/components/keyboard-avoiding-view/index.js +6 -4
  35. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  36. package/build/angular/components/modal/index.d.ts +11 -8
  37. package/build/angular/components/modal/index.js +45 -28
  38. package/build/angular/components/modal/index.js.map +1 -1
  39. package/build/angular/components/pressable/index.d.ts +11 -5
  40. package/build/angular/components/pressable/index.js +61 -12
  41. package/build/angular/components/pressable/index.js.map +1 -1
  42. package/build/angular/components/refresh-control/index.d.ts +8 -4
  43. package/build/angular/components/refresh-control/index.js +52 -16
  44. package/build/angular/components/refresh-control/index.js.map +1 -1
  45. package/build/angular/components/safe-area-view/index.d.ts +9 -4
  46. package/build/angular/components/safe-area-view/index.js +46 -9
  47. package/build/angular/components/safe-area-view/index.js.map +1 -1
  48. package/build/angular/components/scroll-view/index.android.d.ts +2 -3
  49. package/build/angular/components/scroll-view/index.android.js +49 -52
  50. package/build/angular/components/scroll-view/index.android.js.map +1 -1
  51. package/build/angular/components/scroll-view/index.ios.d.ts +2 -3
  52. package/build/angular/components/scroll-view/index.ios.js +15 -17
  53. package/build/angular/components/scroll-view/index.ios.js.map +1 -1
  54. package/build/angular/components/scroll-view/projection.d.ts +3 -0
  55. package/build/angular/components/scroll-view/projection.js +68 -7
  56. package/build/angular/components/scroll-view/projection.js.map +1 -1
  57. package/build/angular/components/scroll-view/shared.d.ts +14 -5
  58. package/build/angular/components/scroll-view/shared.js +198 -69
  59. package/build/angular/components/scroll-view/shared.js.map +1 -1
  60. package/build/angular/components/scroll-view/sticky-header.js +14 -19
  61. package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
  62. package/build/angular/components/section-list/index.d.ts +2 -1
  63. package/build/angular/components/section-list/index.js +31 -39
  64. package/build/angular/components/section-list/index.js.map +1 -1
  65. package/build/angular/components/switch/index.android.d.ts +7 -3
  66. package/build/angular/components/switch/index.android.js +38 -10
  67. package/build/angular/components/switch/index.android.js.map +1 -1
  68. package/build/angular/components/switch/index.ios.d.ts +7 -3
  69. package/build/angular/components/switch/index.ios.js +38 -10
  70. package/build/angular/components/switch/index.ios.js.map +1 -1
  71. package/build/angular/components/switch/shared.d.ts +12 -3
  72. package/build/angular/components/switch/shared.js +66 -5
  73. package/build/angular/components/switch/shared.js.map +1 -1
  74. package/build/angular/components/text-input.d.ts +10 -2
  75. package/build/angular/components/text-input.js +46 -6
  76. package/build/angular/components/text-input.js.map +1 -1
  77. package/build/angular/components/touchable/index.d.ts +17 -8
  78. package/build/angular/components/touchable/index.js +78 -19
  79. package/build/angular/components/touchable/index.js.map +1 -1
  80. package/build/angular/components/touchable-native-feedback/index.d.ts +6 -3
  81. package/build/angular/components/touchable-native-feedback/index.js +61 -25
  82. package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
  83. package/build/angular/components/virtualized-list/directives.js +18 -18
  84. package/build/angular/components/virtualized-list/index.d.ts +12 -4
  85. package/build/angular/components/virtualized-list/index.js +163 -68
  86. package/build/angular/components/virtualized-list/index.js.map +1 -1
  87. package/build/angular/components/virtualized-section-list/directives.js +12 -12
  88. package/build/angular/components/virtualized-section-list/index.d.ts +2 -1
  89. package/build/angular/components/virtualized-section-list/index.js +6 -4
  90. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  91. package/build/angular/components.d.ts +1 -1
  92. package/build/angular/components.js +5 -0
  93. package/build/angular/components.js.map +1 -1
  94. package/build/angular/create-portal/index.js +29 -35
  95. package/build/angular/create-portal/index.js.map +1 -1
  96. package/build/angular/create-tunnel/index.js +19 -22
  97. package/build/angular/create-tunnel/index.js.map +1 -1
  98. package/build/angular/descriptor-to-angular/index.js +3 -3
  99. package/build/angular/index.d.ts +4 -2
  100. package/build/angular/index.js +2 -2
  101. package/build/angular/index.js.map +1 -1
  102. package/build/angular/modules/animated/animated-leaf-binder.d.ts +1 -7
  103. package/build/angular/modules/animated/animated-leaf-binder.js +22 -72
  104. package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
  105. package/build/angular/modules/animated/create-animated-component.d.ts +11 -6
  106. package/build/angular/modules/animated/create-animated-component.js +83 -50
  107. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  108. package/build/angular/modules/app-registry/index.js.map +1 -1
  109. package/build/angular/modules/status-bar/index.js +3 -3
  110. package/build/angular/primitives/index.d.ts +1 -1
  111. package/build/angular/primitives/index.js +46 -46
  112. package/build/angular/primitives/index.js.map +1 -1
  113. package/build/angular/primitives/shared.d.ts +39 -1
  114. package/build/angular/primitives/shared.js +110 -9
  115. package/build/angular/primitives/shared.js.map +1 -1
  116. package/build/angular/render/index.js +23 -35
  117. package/build/angular/render/index.js.map +1 -1
  118. package/build/angular/services/color-scheme.service.js +3 -3
  119. package/build/angular/services/window-dimensions.service.js +3 -3
  120. package/package.json +13 -8
  121. package/src/anchor-host-registry.ts +2 -2
  122. package/src/components/activity-indicator/index.android.ts +2 -0
  123. package/src/components/activity-indicator/index.ios.ts +2 -0
  124. package/src/components/button.ts +28 -14
  125. package/src/components/flat-list/index.ts +34 -36
  126. package/src/components/image/index.android.ts +50 -6
  127. package/src/components/image/index.ios.ts +50 -6
  128. package/src/components/image/shared.ts +34 -2
  129. package/src/components/image-background.ts +2 -1
  130. package/src/components/input-accessory-view/index.ts +42 -4
  131. package/src/components/keyboard-avoiding-view/index.ts +7 -1
  132. package/src/components/modal/index.ts +51 -26
  133. package/src/components/pressable/index.ts +67 -8
  134. package/src/components/refresh-control/index.ts +54 -12
  135. package/src/components/safe-area-view/index.ts +50 -5
  136. package/src/components/scroll-view/index.android.ts +34 -41
  137. package/src/components/scroll-view/index.ios.ts +10 -21
  138. package/src/components/scroll-view/projection.ts +79 -7
  139. package/src/components/scroll-view/shared.ts +223 -68
  140. package/src/components/scroll-view/sticky-header.ts +11 -16
  141. package/src/components/section-list/index.ts +27 -36
  142. package/src/components/switch/index.android.ts +48 -7
  143. package/src/components/switch/index.ios.ts +48 -7
  144. package/src/components/switch/shared.ts +79 -3
  145. package/src/components/text-input.ts +52 -2
  146. package/src/components/touchable/index.ts +76 -16
  147. package/src/components/touchable-native-feedback/index.ts +62 -21
  148. package/src/components/virtualized-list/index.ts +137 -44
  149. package/src/components/virtualized-section-list/index.ts +2 -1
  150. package/src/components.ts +5 -0
  151. package/src/create-portal/index.ts +23 -29
  152. package/src/create-tunnel/index.ts +13 -16
  153. package/src/index.ts +30 -1
  154. package/src/modules/animated/animated-leaf-binder.ts +28 -77
  155. package/src/modules/animated/create-animated-component.ts +75 -36
  156. package/src/modules/app-registry/index.ts +6 -8
  157. package/src/primitives/index.ts +1 -0
  158. package/src/primitives/shared.ts +102 -3
  159. package/src/render/index.ts +23 -35
  160. package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +0 -14
  161. package/build/angular/modules/animated/animated-leaf-binder/index.js +0 -91
  162. package/build/angular/modules/animated/animated-leaf-binder/index.js.map +0 -1
  163. package/src/__tests__/responder-change-detection.test.ts +0 -102
  164. package/src/__tests__/responder-nested-cd.test.ts +0 -113
  165. package/src/angular-gaps.test.ts +0 -142
  166. package/src/angular-imports.test.ts +0 -100
  167. package/src/components/activity-indicator/activity-indicator-class-style.test.ts +0 -44
  168. package/src/components/activity-indicator/activity-indicator.test.ts +0 -131
  169. package/src/components/flat-list/column-wrapper-style-class.test.ts +0 -116
  170. package/src/components/flat-list/flat-list-array-style.test.ts +0 -131
  171. package/src/components/flat-list/flat-list-class-style.test.ts +0 -74
  172. package/src/components/flat-list/flat-list-scroll-containment.test.ts +0 -107
  173. package/src/components/flat-list/flat-list.test.ts +0 -99
  174. package/src/components/image/image-class-style.test.ts +0 -46
  175. package/src/components/image-background-class-style.test.ts +0 -48
  176. package/src/components/image-background-style-class.test.ts +0 -79
  177. package/src/components/input-accessory-view/input-accessory-view.test.ts +0 -43
  178. package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +0 -54
  179. package/src/components/modal/modal.test.ts +0 -43
  180. package/src/components/pressable/pressable.test.ts +0 -85
  181. package/src/components/refresh-control/refresh-control.test.ts +0 -41
  182. package/src/components/safe-area-view/safe-area-view.test.ts +0 -43
  183. package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +0 -123
  184. package/src/components/scroll-view/content-container-style-class.test.ts +0 -75
  185. package/src/components/scroll-view/scroll-view-class-style.test.ts +0 -134
  186. package/src/components/scroll-view/scroll-view-projection.test.ts +0 -194
  187. package/src/components/switch/android-switch-class.test.ts +0 -42
  188. package/src/components/switch/switch.test.ts +0 -92
  189. package/src/components/text-input-class-style.test.ts +0 -54
  190. package/src/components/touchable/touchable.test.ts +0 -102
  191. package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +0 -49
  192. package/src/components/virtualized-list/virtualized-list-class-style.test.ts +0 -76
  193. package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +0 -60
  194. package/src/create-portal/create-portal.test.ts +0 -90
  195. package/src/create-tunnel/create-tunnel.test.ts +0 -120
  196. package/src/descriptor-to-angular/descriptor-outlet.test.ts +0 -94
  197. package/src/host-instance/host-instance.test.ts +0 -49
  198. package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +0 -116
  199. package/src/modules/animated/animated-leaf-binder/index.ts +0 -101
  200. package/src/modules/animated/animated-leaf-binder.test.ts +0 -116
  201. package/src/modules/animated/animated-scroll-view.test.ts +0 -102
  202. package/src/modules/app-registry/app-registry.test.ts +0 -106
  203. package/src/modules/status-bar/status-bar.test.ts +0 -50
  204. package/src/render/render.test.ts +0 -147
  205. package/src/renderer/renderer.test.ts +0 -203
  206. package/src/services/services.test.ts +0 -163
@@ -1,91 +0,0 @@
1
- // Pure Fabrication (GRASP): the leaf-lifecycle orchestration shared by every Animated*
2
- // wrapper, extracted out of AnimatedComponentBase so AnimatedImage — which cannot extend
3
- // AnimatedComponentBase, since it must extend ImageBase for TS's-native image handling —
4
- // gets the SAME logic via composition instead of copy-pasting it. Framework-thin on
5
- // purpose: it knows nothing about Angular (@ViewChild, DI, change detection), only the
6
- // engine's Animated leaf + commit primitives, so it stays testable without any Angular
7
- // bootstrap. NOTE: react/vue drive this same dance through their own runtime HOC
8
- // (createAnimatedComponent) rather than a class, so there is no cross-adapter dedup
9
- // opportunity here yet — this Pure Fabrication is local to the angular adapter.
10
- import { AnimatedProps, attachNativeEventHandler, dlog, whenCommitted, } from '@symbiote-native/engine';
11
- export class AnimatedLeafBinder {
12
- resolveNode;
13
- label;
14
- // The leaf currently wired into the value graph. Null until the first reconcile.
15
- attached = null;
16
- // The whenCommitted waiter for the current leaf's node binding, cancelled before the next.
17
- cancelBind;
18
- // Detachers for any Animated.event prop bound natively to the committed node (JS path: none).
19
- eventDetachers = [];
20
- // `resolveNode` stays a caller-supplied resolver (not a constructor-captured node) so this
21
- // class never needs to know HOW the host node is found — AnimatedComponentBase and
22
- // AnimatedImage both resolve it the same way (resolveHostNode + isSymbioteNode over their
23
- // own @ViewChild), but that's Angular DI state this binder has no business touching.
24
- // `label` is purely diagnostic (dlog), mirroring the constructor.name the pre-extraction
25
- // dlog call used.
26
- constructor(resolveNode, label) {
27
- this.resolveNode = resolveNode;
28
- this.label = label;
29
- }
30
- // Build a fresh AnimatedProps leaf from the current props (the React/Vue per-render
31
- // useMemo(rest)), wire it into the graph, and swap the previous one out. Attach the NEW leaf
32
- // BEFORE detaching the OLD one: a shared Value self-detaches (dropping its native node) the
33
- // instant its child count hits zero, so detaching first would kill a running native animation
34
- // on any unrelated re-render (mirrors RN's AnimatedComponent._attachProps). Then bind the
35
- // committed node, go native if wanted, and rebind native events.
36
- reconcile(props, wantsNative) {
37
- dlog(`${this.label} reconcile`);
38
- const newLeaf = new AnimatedProps(props);
39
- newLeaf.__attach();
40
- if (this.attached !== null && this.attached !== newLeaf)
41
- this.attached.__detach();
42
- this.attached = newLeaf;
43
- this.bindNode(newLeaf, props, wantsNative);
44
- }
45
- // Bind the leaf to the host's Fabric node THROUGH whenCommitted: under Angular's zoneless
46
- // batched change detection the inner host's tag does not exist yet at ngAfterViewInit time
47
- // (the same async-commit gotcha Vue documents), so binding eagerly would no-op. whenCommitted
48
- // runs the action now if already committed, else after the commit that assigns the tag.
49
- bindNode(leaf, props, wantsNative) {
50
- this.cancelBind?.();
51
- this.cancelBind = undefined;
52
- const node = this.resolveNode();
53
- if (node === null)
54
- return;
55
- this.cancelBind = whenCommitted(node, () => {
56
- leaf.setNativeView(node);
57
- if (wantsNative)
58
- leaf.__makeNative();
59
- this.attachEvents(node, props);
60
- });
61
- }
62
- // Native-attach any Animated.event prop (e.g. onScroll={Animated.event(…,{useNativeDriver:true})})
63
- // to the committed node. attachNativeEventHandler no-ops (returns undefined) unless the prop is a
64
- // native event handler with a committed tag, so the JS path stays the fallback. Rebound each
65
- // reconcile so a new inline event re-attaches; detached first to avoid leaking the prior binding.
66
- attachEvents(node, props) {
67
- this.detachEvents();
68
- for (const key of Object.keys(props)) {
69
- const attachment = attachNativeEventHandler(node, key, props[key]);
70
- if (attachment !== undefined)
71
- this.eventDetachers.push(attachment.detach);
72
- }
73
- }
74
- detachEvents() {
75
- for (const detach of this.eventDetachers)
76
- detach();
77
- this.eventDetachers = [];
78
- }
79
- // Tear down everything: cancel a still-pending bind, drop native event listeners, detach the
80
- // current leaf from the value graph. Safe to call even if reconcile() was never called.
81
- destroy() {
82
- this.cancelBind?.();
83
- this.cancelBind = undefined;
84
- this.detachEvents();
85
- if (this.attached !== null) {
86
- this.attached.__detach();
87
- this.attached = null;
88
- }
89
- }
90
- }
91
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/modules/animated/animated-leaf-binder/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,yFAAyF;AACzF,yFAAyF;AACzF,oFAAoF;AACpF,uFAAuF;AACvF,uFAAuF;AACvF,iFAAiF;AACjF,oFAAoF;AACpF,gFAAgF;AAChF,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,IAAI,EACJ,aAAa,GAEd,MAAM,yBAAyB,CAAC;AAEjC,MAAM,OAAO,kBAAkB;IAeV;IACA;IAfnB,iFAAiF;IACzE,QAAQ,GAAyB,IAAI,CAAC;IAC9C,2FAA2F;IACnF,UAAU,CAA2B;IAC7C,8FAA8F;IACtF,cAAc,GAAsB,EAAE,CAAC;IAE/C,2FAA2F;IAC3F,mFAAmF;IACnF,0FAA0F;IAC1F,qFAAqF;IACrF,yFAAyF;IACzF,kBAAkB;IAClB,YACmB,WAAuC,EACvC,KAAa;QADb,gBAAW,GAAX,WAAW,CAA4B;QACvC,UAAK,GAAL,KAAK,CAAQ;IAC7B,CAAC;IAEJ,oFAAoF;IACpF,6FAA6F;IAC7F,4FAA4F;IAC5F,8FAA8F;IAC9F,0FAA0F;IAC1F,iEAAiE;IACjE,SAAS,CAAC,KAA8B,EAAE,WAAoB;QAC5D,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,YAAY,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACnB,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO;YAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAClF,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QAExB,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAC7C,CAAC;IAED,0FAA0F;IAC1F,2FAA2F;IAC3F,8FAA8F;IAC9F,wFAAwF;IAChF,QAAQ,CACd,IAAmB,EACnB,KAA8B,EAC9B,WAAoB;QAEpB,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACpB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAChC,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO;QAC1B,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE;YACzC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YACzB,IAAI,WAAW;gBAAE,IAAI,CAAC,YAAY,EAAE,CAAC;YACrC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,mGAAmG;IACnG,kGAAkG;IAClG,6FAA6F;IAC7F,kGAAkG;IAC1F,YAAY,CAAC,IAAmB,EAAE,KAA8B;QACtE,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACrC,MAAM,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YACnE,IAAI,UAAU,KAAK,SAAS;gBAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAEO,YAAY;QAClB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,cAAc;YAAE,MAAM,EAAE,CAAC;QACnD,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;IAC3B,CAAC;IAED,6FAA6F;IAC7F,wFAAwF;IACxF,OAAO;QACL,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACpB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC;IACH,CAAC;CACF"}
@@ -1,102 +0,0 @@
1
- // A flat-bag `onX` responder callback is invoked by the engine's event dispatch, entirely
2
- // outside Angular. In Angular 20 components compile as SignalView (not CheckAlways), so a plain
3
- // state mutation inside such a callback dirties nothing and the template stays stale — the "pan
4
- // does nothing" bug, while React/Vue repainted the same demo. SymbioteHostPropsDirective now
5
- // calls `cdr.markForCheck()` after each such callback (flags the component + ancestors with
6
- // RefreshView and notifies the scheduler), so the mutation repaints. This is the flat-root case;
7
- // responder-nested-cd.test.ts covers the App→child nesting the real app has.
8
-
9
- import '@angular/compiler';
10
- import { Component } from '@angular/core';
11
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
- import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
13
-
14
- import { mount, unmount } from '../render';
15
- import { ViewHost as View, TextHost as Text, SymbioteHostPropsDirective } from '../primitives';
16
-
17
- const ROOT_TAG = 970;
18
- const TOUCH_START = 'topTouchStart';
19
- const TOUCH_MOVE = 'topTouchMove';
20
- const TOUCH_END = 'topTouchEnd';
21
-
22
- const fabric = installFabric();
23
-
24
- // Fabric is clone-on-write: a prop update yields a NEW node object in the committed tree,
25
- // never in `created`. Walk the live committed child-set for post-mutation assertions.
26
- function findCommitted(predicate: (node: IFakeNode) => boolean): IFakeNode | undefined {
27
- const stack = [...fabric.committed];
28
- while (stack.length > 0) {
29
- const node = stack.pop();
30
- if (node === undefined) continue;
31
- if (predicate(node)) return node;
32
- stack.push(...node.children);
33
- }
34
- return undefined;
35
- }
36
-
37
- function statusText(testID: string): string | undefined {
38
- const node = findCommitted(n => n.props.testID === testID);
39
- const raw = node?.children[0];
40
- return typeof raw?.props.text === 'string' ? raw.props.text : undefined;
41
- }
42
-
43
- // The stable SymbioteNode (event target). Its identity and listener map survive clone-on-
44
- // write, so firing on the first-created handle reaches the live listeners.
45
- function handleFor(testID: string): unknown {
46
- const node = fabric.find(n => n.props.testID === testID);
47
- if (!node) throw new Error(`no node created with testID=${testID}`);
48
- return node.instanceHandle;
49
- }
50
-
51
- const flush = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
52
-
53
- @Component({
54
- selector: 'symbiote-responder-cd-host',
55
- standalone: true,
56
- imports: [View, Text, SymbioteHostPropsDirective],
57
- template: `
58
- <View [symbioteHostProps]="handlers"></View>
59
- <Text [symbioteHostProps]="statusProps">{{ status }}</Text>
60
- `,
61
- })
62
- class ResponderCdHost {
63
- status = 'idle';
64
- statusProps = { testID: 'status' };
65
- handlers = {
66
- testID: 'chip',
67
- onStartShouldSetResponder: () => true,
68
- onResponderGrant: () => {
69
- this.status = 'granted';
70
- },
71
- onResponderMove: () => {
72
- this.status = 'moving';
73
- },
74
- onResponderRelease: () => {
75
- this.status = 'released';
76
- },
77
- };
78
- }
79
-
80
- beforeEach(() => fabric.reset());
81
- afterEach(() => unmount(ROOT_TAG));
82
-
83
- describe('Angular responder callback triggers change detection', () => {
84
- it('re-renders bound state mutated inside a flat-bag responder callback', async () => {
85
- mount(ROOT_TAG, ResponderCdHost);
86
- await flush();
87
- expect(statusText('status')).toBe('idle');
88
-
89
- const chip = handleFor('chip');
90
- fabric.fireEvent(chip, TOUCH_START);
91
- await flush();
92
- expect(statusText('status')).toBe('granted');
93
-
94
- fabric.fireEvent(chip, TOUCH_MOVE);
95
- await flush();
96
- expect(statusText('status')).toBe('moving');
97
-
98
- fabric.fireEvent(chip, TOUCH_END);
99
- await flush();
100
- expect(statusText('status')).toBe('released');
101
- });
102
- });
@@ -1,113 +0,0 @@
1
- // The device-faithful shape: a ROOT component whose template embeds a CHILD component that
2
- // owns the responder state + the {{status}} binding (App -> ResponderDemo). Angular 20 compiles
3
- // components as SignalView (not CheckAlways), so a plain (non-signal) state mutation inside a
4
- // flat-bag responder callback does NOT dirty the child's reactive consumer, and the scheduler's
5
- // root detectChanges() will NOT descend into it — the child's {{status}} used to stay stale
6
- // ("pan does nothing"). SymbioteHostPropsDirective now refreshes its own host component's view
7
- // right after the callback runs, so the nested child repaints.
8
-
9
- import '@angular/compiler';
10
- import { Component } from '@angular/core';
11
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
- import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
13
-
14
- import { mount, unmount } from '../render';
15
- import { ViewHost as View, TextHost as Text, SymbioteHostPropsDirective } from '../primitives';
16
- import { registerComposedComponent } from '../anchor-host-registry';
17
-
18
- const ROOT_TAG = 972;
19
- const TOUCH_START = 'topTouchStart';
20
- const TOUCH_MOVE = 'topTouchMove';
21
- const TOUCH_END = 'topTouchEnd';
22
-
23
- registerComposedComponent('nested-responder-inner');
24
-
25
- const fabric = installFabric();
26
-
27
- function findCommitted(predicate: (node: IFakeNode) => boolean): IFakeNode | undefined {
28
- const stack = [...fabric.committed];
29
- while (stack.length > 0) {
30
- const node = stack.pop();
31
- if (node === undefined) continue;
32
- if (predicate(node)) return node;
33
- stack.push(...node.children);
34
- }
35
- return undefined;
36
- }
37
-
38
- function statusText(testID: string): string | undefined {
39
- const node = findCommitted(n => n.props.testID === testID);
40
- const raw = node?.children[0];
41
- return typeof raw?.props.text === 'string' ? raw.props.text : undefined;
42
- }
43
-
44
- function handleFor(testID: string): unknown {
45
- const node = fabric.find(n => n.props.testID === testID);
46
- if (!node) throw new Error(`no node created with testID=${testID}`);
47
- return node.instanceHandle;
48
- }
49
-
50
- const flush = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
51
-
52
- @Component({
53
- selector: 'nested-responder-inner',
54
- standalone: true,
55
- imports: [View, Text, SymbioteHostPropsDirective],
56
- template: `
57
- <View [symbioteHostProps]="handlers"></View>
58
- <Text [symbioteHostProps]="statusProps">{{ status }}</Text>
59
- `,
60
- })
61
- class NestedResponderInner {
62
- status = 'idle';
63
- statusProps = { testID: 'nested-status' };
64
- handlers = {
65
- testID: 'nested-chip',
66
- onStartShouldSetResponder: () => true,
67
- onResponderGrant: () => {
68
- this.status = 'granted';
69
- },
70
- onResponderMove: () => {
71
- this.status = 'moving';
72
- },
73
- onResponderRelease: () => {
74
- this.status = 'released';
75
- },
76
- };
77
- }
78
-
79
- @Component({
80
- selector: 'nested-responder-outer',
81
- standalone: true,
82
- imports: [View, NestedResponderInner],
83
- template: `
84
- <View>
85
- <nested-responder-inner></nested-responder-inner>
86
- </View>
87
- `,
88
- })
89
- class NestedResponderOuter {}
90
-
91
- beforeEach(() => fabric.reset());
92
- afterEach(() => unmount(ROOT_TAG));
93
-
94
- describe('Angular nested-child responder CD', () => {
95
- it('re-renders a nested child component`s state mutated in a flat-bag responder callback', async () => {
96
- mount(ROOT_TAG, NestedResponderOuter);
97
- await flush();
98
- expect(statusText('nested-status')).toBe('idle');
99
-
100
- const chip = handleFor('nested-chip');
101
- fabric.fireEvent(chip, TOUCH_START);
102
- await flush();
103
- expect(statusText('nested-status')).toBe('granted');
104
-
105
- fabric.fireEvent(chip, TOUCH_MOVE);
106
- await flush();
107
- expect(statusText('nested-status')).toBe('moving');
108
-
109
- fabric.fireEvent(chip, TOUCH_END);
110
- await flush();
111
- expect(statusText('nested-status')).toBe('released');
112
- });
113
- });
@@ -1,142 +0,0 @@
1
- /// <reference types="node" />
2
-
3
- import { readFileSync } from 'node:fs';
4
- import { describe, expect, it } from 'vitest';
5
-
6
- const readSource = (path: string): string => readFileSync(path, 'utf8');
7
-
8
- describe('Angular adapter gap regressions', () => {
9
- it('VirtualizedList exposes accessibility and aria inputs and forwards them to ScrollView', () => {
10
- const source = readSource('adapters/angular/src/components/virtualized-list/index.ts');
11
-
12
- expect(source).toContain('@Input() accessibilityLabel?: string');
13
- expect(source).toContain('@Input() ariaBusy?: boolean');
14
- expect(source).toContain('[accessibilityLabel]="foldedAccessibility.accessibilityLabel"');
15
- expect(source).toContain('[accessibilityState]="foldedAccessibility.accessibilityState"');
16
- expect(source).toContain('[accessibilityRole]="foldedAccessibility.accessibilityRole"');
17
- });
18
-
19
- it('Animated namespace exposes AOT-safe built-in entries and documents runtime HOC as a non-goal', () => {
20
- const animatedSource = readSource('adapters/angular/src/modules/animated/index.ts');
21
- const componentSource = readSource(
22
- 'adapters/angular/src/modules/animated/create-animated-component.ts',
23
- );
24
-
25
- expect(componentSource).toContain('export const AnimatedFlatList');
26
- expect(componentSource).toContain('export const AnimatedSectionList');
27
- expect(componentSource).toContain('if (base === View) return AnimatedView');
28
- expect(componentSource).toContain('if (base === Text) return AnimatedText');
29
- expect(componentSource).toContain('if (base === Image) return AnimatedImage');
30
- expect(componentSource).toContain('if (base === ScrollView) return AnimatedScrollView');
31
- expect(componentSource).toContain('Angular cannot synthesize a component at runtime');
32
- expect(componentSource).toContain('no JIT under AOT/Metro');
33
- expect(componentSource).toContain(
34
- 'Author an explicit standalone @Component extending AnimatedComponentBase',
35
- );
36
- expect(animatedSource).toContain('View: AnimatedView');
37
- expect(animatedSource).toContain('Text: AnimatedText');
38
- expect(animatedSource).toContain('Image: AnimatedImage');
39
- expect(animatedSource).toContain('ScrollView: AnimatedScrollView');
40
- expect(animatedSource).toContain('FlatList: AnimatedFlatList');
41
- expect(animatedSource).toContain('SectionList: AnimatedSectionList');
42
- });
43
-
44
- it('AnimatedImage uses composed Image props and normalized load events', () => {
45
- const source = readSource('adapters/angular/src/modules/animated/create-animated-component.ts');
46
-
47
- expect(source).toContain('export class AnimatedImage extends ImageBase');
48
- expect(source).toContain('inputs: ANIMATED_IMAGE_INPUTS');
49
- expect(source).toContain('outputs: IMAGE_OUTPUTS');
50
- expect(source).toContain('const resolved = resolveImageProps(reduced)');
51
- expect(source).toContain('(load)="handleLoad($event)"');
52
- expect(source).toContain('(error)="handleError($event)"');
53
- });
54
-
55
- it('Image prop resolution is shared with AnimatedImage', () => {
56
- const imageSource = readSource('adapters/angular/src/components/image/shared.ts');
57
-
58
- expect(imageSource).toContain('export function resolveImageProps');
59
- expect(imageSource).toContain('get imageProps(): Record<string, unknown>');
60
- expect(imageSource).toContain('return resolveImageProps(this.imageInputProps)');
61
- });
62
-
63
- it('Button forwards its full accessibility and TV-focus surface through TouchableOpacity', () => {
64
- const buttonSource = readSource('adapters/angular/src/components/button.ts');
65
- const touchableSource = readSource('adapters/angular/src/components/touchable/index.ts');
66
- const pressableSource = readSource('adapters/angular/src/components/pressable/index.ts');
67
-
68
- for (const binding of [
69
- '[accessible]="true"',
70
- '[accessibilityLabelledBy]="accessibilityLabelledBy"',
71
- '[importantForAccessibility]="importantForAccessibility"',
72
- '[accessibilityLiveRegion]="accessibilityLiveRegion"',
73
- '[screenReaderFocusable]="screenReaderFocusable"',
74
- '[accessibilityViewIsModal]="accessibilityViewIsModal"',
75
- '[accessibilityElementsHidden]="accessibilityElementsHidden"',
76
- '[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"',
77
- '[accessibilityLanguage]="accessibilityLanguage"',
78
- '[accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"',
79
- '[accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"',
80
- '[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"',
81
- '(accessibilityAction)="accessibilityAction.emit($event)"',
82
- '(accessibilityTap)="accessibilityTap.emit($event)"',
83
- '(magicTap)="magicTap.emit($event)"',
84
- '(accessibilityEscape)="accessibilityEscape.emit($event)"',
85
- '[ariaModal]="ariaModal"',
86
- '[ariaValueMax]="ariaValueMax"',
87
- '[ariaValueMin]="ariaValueMin"',
88
- '[ariaValueNow]="ariaValueNow"',
89
- '[ariaValueText]="ariaValueText"',
90
- '[hasTVPreferredFocus]="hasTVPreferredFocus"',
91
- '[nextFocusDown]="nextFocusDown"',
92
- '[nextFocusForward]="nextFocusForward"',
93
- '[nextFocusLeft]="nextFocusLeft"',
94
- '[nextFocusRight]="nextFocusRight"',
95
- '[nextFocusUp]="nextFocusUp"',
96
- ]) {
97
- expect(buttonSource).toContain(binding);
98
- }
99
-
100
- for (const binding of [
101
- '[accessible]="accessible"',
102
- '[accessibilityLabelledBy]="accessibilityLabelledBy"',
103
- '[importantForAccessibility]="importantForAccessibility"',
104
- '[accessibilityLiveRegion]="accessibilityLiveRegion"',
105
- '[screenReaderFocusable]="screenReaderFocusable"',
106
- '[accessibilityViewIsModal]="accessibilityViewIsModal"',
107
- '[accessibilityElementsHidden]="accessibilityElementsHidden"',
108
- '[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"',
109
- '[accessibilityLanguage]="accessibilityLanguage"',
110
- '[accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"',
111
- '[accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"',
112
- '[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"',
113
- '(accessibilityAction)="accessibilityAction.emit($event)"',
114
- '(accessibilityTap)="accessibilityTap.emit($event)"',
115
- '(magicTap)="magicTap.emit($event)"',
116
- '(accessibilityEscape)="accessibilityEscape.emit($event)"',
117
- '[ariaModal]="ariaModal"',
118
- '[ariaValueMax]="ariaValueMax"',
119
- '[ariaValueMin]="ariaValueMin"',
120
- '[ariaValueNow]="ariaValueNow"',
121
- '[ariaValueText]="ariaValueText"',
122
- '[hasTVPreferredFocus]="hasTVPreferredFocus"',
123
- '[nextFocusDown]="nextFocusDown"',
124
- '[nextFocusForward]="nextFocusForward"',
125
- '[nextFocusLeft]="nextFocusLeft"',
126
- '[nextFocusRight]="nextFocusRight"',
127
- '[nextFocusUp]="nextFocusUp"',
128
- ]) {
129
- expect(touchableSource).toContain(binding);
130
- }
131
-
132
- // Pressable forwards its resolved props through the shared SymbioteHostPropsDirective
133
- // (adapters/angular/src/primitives/shared.ts) rather than one `[prop]="x"` binding per
134
- // key, so the contract to check is: the binding exists, and the `hostProps` getter it
135
- // reads from actually assembles `accessible` / the folded accessibility bag / TV-focus.
136
- expect(pressableSource).toContain('[symbioteHostProps]="hostProps"');
137
- expect(pressableSource).toContain('accessible: this.accessible');
138
- expect(pressableSource).toContain('...this.foldedAccessibility');
139
- expect(pressableSource).toContain('hasTVPreferredFocus: this.hasTVPreferredFocus');
140
- expect(pressableSource).toContain('(accessibilityAction)="emit(accessibilityAction, $event)"');
141
- });
142
- });
@@ -1,100 +0,0 @@
1
- // Angular source-import harness regression. Vitest imports adapter source (`src/*.ts`) directly,
2
- // not the partial-Ivy `ngc` output Metro consumes. The adapter tsconfig must therefore enable
3
- // TypeScript's legacy decorator lowering so Node never sees raw `@Component` / `@Directive`
4
- // syntax. Importing `@angular/compiler` enables JIT metadata creation for these source-only tests;
5
- // production still proves AOT/partial-Ivy through `pnpm --filter @symbiote-native/angular ng:build`.
6
-
7
- import '@angular/compiler';
8
- import { ElementRef, Injector, runInInjectionContext } from '@angular/core';
9
- import { describe, expect, it } from 'vitest';
10
- import { Image, ScrollView, Text, View } from './components';
11
- import { VirtualizedList, VListItemDirective } from './components/virtualized-list';
12
- import { SymbioteHostPropsDirective } from './primitives';
13
- import {
14
- Animated,
15
- AnimatedImage,
16
- AnimatedScrollView,
17
- AnimatedText,
18
- AnimatedView,
19
- createAnimatedComponent,
20
- } from './modules/animated';
21
-
22
- interface IAngularCompiledComponent {
23
- ɵcmp?: { selectors?: unknown };
24
- }
25
-
26
- interface IAngularCompiledDirective {
27
- ɵdir?: { selectors?: unknown };
28
- }
29
-
30
- describe('Angular source imports under Vitest', () => {
31
- it('imports decorated adapter components and directives directly from src', () => {
32
- expect(VirtualizedList).toBeTypeOf('function');
33
- expect(VListItemDirective).toBeTypeOf('function');
34
- expect(AnimatedView).toBeTypeOf('function');
35
- expect(SymbioteHostPropsDirective).toBeTypeOf('function');
36
- });
37
-
38
- it('keeps Angular decorator metadata available to the JIT test runtime', () => {
39
- expect((VirtualizedList as IAngularCompiledComponent).ɵcmp?.selectors).toEqual([
40
- ['VirtualizedList'],
41
- ]);
42
- expect((AnimatedView as IAngularCompiledComponent).ɵcmp?.selectors).toEqual([
43
- ['AnimatedView'],
44
- ['symbiote-animated-view'],
45
- ]);
46
- expect((VListItemDirective as IAngularCompiledDirective).ɵdir?.selectors).toEqual([
47
- ['', 'vListItem', ''],
48
- ]);
49
- expect((SymbioteHostPropsDirective as IAngularCompiledDirective).ɵdir?.selectors).toEqual([
50
- ['', 'symbioteHostProps', ''],
51
- ]);
52
- });
53
-
54
- it('imports the Animated namespace without losing component identity', () => {
55
- expect(Animated.View).toBe(AnimatedView);
56
- expect(Animated.Text).toBe(AnimatedText);
57
- expect(Animated.Image).toBe(AnimatedImage);
58
- expect(Animated.ScrollView).toBe(AnimatedScrollView);
59
- expect(Animated.FlatList).toBeTypeOf('function');
60
- expect(Animated.SectionList).toBeTypeOf('function');
61
- });
62
-
63
- it('keeps createAnimatedComponent limited to pre-authored AOT-safe Angular wrappers', () => {
64
- class CustomComponent {}
65
-
66
- expect(createAnimatedComponent(View)).toBe(AnimatedView);
67
- expect(createAnimatedComponent(Text)).toBe(AnimatedText);
68
- expect(createAnimatedComponent(Image)).toBe(AnimatedImage);
69
- expect(createAnimatedComponent(ScrollView)).toBe(AnimatedScrollView);
70
- expect(() => createAnimatedComponent(CustomComponent)).toThrow(
71
- /Angular cannot synthesize a component at runtime \(no JIT compiler under AOT\/Metro\)/,
72
- );
73
- expect(() => createAnimatedComponent(CustomComponent)).toThrow(
74
- /Author an explicit standalone @Component extending AnimatedComponentBase instead/,
75
- );
76
- });
77
-
78
- it('resolves AnimatedImage props through the composed Image path', () => {
79
- // AnimatedImage is an ANCHOR_HOST_COMPONENT: its field initializer injects its own
80
- // ElementRef (anchorHostStyle merge, see create-animated-component.ts), so constructing it
81
- // outside Angular's component machinery needs an explicit injection context.
82
- const injector = Injector.create({
83
- providers: [{ provide: ElementRef, useValue: new ElementRef({}) }],
84
- });
85
- const image = runInInjectionContext(injector, () => new AnimatedImage());
86
- image.src = 'https://example.invalid/image.png';
87
- image.width = 32;
88
- image.height = 24;
89
- image.alt = 'Preview';
90
- image.animatedProps = { style: { opacity: 0.5 }, testID: 'animated-image' };
91
-
92
- expect(image.animatedImageProps).toMatchObject({
93
- testID: 'animated-image',
94
- accessible: true,
95
- accessibilityLabel: 'Preview',
96
- source: [{ uri: 'https://example.invalid/image.png' }],
97
- style: [undefined, [{ width: 32, height: 24 }, { opacity: 0.5 }]],
98
- });
99
- });
100
- });
@@ -1,44 +0,0 @@
1
- // Regression test for the anchor/class bug: ActivityIndicator is its own
2
- // ANCHOR_HOST_COMPONENTS entry AND renders through DescriptorOutlet (`symbiote-descriptor-outlet`,
3
- // itself also an anchor entry) — a class= on <ActivityIndicator> resolves onto ActivityIndicator's
4
- // OWN anchor, two levels up from the real committed wrapper View the descriptor builds, and needs
5
- // its OWN anchorHostStyle merge (see shared.ts's `descriptor` getter). Mirrors pressable.test.ts's
6
- // "resolves a class=" case.
7
- import '@angular/compiler';
8
- import { Component } from '@angular/core';
9
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
10
- import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
11
- import { installFabric } from '@symbiote-native/test-utils';
12
-
13
- import { mount, unmount } from '../../render';
14
- import { ActivityIndicator } from './index';
15
-
16
- const ROOT_TAG = 909;
17
- const fabric = installFabric();
18
-
19
- @Component({
20
- selector: 'symbiote-activity-indicator-class-host',
21
- standalone: true,
22
- imports: [ActivityIndicator],
23
- template: `<ActivityIndicator class="card" />`,
24
- })
25
- class ActivityIndicatorClassHost {}
26
-
27
- beforeEach(() => {
28
- fabric.reset();
29
- registerStyles({ card: { backgroundColor: 'red' } });
30
- });
31
- afterEach(() => {
32
- unmount(ROOT_TAG);
33
- clearGlobalStyles();
34
- });
35
-
36
- describe('ActivityIndicator anchor class= resolution', () => {
37
- it('resolves a class= on the ActivityIndicator use site onto the real committed wrapper View', async () => {
38
- mount(ROOT_TAG, ActivityIndicatorClassHost);
39
- await new Promise<void>(resolve => setTimeout(resolve, 0));
40
-
41
- const node = fabric.find(n => n.props.backgroundColor === 'red');
42
- expect(node, 'a real Fabric node carries the class-derived style').toBeDefined();
43
- });
44
- });