@symbiote-native/angular 0.1.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.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/build/angular/components/activity-indicator/index.android.d.ts +12 -0
- package/build/angular/components/activity-indicator/index.android.js +21 -0
- package/build/angular/components/activity-indicator/index.android.js.map +1 -0
- package/build/angular/components/activity-indicator/index.d.ts +1 -0
- package/build/angular/components/activity-indicator/index.ios.d.ts +8 -0
- package/build/angular/components/activity-indicator/index.ios.js +21 -0
- package/build/angular/components/activity-indicator/index.ios.js.map +1 -0
- package/build/angular/components/activity-indicator/index.js +4 -0
- package/build/angular/components/activity-indicator/index.js.map +1 -0
- package/build/angular/components/activity-indicator/shared.d.ts +67 -0
- package/build/angular/components/activity-indicator/shared.js +260 -0
- package/build/angular/components/activity-indicator/shared.js.map +1 -0
- package/build/angular/components/button.d.ts +63 -0
- package/build/angular/components/button.js +332 -0
- package/build/angular/components/button.js.map +1 -0
- package/build/angular/components/drawer-layout-android/index.android.d.ts +34 -0
- package/build/angular/components/drawer-layout-android/index.android.js +291 -0
- package/build/angular/components/drawer-layout-android/index.android.js.map +1 -0
- package/build/angular/components/drawer-layout-android/index.d.ts +11 -0
- package/build/angular/components/drawer-layout-android/index.js +109 -0
- package/build/angular/components/drawer-layout-android/index.js.map +1 -0
- package/build/angular/components/drawer-layout-android/shared.d.ts +74 -0
- package/build/angular/components/drawer-layout-android/shared.js +138 -0
- package/build/angular/components/drawer-layout-android/shared.js.map +1 -0
- package/build/angular/components/flat-list/index.d.ts +122 -0
- package/build/angular/components/flat-list/index.js +689 -0
- package/build/angular/components/flat-list/index.js.map +1 -0
- package/build/angular/components/image/index.android.d.ts +10 -0
- package/build/angular/components/image/index.android.js +59 -0
- package/build/angular/components/image/index.android.js.map +1 -0
- package/build/angular/components/image/index.d.ts +1 -0
- package/build/angular/components/image/index.ios.d.ts +10 -0
- package/build/angular/components/image/index.ios.js +59 -0
- package/build/angular/components/image/index.ios.js.map +1 -0
- package/build/angular/components/image/index.js +3 -0
- package/build/angular/components/image/index.js.map +1 -0
- package/build/angular/components/image/shared.d.ts +101 -0
- package/build/angular/components/image/shared.js +408 -0
- package/build/angular/components/image/shared.js.map +1 -0
- package/build/angular/components/image-background.d.ts +81 -0
- package/build/angular/components/image-background.js +418 -0
- package/build/angular/components/image-background.js.map +1 -0
- package/build/angular/components/input-accessory-view.d.ts +59 -0
- package/build/angular/components/input-accessory-view.js +266 -0
- package/build/angular/components/input-accessory-view.js.map +1 -0
- package/build/angular/components/keyboard-avoiding-view.d.ts +82 -0
- package/build/angular/components/keyboard-avoiding-view.js +363 -0
- package/build/angular/components/keyboard-avoiding-view.js.map +1 -0
- package/build/angular/components/modal.d.ts +92 -0
- package/build/angular/components/modal.js +365 -0
- package/build/angular/components/modal.js.map +1 -0
- package/build/angular/components/pressable/index.d.ts +123 -0
- package/build/angular/components/pressable/index.js +421 -0
- package/build/angular/components/pressable/index.js.map +1 -0
- package/build/angular/components/refresh-control.d.ts +86 -0
- package/build/angular/components/refresh-control.js +347 -0
- package/build/angular/components/refresh-control.js.map +1 -0
- package/build/angular/components/safe-area-view.d.ts +57 -0
- package/build/angular/components/safe-area-view.js +253 -0
- package/build/angular/components/safe-area-view.js.map +1 -0
- package/build/angular/components/scroll-view/index.android.d.ts +21 -0
- package/build/angular/components/scroll-view/index.android.js +235 -0
- package/build/angular/components/scroll-view/index.android.js.map +1 -0
- package/build/angular/components/scroll-view/index.d.ts +3 -0
- package/build/angular/components/scroll-view/index.ios.d.ts +9 -0
- package/build/angular/components/scroll-view/index.ios.js +86 -0
- package/build/angular/components/scroll-view/index.ios.js.map +1 -0
- package/build/angular/components/scroll-view/index.js +6 -0
- package/build/angular/components/scroll-view/index.js.map +1 -0
- package/build/angular/components/scroll-view/projection.d.ts +32 -0
- package/build/angular/components/scroll-view/projection.js +312 -0
- package/build/angular/components/scroll-view/projection.js.map +1 -0
- package/build/angular/components/scroll-view/shared.d.ts +230 -0
- package/build/angular/components/scroll-view/shared.js +679 -0
- package/build/angular/components/scroll-view/shared.js.map +1 -0
- package/build/angular/components/scroll-view/sticky-header.d.ts +41 -0
- package/build/angular/components/scroll-view/sticky-header.js +208 -0
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -0
- package/build/angular/components/section-list/index.d.ts +116 -0
- package/build/angular/components/section-list/index.js +641 -0
- package/build/angular/components/section-list/index.js.map +1 -0
- package/build/angular/components/switch/index.android.d.ts +20 -0
- package/build/angular/components/switch/index.android.js +70 -0
- package/build/angular/components/switch/index.android.js.map +1 -0
- package/build/angular/components/switch/index.d.ts +1 -0
- package/build/angular/components/switch/index.ios.d.ts +19 -0
- package/build/angular/components/switch/index.ios.js +69 -0
- package/build/angular/components/switch/index.ios.js.map +1 -0
- package/build/angular/components/switch/index.js +4 -0
- package/build/angular/components/switch/index.js.map +1 -0
- package/build/angular/components/switch/shared.d.ts +40 -0
- package/build/angular/components/switch/shared.js +127 -0
- package/build/angular/components/switch/shared.js.map +1 -0
- package/build/angular/components/text-input.d.ts +153 -0
- package/build/angular/components/text-input.js +604 -0
- package/build/angular/components/text-input.js.map +1 -0
- package/build/angular/components/touchable-native-feedback.d.ts +83 -0
- package/build/angular/components/touchable-native-feedback.js +376 -0
- package/build/angular/components/touchable-native-feedback.js.map +1 -0
- package/build/angular/components/touchable.d.ts +248 -0
- package/build/angular/components/touchable.js +1206 -0
- package/build/angular/components/touchable.js.map +1 -0
- package/build/angular/components/virtualized-list/directives.d.ts +54 -0
- package/build/angular/components/virtualized-list/directives.js +154 -0
- package/build/angular/components/virtualized-list/directives.js.map +1 -0
- package/build/angular/components/virtualized-list/index.d.ts +268 -0
- package/build/angular/components/virtualized-list/index.js +1162 -0
- package/build/angular/components/virtualized-list/index.js.map +1 -0
- package/build/angular/components/virtualized-section-list/directives.d.ts +37 -0
- package/build/angular/components/virtualized-section-list/directives.js +77 -0
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -0
- package/build/angular/components/virtualized-section-list/index.d.ts +178 -0
- package/build/angular/components/virtualized-section-list/index.js +722 -0
- package/build/angular/components/virtualized-section-list/index.js.map +1 -0
- package/build/angular/components.d.ts +39 -0
- package/build/angular/components.js +38 -0
- package/build/angular/components.js.map +1 -0
- package/build/angular/create-portal.d.ts +22 -0
- package/build/angular/create-portal.js +74 -0
- package/build/angular/create-portal.js.map +1 -0
- package/build/angular/create-tunnel.d.ts +34 -0
- package/build/angular/create-tunnel.js +126 -0
- package/build/angular/create-tunnel.js.map +1 -0
- package/build/angular/descriptor-to-angular/index.d.ts +22 -0
- package/build/angular/descriptor-to-angular/index.js +142 -0
- package/build/angular/descriptor-to-angular/index.js.map +1 -0
- package/build/angular/host-instance/index.d.ts +2 -0
- package/build/angular/host-instance/index.js +37 -0
- package/build/angular/host-instance/index.js.map +1 -0
- package/build/angular/index.d.ts +21 -0
- package/build/angular/index.js +42 -0
- package/build/angular/index.js.map +1 -0
- package/build/angular/modules/animated/create-animated-component.d.ts +68 -0
- package/build/angular/modules/animated/create-animated-component.js +429 -0
- package/build/angular/modules/animated/create-animated-component.js.map +1 -0
- package/build/angular/modules/animated/index.d.ts +51 -0
- package/build/angular/modules/animated/index.js +59 -0
- package/build/angular/modules/animated/index.js.map +1 -0
- package/build/angular/modules/app-registry/index.d.ts +7 -0
- package/build/angular/modules/app-registry/index.js +23 -0
- package/build/angular/modules/app-registry/index.js.map +1 -0
- package/build/angular/modules/status-bar.d.ts +20 -0
- package/build/angular/modules/status-bar.js +59 -0
- package/build/angular/modules/status-bar.js.map +1 -0
- package/build/angular/primitives/index.d.ts +63 -0
- package/build/angular/primitives/index.js +189 -0
- package/build/angular/primitives/index.js.map +1 -0
- package/build/angular/primitives/shared.d.ts +89 -0
- package/build/angular/primitives/shared.js +172 -0
- package/build/angular/primitives/shared.js.map +1 -0
- package/build/angular/render.d.ts +11 -0
- package/build/angular/render.js +161 -0
- package/build/angular/render.js.map +1 -0
- package/build/angular/renderer.d.ts +38 -0
- package/build/angular/renderer.js +360 -0
- package/build/angular/renderer.js.map +1 -0
- package/build/angular/services/color-scheme.service.d.ts +16 -0
- package/build/angular/services/color-scheme.service.js +31 -0
- package/build/angular/services/color-scheme.service.js.map +1 -0
- package/build/angular/services/index.d.ts +2 -0
- package/build/angular/services/index.js +3 -0
- package/build/angular/services/index.js.map +1 -0
- package/build/angular/services/window-dimensions.service.d.ts +16 -0
- package/build/angular/services/window-dimensions.service.js +42 -0
- package/build/angular/services/window-dimensions.service.js.map +1 -0
- package/build/components/activity-indicator/index.android.d.ts +9 -0
- package/build/components/activity-indicator/index.android.js +64 -0
- package/build/components/activity-indicator/index.d.ts +1 -0
- package/build/components/activity-indicator/index.ios.d.ts +5 -0
- package/build/components/activity-indicator/index.ios.js +64 -0
- package/build/components/activity-indicator/index.js +3 -0
- package/build/components/activity-indicator/shared.d.ts +64 -0
- package/build/components/activity-indicator/shared.js +424 -0
- package/build/components/button.d.ts +60 -0
- package/build/components/button.js +456 -0
- package/build/components/drawer-layout-android/index.android.d.ts +29 -0
- package/build/components/drawer-layout-android/index.android.js +326 -0
- package/build/components/drawer-layout-android/index.d.ts +8 -0
- package/build/components/drawer-layout-android/index.js +145 -0
- package/build/components/drawer-layout-android/shared.d.ts +71 -0
- package/build/components/drawer-layout-android/shared.js +205 -0
- package/build/components/flat-list/index.d.ts +119 -0
- package/build/components/flat-list/index.js +692 -0
- package/build/components/image/index.android.d.ts +7 -0
- package/build/components/image/index.android.js +88 -0
- package/build/components/image/index.d.ts +1 -0
- package/build/components/image/index.ios.d.ts +7 -0
- package/build/components/image/index.ios.js +88 -0
- package/build/components/image/index.js +2 -0
- package/build/components/image/shared.d.ts +101 -0
- package/build/components/image/shared.js +407 -0
- package/build/components/image-background.d.ts +78 -0
- package/build/components/image-background.js +583 -0
- package/build/components/input-accessory-view.d.ts +56 -0
- package/build/components/input-accessory-view.js +407 -0
- package/build/components/keyboard-avoiding-view.d.ts +79 -0
- package/build/components/keyboard-avoiding-view.js +509 -0
- package/build/components/modal.d.ts +89 -0
- package/build/components/modal.js +534 -0
- package/build/components/pressable/index.d.ts +120 -0
- package/build/components/pressable/index.js +640 -0
- package/build/components/refresh-control.d.ts +83 -0
- package/build/components/refresh-control.js +514 -0
- package/build/components/safe-area-view.d.ts +54 -0
- package/build/components/safe-area-view.js +393 -0
- package/build/components/scroll-view/index.android.d.ts +16 -0
- package/build/components/scroll-view/index.android.js +228 -0
- package/build/components/scroll-view/index.d.ts +3 -0
- package/build/components/scroll-view/index.ios.d.ts +6 -0
- package/build/components/scroll-view/index.ios.js +113 -0
- package/build/components/scroll-view/index.js +5 -0
- package/build/components/scroll-view/projection.d.ts +32 -0
- package/build/components/scroll-view/projection.js +311 -0
- package/build/components/scroll-view/shared.d.ts +225 -0
- package/build/components/scroll-view/shared.js +757 -0
- package/build/components/scroll-view/sticky-header.d.ts +38 -0
- package/build/components/scroll-view/sticky-header.js +257 -0
- package/build/components/section-list/index.d.ts +113 -0
- package/build/components/section-list/index.js +768 -0
- package/build/components/switch/index.android.d.ts +17 -0
- package/build/components/switch/index.android.js +107 -0
- package/build/components/switch/index.d.ts +1 -0
- package/build/components/switch/index.ios.d.ts +16 -0
- package/build/components/switch/index.ios.js +106 -0
- package/build/components/switch/index.js +3 -0
- package/build/components/switch/shared.d.ts +37 -0
- package/build/components/switch/shared.js +176 -0
- package/build/components/text-input.d.ts +150 -0
- package/build/components/text-input.js +826 -0
- package/build/components/touchable-native-feedback.d.ts +80 -0
- package/build/components/touchable-native-feedback.js +550 -0
- package/build/components/touchable.d.ts +241 -0
- package/build/components/touchable.js +1672 -0
- package/build/components/virtualized-list/directives.d.ts +41 -0
- package/build/components/virtualized-list/directives.js +250 -0
- package/build/components/virtualized-list/index.d.ts +265 -0
- package/build/components/virtualized-list/index.js +1344 -0
- package/build/components/virtualized-section-list/directives.d.ts +28 -0
- package/build/components/virtualized-section-list/directives.js +149 -0
- package/build/components/virtualized-section-list/index.d.ts +175 -0
- package/build/components/virtualized-section-list/index.js +854 -0
- package/build/components.d.ts +39 -0
- package/build/components.js +37 -0
- package/build/create-portal.d.ts +17 -0
- package/build/create-portal.js +128 -0
- package/build/create-tunnel.d.ts +29 -0
- package/build/create-tunnel.js +181 -0
- package/build/descriptor-to-angular/index.d.ts +19 -0
- package/build/descriptor-to-angular/index.js +186 -0
- package/build/host-instance/index.d.ts +2 -0
- package/build/host-instance/index.js +36 -0
- package/build/index.d.ts +21 -0
- package/build/index.js +41 -0
- package/build/modules/animated/create-animated-component.d.ts +57 -0
- package/build/modules/animated/create-animated-component.js +490 -0
- package/build/modules/animated/index.d.ts +51 -0
- package/build/modules/animated/index.js +58 -0
- package/build/modules/app-registry/index.d.ts +7 -0
- package/build/modules/app-registry/index.js +22 -0
- package/build/modules/status-bar.d.ts +17 -0
- package/build/modules/status-bar.js +121 -0
- package/build/primitives/index.d.ts +32 -0
- package/build/primitives/index.js +386 -0
- package/build/primitives/shared.d.ts +84 -0
- package/build/primitives/shared.js +231 -0
- package/build/render.d.ts +11 -0
- package/build/render.js +160 -0
- package/build/renderer.d.ts +38 -0
- package/build/renderer.js +359 -0
- package/build/services/color-scheme.service.d.ts +13 -0
- package/build/services/color-scheme.service.js +72 -0
- package/build/services/index.d.ts +2 -0
- package/build/services/index.js +2 -0
- package/build/services/window-dimensions.service.d.ts +13 -0
- package/build/services/window-dimensions.service.js +83 -0
- package/metro-css-parser.cjs +11 -0
- package/package.json +57 -0
- package/src/__tests__/responder-change-detection.test.ts +102 -0
- package/src/__tests__/responder-nested-cd.test.ts +113 -0
- package/src/angular-gaps.test.ts +142 -0
- package/src/angular-imports.test.ts +100 -0
- package/src/components/activity-indicator/activity-indicator-class-style.test.ts +44 -0
- package/src/components/activity-indicator/activity-indicator.test.ts +131 -0
- package/src/components/activity-indicator/index.android.ts +16 -0
- package/src/components/activity-indicator/index.ios.ts +17 -0
- package/src/components/activity-indicator/index.ts +4 -0
- package/src/components/activity-indicator/shared.ts +206 -0
- package/src/components/button.ts +193 -0
- package/src/components/flat-list/column-wrapper-style-class.test.ts +116 -0
- package/src/components/flat-list/flat-list-array-style.test.ts +131 -0
- package/src/components/flat-list/flat-list-class-style.test.ts +74 -0
- package/src/components/flat-list/flat-list-scroll-containment.test.ts +107 -0
- package/src/components/flat-list/flat-list.test.ts +99 -0
- package/src/components/flat-list/index.ts +575 -0
- package/src/components/image/image-class-style.test.ts +46 -0
- package/src/components/image/index.android.ts +48 -0
- package/src/components/image/index.ios.ts +48 -0
- package/src/components/image/index.ts +3 -0
- package/src/components/image/shared.ts +478 -0
- package/src/components/image-background-class-style.test.ts +48 -0
- package/src/components/image-background-style-class.test.ts +79 -0
- package/src/components/image-background.ts +270 -0
- package/src/components/input-accessory-view.test.ts +43 -0
- package/src/components/input-accessory-view.ts +191 -0
- package/src/components/keyboard-avoiding-view.test.ts +54 -0
- package/src/components/keyboard-avoiding-view.ts +308 -0
- package/src/components/modal.test.ts +43 -0
- package/src/components/modal.ts +304 -0
- package/src/components/pressable/index.ts +371 -0
- package/src/components/pressable/pressable.test.ts +85 -0
- package/src/components/refresh-control.test.ts +41 -0
- package/src/components/refresh-control.ts +288 -0
- package/src/components/safe-area-view.test.ts +43 -0
- package/src/components/safe-area-view.ts +178 -0
- package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +123 -0
- package/src/components/scroll-view/content-container-style-class.test.ts +75 -0
- package/src/components/scroll-view/index.android.ts +190 -0
- package/src/components/scroll-view/index.ios.ts +79 -0
- package/src/components/scroll-view/index.ts +7 -0
- package/src/components/scroll-view/projection.ts +384 -0
- package/src/components/scroll-view/scroll-view-class-style.test.ts +134 -0
- package/src/components/scroll-view/scroll-view-projection.test.ts +194 -0
- package/src/components/scroll-view/shared.ts +836 -0
- package/src/components/scroll-view/sticky-header.ts +230 -0
- package/src/components/section-list/index.ts +445 -0
- package/src/components/switch/android-switch-class.test.ts +42 -0
- package/src/components/switch/index.android.ts +61 -0
- package/src/components/switch/index.ios.ts +60 -0
- package/src/components/switch/index.ts +4 -0
- package/src/components/switch/shared.ts +197 -0
- package/src/components/switch/switch.test.ts +92 -0
- package/src/components/text-input-class-style.test.ts +54 -0
- package/src/components/text-input.ts +536 -0
- package/src/components/touchable-native-feedback.test.ts +49 -0
- package/src/components/touchable-native-feedback.ts +260 -0
- package/src/components/touchable.test.ts +102 -0
- package/src/components/touchable.ts +643 -0
- package/src/components/virtualized-list/directives.ts +169 -0
- package/src/components/virtualized-list/index.ts +1204 -0
- package/src/components/virtualized-list/virtualized-list-class-style.test.ts +76 -0
- package/src/components/virtualized-section-list/directives.ts +91 -0
- package/src/components/virtualized-section-list/index.ts +598 -0
- package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +60 -0
- package/src/components.ts +149 -0
- package/src/create-portal.test.ts +90 -0
- package/src/create-portal.ts +74 -0
- package/src/create-tunnel.test.ts +120 -0
- package/src/create-tunnel.ts +136 -0
- package/src/descriptor-to-angular/descriptor-outlet.test.ts +94 -0
- package/src/descriptor-to-angular/index.ts +190 -0
- package/src/host-instance/host-instance.test.ts +49 -0
- package/src/host-instance/index.ts +36 -0
- package/src/index.ts +257 -0
- package/src/modules/animated/animated-scroll-view.test.ts +85 -0
- package/src/modules/animated/create-animated-component.ts +435 -0
- package/src/modules/animated/index.ts +105 -0
- package/src/modules/app-registry/app-registry.test.ts +106 -0
- package/src/modules/app-registry/index.ts +60 -0
- package/src/modules/status-bar.test.ts +50 -0
- package/src/modules/status-bar.ts +57 -0
- package/src/primitives/index.ts +119 -0
- package/src/primitives/shared.ts +191 -0
- package/src/render.test.ts +147 -0
- package/src/render.ts +224 -0
- package/src/renderer.test.ts +179 -0
- package/src/renderer.ts +407 -0
- package/src/services/color-scheme.service.ts +27 -0
- package/src/services/index.ts +2 -0
- package/src/services/services.test.ts +163 -0
- package/src/services/window-dimensions.service.ts +41 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// AppRegistry: the JS entry point RN apps already use:
|
|
2
|
+
// AppRegistry.registerComponent(appKey, () => App)
|
|
3
|
+
// The registry bookkeeping (sections, host-registrar bridge, headless tasks) is
|
|
4
|
+
// framework-agnostic and lives in @symbiote-native/engine's createAppRegistry; this file supplies
|
|
5
|
+
// only the one Angular-specific seam — building a runnable from a component provider via
|
|
6
|
+
// `mount`'s wrapper/initialProps support (render.ts) — so examples/angular's own
|
|
7
|
+
// registerRunnable + mount call can move onto the same entry point React/Vue already have.
|
|
8
|
+
|
|
9
|
+
import type { Type } from '@angular/core';
|
|
10
|
+
import {
|
|
11
|
+
createAppRegistry,
|
|
12
|
+
dlog,
|
|
13
|
+
type IAppParameters,
|
|
14
|
+
type IRunnable,
|
|
15
|
+
} from '@symbiote-native/engine';
|
|
16
|
+
import { mount } from '../../render';
|
|
17
|
+
|
|
18
|
+
// RN's IComponentProvider: a thunk returning the root component (lazy so the module
|
|
19
|
+
// graph stays cheap until the app actually runs).
|
|
20
|
+
export type IComponentProvider = () => Type<unknown>;
|
|
21
|
+
|
|
22
|
+
// RN's IWrapperComponentProvider: given the surface's parameters, returns a
|
|
23
|
+
// component to wrap the app root in (e.g. a context provider). Optional. Angular has no
|
|
24
|
+
// runtime template synthesis (no JIT under AOT/Metro — see create-animated-component.ts),
|
|
25
|
+
// so the wrapper must be a pre-authored standalone @Component whose template renders
|
|
26
|
+
// `<ng-content>` — the Angular idiom for "render my children" (mount() projects the root's
|
|
27
|
+
// host node into it, the twin of React's createElement(Wrapper, null, rootElement)).
|
|
28
|
+
export type IWrapperComponentProvider = (appParameters: IAppParameters) => Type<unknown>;
|
|
29
|
+
|
|
30
|
+
function runnableFor(
|
|
31
|
+
componentProvider: IComponentProvider,
|
|
32
|
+
getWrapperComponentProvider: () => IWrapperComponentProvider | undefined,
|
|
33
|
+
): IRunnable {
|
|
34
|
+
return appParameters => {
|
|
35
|
+
dlog(`AppRegistry: mounting on rootTag ${String(appParameters.rootTag)}`);
|
|
36
|
+
// Read live (not at registration time) so setWrapperComponentProvider affects every
|
|
37
|
+
// runnable's next run, not just future ones.
|
|
38
|
+
mount(appParameters.rootTag, componentProvider(), {
|
|
39
|
+
initialProps: appParameters.initialProps,
|
|
40
|
+
wrapperComponent: getWrapperComponentProvider()?.(appParameters),
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { AppRegistry, setHostRegistrar } = createAppRegistry<
|
|
46
|
+
IComponentProvider,
|
|
47
|
+
IWrapperComponentProvider
|
|
48
|
+
>(runnableFor);
|
|
49
|
+
|
|
50
|
+
export { AppRegistry, setHostRegistrar };
|
|
51
|
+
export type {
|
|
52
|
+
IAppParameters,
|
|
53
|
+
IRunnable,
|
|
54
|
+
IHostRegistrar,
|
|
55
|
+
IRegistry,
|
|
56
|
+
IHeadlessTask,
|
|
57
|
+
ITaskProvider,
|
|
58
|
+
ITaskCanceller,
|
|
59
|
+
ITaskCancelProvider,
|
|
60
|
+
} from '@symbiote-native/engine';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import '@angular/compiler';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import * as engine from '@symbiote-native/engine';
|
|
5
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
6
|
+
|
|
7
|
+
import { mount, unmount } from '../render';
|
|
8
|
+
import { StatusBar } from './status-bar';
|
|
9
|
+
|
|
10
|
+
const ROOT_TAG = 901;
|
|
11
|
+
const fabric = installFabric();
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'symbiote-status-bar-host',
|
|
15
|
+
standalone: true,
|
|
16
|
+
imports: [StatusBar],
|
|
17
|
+
template: ` <StatusBar [barStyle]="'dark-content'" [hidden]="true" [animated]="false" /> `,
|
|
18
|
+
})
|
|
19
|
+
class StatusBarHost {}
|
|
20
|
+
|
|
21
|
+
beforeEach(() => fabric.reset());
|
|
22
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
23
|
+
|
|
24
|
+
describe('StatusBar', () => {
|
|
25
|
+
it('applies status bar props on mount and renders no Fabric node', async () => {
|
|
26
|
+
const spy = vi.spyOn(engine, 'applyStatusBarProps').mockReturnValue(undefined);
|
|
27
|
+
|
|
28
|
+
mount(ROOT_TAG, StatusBarHost);
|
|
29
|
+
await new Promise<void>(resolve => setTimeout(resolve, 0));
|
|
30
|
+
|
|
31
|
+
expect(spy).toHaveBeenCalledWith(
|
|
32
|
+
expect.objectContaining({
|
|
33
|
+
barStyle: 'dark-content',
|
|
34
|
+
hidden: true,
|
|
35
|
+
animated: false,
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const root = fabric.appRoot();
|
|
40
|
+
expect(root.children).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('exposes imperative statics on the component value', () => {
|
|
44
|
+
expect(typeof StatusBar.setHidden).toBe('function');
|
|
45
|
+
expect(typeof StatusBar.setBarStyle).toBe('function');
|
|
46
|
+
expect(typeof StatusBar.setNetworkActivityIndicatorVisible).toBe('function');
|
|
47
|
+
expect(typeof StatusBar.setBackgroundColor).toBe('function');
|
|
48
|
+
expect(typeof StatusBar.setTranslucent).toBe('function');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// StatusBar, the Angular lifecycle half. The native StatusBarManager driving, the imperative
|
|
2
|
+
// statics, and the Android bar-height constant all live in @symbiote-native/engine, shared verbatim with
|
|
3
|
+
// React and Vue; Metro selects the engine's platform variant. Angular supplies only the
|
|
4
|
+
// declarative shape: a component that renders nothing and re-applies the props through
|
|
5
|
+
// ngOnChanges on mount + every prop change. Imperative statics are attached to the component
|
|
6
|
+
// object the same way RN does.
|
|
7
|
+
|
|
8
|
+
import { Component, Input, type OnChanges, type SimpleChanges } from '@angular/core';
|
|
9
|
+
import {
|
|
10
|
+
applyStatusBarProps,
|
|
11
|
+
statusBarImperative,
|
|
12
|
+
statusBarCurrentHeight,
|
|
13
|
+
type IColorValue,
|
|
14
|
+
type IStatusBarProps,
|
|
15
|
+
type IStatusBarStyle,
|
|
16
|
+
} from '@symbiote-native/engine';
|
|
17
|
+
|
|
18
|
+
export type { IStatusBarProps, IStatusBarStyle } from '@symbiote-native/engine';
|
|
19
|
+
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'StatusBar',
|
|
22
|
+
standalone: true,
|
|
23
|
+
template: '',
|
|
24
|
+
})
|
|
25
|
+
class StatusBarComponent implements OnChanges {
|
|
26
|
+
@Input() barStyle?: IStatusBarStyle;
|
|
27
|
+
@Input() hidden?: boolean;
|
|
28
|
+
@Input() animated?: boolean;
|
|
29
|
+
@Input() networkActivityIndicatorVisible?: boolean;
|
|
30
|
+
@Input() backgroundColor?: IColorValue;
|
|
31
|
+
@Input() translucent?: boolean;
|
|
32
|
+
|
|
33
|
+
ngOnChanges(_changes: SimpleChanges): void {
|
|
34
|
+
applyStatusBarProps(this.buildProps());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private buildProps(): IStatusBarProps {
|
|
38
|
+
return {
|
|
39
|
+
barStyle: this.barStyle,
|
|
40
|
+
hidden: this.hidden,
|
|
41
|
+
animated: this.animated,
|
|
42
|
+
networkActivityIndicatorVisible: this.networkActivityIndicatorVisible,
|
|
43
|
+
backgroundColor: this.backgroundColor,
|
|
44
|
+
translucent: this.translucent,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const StatusBarWithStatics = Object.assign(StatusBarComponent, statusBarImperative);
|
|
50
|
+
|
|
51
|
+
Object.defineProperty(StatusBarWithStatics, 'currentHeight', {
|
|
52
|
+
get: statusBarCurrentHeight,
|
|
53
|
+
enumerable: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const StatusBar: typeof StatusBarWithStatics & { readonly currentHeight?: number } =
|
|
57
|
+
StatusBarWithStatics;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Angular primitive host components over Symbiote engine intrinsics. These are the only
|
|
2
|
+
// components that directly own the `symbiote-*` selectors; every composed adapter
|
|
3
|
+
// component imports them and renders them in its template. Declaring `style` as a real
|
|
4
|
+
// Angular input prevents Angular's CSS style engine from decomposing RN `StyleProp` arrays.
|
|
5
|
+
|
|
6
|
+
import { Component } from '@angular/core';
|
|
7
|
+
import { SymbiotePrimitiveHost } from './shared';
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
anchorHostStyle,
|
|
11
|
+
anchorStyleProp,
|
|
12
|
+
stableAnchorStyle,
|
|
13
|
+
SymbioteHostPropsDirective,
|
|
14
|
+
} from './shared';
|
|
15
|
+
|
|
16
|
+
@Component({
|
|
17
|
+
selector: 'symbiote-view, View',
|
|
18
|
+
standalone: true,
|
|
19
|
+
template: '<ng-content></ng-content>',
|
|
20
|
+
})
|
|
21
|
+
export class ViewHost extends SymbiotePrimitiveHost {}
|
|
22
|
+
|
|
23
|
+
@Component({
|
|
24
|
+
selector: 'symbiote-text, Text',
|
|
25
|
+
standalone: true,
|
|
26
|
+
template: '<ng-content></ng-content>',
|
|
27
|
+
})
|
|
28
|
+
export class TextHost extends SymbiotePrimitiveHost {}
|
|
29
|
+
|
|
30
|
+
@Component({
|
|
31
|
+
selector: 'symbiote-image',
|
|
32
|
+
standalone: true,
|
|
33
|
+
template: '<ng-content></ng-content>',
|
|
34
|
+
})
|
|
35
|
+
export class ImageHost extends SymbiotePrimitiveHost {}
|
|
36
|
+
|
|
37
|
+
@Component({
|
|
38
|
+
selector: 'symbiote-scroll-view',
|
|
39
|
+
standalone: true,
|
|
40
|
+
template: '<ng-content></ng-content>',
|
|
41
|
+
})
|
|
42
|
+
export class ScrollViewHost extends SymbiotePrimitiveHost {}
|
|
43
|
+
|
|
44
|
+
@Component({
|
|
45
|
+
selector: 'symbiote-scroll-content',
|
|
46
|
+
standalone: true,
|
|
47
|
+
template: '<ng-content></ng-content>',
|
|
48
|
+
})
|
|
49
|
+
export class ScrollContentView extends SymbiotePrimitiveHost {}
|
|
50
|
+
|
|
51
|
+
@Component({
|
|
52
|
+
selector: 'symbiote-horizontal-scroll-view',
|
|
53
|
+
standalone: true,
|
|
54
|
+
template: '<ng-content></ng-content>',
|
|
55
|
+
})
|
|
56
|
+
export class HorizontalScrollView extends SymbiotePrimitiveHost {}
|
|
57
|
+
|
|
58
|
+
@Component({
|
|
59
|
+
selector: 'symbiote-horizontal-scroll-content',
|
|
60
|
+
standalone: true,
|
|
61
|
+
template: '<ng-content></ng-content>',
|
|
62
|
+
})
|
|
63
|
+
export class HorizontalScrollContentView extends SymbiotePrimitiveHost {}
|
|
64
|
+
|
|
65
|
+
@Component({
|
|
66
|
+
selector: 'symbiote-text-input',
|
|
67
|
+
standalone: true,
|
|
68
|
+
template: '<ng-content></ng-content>',
|
|
69
|
+
})
|
|
70
|
+
export class TextInputHost extends SymbiotePrimitiveHost {}
|
|
71
|
+
|
|
72
|
+
@Component({
|
|
73
|
+
selector: 'symbiote-text-input-multiline',
|
|
74
|
+
standalone: true,
|
|
75
|
+
template: '<ng-content></ng-content>',
|
|
76
|
+
})
|
|
77
|
+
export class MultilineTextInputHost extends SymbiotePrimitiveHost {}
|
|
78
|
+
|
|
79
|
+
@Component({
|
|
80
|
+
selector: 'symbiote-switch',
|
|
81
|
+
standalone: true,
|
|
82
|
+
template: '<ng-content></ng-content>',
|
|
83
|
+
})
|
|
84
|
+
export class SwitchHost extends SymbiotePrimitiveHost {}
|
|
85
|
+
|
|
86
|
+
@Component({
|
|
87
|
+
selector: 'symbiote-activity-indicator',
|
|
88
|
+
standalone: true,
|
|
89
|
+
template: '<ng-content></ng-content>',
|
|
90
|
+
})
|
|
91
|
+
export class ActivityIndicatorHost extends SymbiotePrimitiveHost {}
|
|
92
|
+
|
|
93
|
+
@Component({
|
|
94
|
+
selector: 'symbiote-safe-area-view',
|
|
95
|
+
standalone: true,
|
|
96
|
+
template: '<ng-content></ng-content>',
|
|
97
|
+
})
|
|
98
|
+
export class SafeAreaViewHost extends SymbiotePrimitiveHost {}
|
|
99
|
+
|
|
100
|
+
@Component({
|
|
101
|
+
selector: 'symbiote-modal',
|
|
102
|
+
standalone: true,
|
|
103
|
+
template: '<ng-content></ng-content>',
|
|
104
|
+
})
|
|
105
|
+
export class ModalHost extends SymbiotePrimitiveHost {}
|
|
106
|
+
|
|
107
|
+
@Component({
|
|
108
|
+
selector: 'symbiote-refresh-control',
|
|
109
|
+
standalone: true,
|
|
110
|
+
template: '<ng-content></ng-content>',
|
|
111
|
+
})
|
|
112
|
+
export class RefreshControlHost extends SymbiotePrimitiveHost {}
|
|
113
|
+
|
|
114
|
+
@Component({
|
|
115
|
+
selector: 'symbiote-input-accessory-view',
|
|
116
|
+
standalone: true,
|
|
117
|
+
template: '<ng-content></ng-content>',
|
|
118
|
+
})
|
|
119
|
+
export class InputAccessoryViewHost extends SymbiotePrimitiveHost {}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectorRef,
|
|
3
|
+
Directive,
|
|
4
|
+
ElementRef,
|
|
5
|
+
inject,
|
|
6
|
+
Input,
|
|
7
|
+
type OnChanges,
|
|
8
|
+
Renderer2,
|
|
9
|
+
type SimpleChanges,
|
|
10
|
+
} from '@angular/core';
|
|
11
|
+
import {
|
|
12
|
+
flattenStyle,
|
|
13
|
+
isSymbioteNode,
|
|
14
|
+
type IHostInstance,
|
|
15
|
+
type IStyleProp,
|
|
16
|
+
} from '@symbiote-native/engine';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A composed Angular component (Pressable, SafeAreaView, ScrollView, ...) is created as a
|
|
20
|
+
* non-painting ANCHOR host (`ANCHOR_HOST_COMPONENTS` in renderer.ts) so it doesn't paint an
|
|
21
|
+
* extra native view over its own template's real content. `class="..."`/`[class.x]`/`[ngClass]`
|
|
22
|
+
* at the component's use site ALWAYS resolves through the renderer's addClass/removeClass onto
|
|
23
|
+
* that anchor — Angular gives no `@Input()` interception hook for `class` the way it does for
|
|
24
|
+
* `[style]` (which is why `style` alone was declared as an `@Input()` in the first place, see
|
|
25
|
+
* `SymbiotePrimitiveHost` above) — so the anchor ends up holding the fully-resolved class-derived
|
|
26
|
+
* style (`routeProp`'s `commitClassStyle` already wrote it to `anchor.props.style`) while the
|
|
27
|
+
* REAL native node the component's own template creates never sees it.
|
|
28
|
+
*
|
|
29
|
+
* Call this from a composed component's own `hostProps`/style getter and merge the result AHEAD
|
|
30
|
+
* of its own explicit `style` @Input, e.g. `style: [anchorHostStyle(this.elementRef),
|
|
31
|
+
* this.resolvedStyle]` — array order matters: `flattenStyle`'s later-wins collapse is what makes
|
|
32
|
+
* an explicit `[style]` on the component still beat its ambient class, mirroring the
|
|
33
|
+
* class-loses-to-explicit-style cascade `commitClassStyle` already enforces for a direct
|
|
34
|
+
* primitive. `this.elementRef` must be this component's OWN injected `ElementRef` (its anchor
|
|
35
|
+
* host), not a `@ViewChild` into its template's inner primitive.
|
|
36
|
+
*
|
|
37
|
+
* Skipping this call for a new `ANCHOR_HOST_COMPONENTS` entry compiles clean under `tsc` AND a
|
|
38
|
+
* real `ngc` AOT build — no type error, no template error — it just silently drops `class="..."`
|
|
39
|
+
* forever; only a device/simulator render (or a committed-node style assertion) catches it. See
|
|
40
|
+
* the `angular-adapter` skill §21 for the device-confirmed incident and the full checklist.
|
|
41
|
+
*/
|
|
42
|
+
export function anchorHostStyle(elementRef: ElementRef<unknown>): unknown {
|
|
43
|
+
const node = elementRef.nativeElement;
|
|
44
|
+
return isSymbioteNode(node) ? node.props.style : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// anchorHostStyle returns `unknown` (it reads whatever already resolved onto the anchor node's
|
|
48
|
+
// props bag); a style prop's fields are all optional so any object structurally satisfies it —
|
|
49
|
+
// this narrows just enough (falsy-per-IStyleProp, or an object/array) to fold it into a
|
|
50
|
+
// strictly-typed IStyleProp<T> array without an `as` cast, for a composed component whose own
|
|
51
|
+
// inner primitive declares a real `@Input() style: IStyleProp<SomeConcreteStyle>` (e.g.
|
|
52
|
+
// TouchableOpacity's Pressable, Button's TouchableOpacity) rather than the loosely-typed
|
|
53
|
+
// `symbioteHostProps` bag most composed components merge anchorHostStyle into directly.
|
|
54
|
+
function isStyleValue<T>(value: unknown): value is IStyleProp<T> {
|
|
55
|
+
return (
|
|
56
|
+
value === undefined ||
|
|
57
|
+
value === null ||
|
|
58
|
+
value === false ||
|
|
59
|
+
value === '' ||
|
|
60
|
+
typeof value === 'object'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function anchorStyleProp<T>(elementRef: ElementRef<unknown>): IStyleProp<T> | undefined {
|
|
65
|
+
const value = anchorHostStyle(elementRef);
|
|
66
|
+
return isStyleValue<T>(value) ? value : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
70
|
+
return typeof value === 'object' && value !== null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function shallowStyleEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {
|
|
74
|
+
const aKeys = Object.keys(a);
|
|
75
|
+
if (aKeys.length !== Object.keys(b).length) return false;
|
|
76
|
+
return aKeys.every(key => a[key] === b[key]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* `anchorHostStyle` must be re-read on every check (a bare `class="x"` with no matching
|
|
81
|
+
* `[style]` binding never appears in Angular's `SimpleChanges`/dirty-check inputs, so nothing
|
|
82
|
+
* else signals "the anchor's class-derived style may have changed"). But `flattenStyle([...])`
|
|
83
|
+
* always allocates a FRESH object, even when its content is byte-for-byte identical to last
|
|
84
|
+
* time — and a windowed list component (FlatList/VirtualizedList/SectionList/
|
|
85
|
+
* VirtualizedSectionList) binds its resolved style into a structural-directive-bound `@Input()`
|
|
86
|
+
* chain where a fresh reference on an UNRELATED CD pass is mistaken for a real change, which
|
|
87
|
+
* reschedules ANOTHER CD tick (`VListOutletDirective`'s `context` `@Input()` calling
|
|
88
|
+
* `markForCheck()`) — free-running forever. See the `flat-list-array-style.test.ts` regression
|
|
89
|
+
* this fixes (and the near-identical `lastRecompute` dedup guard in `virtualized-list/index.ts`,
|
|
90
|
+
* which this mirrors at the style-merge call site instead of a whole recompute step). Returns
|
|
91
|
+
* `previous` unchanged when the freshly-flattened merge is shallow-equal to it, so a downstream
|
|
92
|
+
* `@Input() style` binding sees NO change and skips its own update on an unrelated tick.
|
|
93
|
+
*/
|
|
94
|
+
export function stableAnchorStyle(
|
|
95
|
+
elementRef: ElementRef<unknown>,
|
|
96
|
+
explicitStyle: unknown,
|
|
97
|
+
previous: unknown,
|
|
98
|
+
): Record<string, unknown> {
|
|
99
|
+
const next = flattenStyle([anchorHostStyle(elementRef), explicitStyle]);
|
|
100
|
+
return isRecord(previous) && shallowStyleEqual(previous, next) ? previous : next;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const ON_PREFIX = /^on[A-Z]/;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Base for all Angular primitive host components (`symbiote-view`, `symbiote-text`, ...).
|
|
107
|
+
* RN's `StyleProp` can be an object, an array, nested arrays, falsy entries, and Animated
|
|
108
|
+
* values. Angular's raw `[style]` binding on a custom element routes through the CSS style
|
|
109
|
+
* engine (`setStyle` per key), which crashes on numeric array keys. By declaring `style` as
|
|
110
|
+
* an Angular `@Input()` on a known component, we intercept it here and forward a flattened
|
|
111
|
+
* style object through `Renderer2.setProperty`, bypassing Angular's CSS styling path.
|
|
112
|
+
*
|
|
113
|
+
* Other props (source, pointerEvents, ...) are intentionally *not* declared as inputs.
|
|
114
|
+
* Angular then treats them as host property bindings and still dispatches them through the
|
|
115
|
+
* custom renderer's `setProperty`, so the engine's `routeProp` handles them as usual.
|
|
116
|
+
*/
|
|
117
|
+
@Directive()
|
|
118
|
+
export abstract class SymbiotePrimitiveHost implements OnChanges {
|
|
119
|
+
private readonly renderer = inject(Renderer2);
|
|
120
|
+
private readonly elementRef = inject(ElementRef);
|
|
121
|
+
|
|
122
|
+
@Input() style?: IStyleProp<unknown>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The underlying engine host node (public instance). Exposed so `findNodeHandle` can resolve
|
|
126
|
+
* an Angular template ref (`#ref`) on a primitive host to its native reactTag.
|
|
127
|
+
*/
|
|
128
|
+
get nativeElement(): IHostInstance {
|
|
129
|
+
return this.elementRef.nativeElement;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
133
|
+
if (!('style' in changes)) return;
|
|
134
|
+
const value = this.style === undefined ? undefined : flattenStyle(this.style);
|
|
135
|
+
this.renderer.setProperty(this.elementRef.nativeElement, 'style', value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Applies a whole resolved props record (a shared `renderX(...).props` bag) onto a host
|
|
141
|
+
* element via `Renderer2.setProperty`, one call per key. Angular templates compile bindings
|
|
142
|
+
* ahead of time — there is no `v-bind="obj"` equivalent for a custom element — so a component
|
|
143
|
+
* whose props come from a shared render function would otherwise have to declare every key as
|
|
144
|
+
* its own static `[prop]="hostProps['x']"` binding. This generalizes the pattern
|
|
145
|
+
* `SwitchHostPropsDirective` proved first: `<symbiote-x [symbioteHostProps]="hostProps" #ref="symbioteHost">`,
|
|
146
|
+
* with `ref.node` exposing the native element for any imperative follow-up call.
|
|
147
|
+
*/
|
|
148
|
+
@Directive({
|
|
149
|
+
selector: '[symbioteHostProps]',
|
|
150
|
+
standalone: true,
|
|
151
|
+
exportAs: 'symbioteHost',
|
|
152
|
+
inputs: ['symbioteHostProps'],
|
|
153
|
+
})
|
|
154
|
+
export class SymbioteHostPropsDirective {
|
|
155
|
+
private readonly renderer = inject(Renderer2);
|
|
156
|
+
private readonly elementRef = inject<ElementRef<unknown>>(ElementRef);
|
|
157
|
+
// The directive lives in its HOST component's template, so its injected ChangeDetectorRef IS
|
|
158
|
+
// that component's own view detector — the one thing that can refresh it (see wrapCallback).
|
|
159
|
+
private readonly cdr = inject(ChangeDetectorRef);
|
|
160
|
+
|
|
161
|
+
get node(): unknown {
|
|
162
|
+
return this.elementRef.nativeElement;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
set symbioteHostProps(props: Record<string, unknown>) {
|
|
166
|
+
for (const [key, value] of Object.entries(props)) {
|
|
167
|
+
this.renderer.setProperty(this.elementRef.nativeElement, key, this.wrapCallback(key, value));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// A flat-bag `onX` callback (responder negotiation, onLongPress, …) is invoked by the engine's
|
|
172
|
+
// event dispatch — a plain JS call, entirely outside Angular. In Angular 20 a component compiles
|
|
173
|
+
// as a SignalView (not CheckAlways), so a plain (non-signal) state mutation inside such a callback
|
|
174
|
+
// does NOT dirty the view's reactive consumer, and the scheduler's root detectChanges() will NOT
|
|
175
|
+
// descend into it (a Global pass only refreshes CheckAlways / Dirty / RefreshView views) — the
|
|
176
|
+
// "pan does nothing" bug on a nested component. `markForCheck()` flags THIS component's view AND
|
|
177
|
+
// all its ancestors with RefreshView (which survives the descent) and notifies the CD scheduler,
|
|
178
|
+
// so the next root tick repaints the mutation. It targets the directive's injecting view
|
|
179
|
+
// (`_cdRefInjectingView`, i.e. the host component), unlike `detectChanges()` which acts on a
|
|
180
|
+
// resolved `_lView` that is NOT the host component here. The Angular twin of what React/Vue get
|
|
181
|
+
// for free (setState / proxy reactivity). See the `angular-adapter` skill §17.
|
|
182
|
+
private wrapCallback(key: string, value: unknown): unknown {
|
|
183
|
+
if (!ON_PREFIX.test(key) || typeof value !== 'function') return value;
|
|
184
|
+
const handler = value as (...args: unknown[]) => unknown;
|
|
185
|
+
return (...args: unknown[]): unknown => {
|
|
186
|
+
const result = handler(...args);
|
|
187
|
+
this.cdr.markForCheck();
|
|
188
|
+
return result;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Integration coverage: run the real Angular runtime (createComponent + RendererFactory2)
|
|
2
|
+
// over the headless fake Fabric slot. Unlike renderer.test.ts, this proves mount() wires
|
|
3
|
+
// Angular's bootstrap to the Symbiote renderer, so a standalone template paints and updates
|
|
4
|
+
// through the engine.
|
|
5
|
+
|
|
6
|
+
import '@angular/compiler';
|
|
7
|
+
import { Component } from '@angular/core';
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
9
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
10
|
+
import { mount, unmount } from './render';
|
|
11
|
+
|
|
12
|
+
const ROOT_TAG = 808;
|
|
13
|
+
|
|
14
|
+
const fabric = installFabric();
|
|
15
|
+
const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
|
|
16
|
+
const drainAngularAndCommit = async (): Promise<void> => {
|
|
17
|
+
await tick();
|
|
18
|
+
await tick();
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
class TestView {}
|
|
22
|
+
Component({
|
|
23
|
+
selector: 'symbiote-view',
|
|
24
|
+
standalone: true,
|
|
25
|
+
template: '<ng-content></ng-content>',
|
|
26
|
+
})(TestView);
|
|
27
|
+
|
|
28
|
+
class TestText {}
|
|
29
|
+
Component({
|
|
30
|
+
selector: 'symbiote-text',
|
|
31
|
+
standalone: true,
|
|
32
|
+
template: '<ng-content></ng-content>',
|
|
33
|
+
})(TestText);
|
|
34
|
+
|
|
35
|
+
class SmokeComponent {
|
|
36
|
+
name = 'Angular';
|
|
37
|
+
count = 0;
|
|
38
|
+
boxStyle = { padding: 12 };
|
|
39
|
+
|
|
40
|
+
increment(): void {
|
|
41
|
+
this.count += 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Component({
|
|
46
|
+
selector: 'symbiote-angular-smoke',
|
|
47
|
+
standalone: true,
|
|
48
|
+
imports: [TestView, TestText],
|
|
49
|
+
template: `<symbiote-view [style]="boxStyle"><symbiote-text>Hello {{ name }}</symbiote-text><symbiote-view testID="counter" (press)="increment()"><symbiote-text>tapped {{ count }}×</symbiote-text></symbiote-view></symbiote-view>`,
|
|
50
|
+
})(SmokeComponent);
|
|
51
|
+
|
|
52
|
+
beforeEach(() => fabric.reset());
|
|
53
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
54
|
+
|
|
55
|
+
// A plain (non-OnPush) child component compiles as SignalView in Angular 20+, so an UNTOUCHED
|
|
56
|
+
// sibling child is skipped by the per-view CheckAlways/Dirty/RefreshView gate inside
|
|
57
|
+
// detectChangesInView — true with or without ApplicationRef (the gate lives inside
|
|
58
|
+
// detectChangesInView regardless of who calls it). This is NOT a claim that the root's own
|
|
59
|
+
// template is spared: markViewDirty (used by both native (event) bindings and
|
|
60
|
+
// ChangeDetectorRef.markForCheck(), see SymbioteHostPropsDirective) unconditionally sets
|
|
61
|
+
// RefreshView on every ancestor up to the root — a press anywhere always re-runs the root's
|
|
62
|
+
// own template, in every Angular app, regardless of scheduler. What this guards is narrower
|
|
63
|
+
// but still real: a SIBLING component with no dirty descendant of its own must not be dragged
|
|
64
|
+
// along for the ride.
|
|
65
|
+
let unrelatedRenderCount = 0;
|
|
66
|
+
class CounterChild {
|
|
67
|
+
count = 0;
|
|
68
|
+
|
|
69
|
+
increment(): void {
|
|
70
|
+
this.count += 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
Component({
|
|
74
|
+
selector: 'counter-child',
|
|
75
|
+
standalone: true,
|
|
76
|
+
imports: [TestView, TestText],
|
|
77
|
+
template:
|
|
78
|
+
'<symbiote-view testID="counter" (press)="increment()"><symbiote-text>tapped {{ count }}×</symbiote-text></symbiote-view>',
|
|
79
|
+
})(CounterChild);
|
|
80
|
+
|
|
81
|
+
class UnrelatedSiblingChild {
|
|
82
|
+
trackRender(): number {
|
|
83
|
+
unrelatedRenderCount++;
|
|
84
|
+
return unrelatedRenderCount;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
Component({
|
|
88
|
+
selector: 'unrelated-sibling-child',
|
|
89
|
+
standalone: true,
|
|
90
|
+
template: '<symbiote-text>unrelated {{ trackRender() }}</symbiote-text>',
|
|
91
|
+
})(UnrelatedSiblingChild);
|
|
92
|
+
|
|
93
|
+
class TargetedComponent {}
|
|
94
|
+
Component({
|
|
95
|
+
selector: 'symbiote-angular-targeted',
|
|
96
|
+
standalone: true,
|
|
97
|
+
imports: [TestView, CounterChild, UnrelatedSiblingChild],
|
|
98
|
+
template: `<symbiote-view><counter-child /><unrelated-sibling-child /></symbiote-view>`,
|
|
99
|
+
})(TargetedComponent);
|
|
100
|
+
|
|
101
|
+
describe('Angular mount', () => {
|
|
102
|
+
it('bootstraps a standalone component into a committed Fabric tree', async () => {
|
|
103
|
+
mount(ROOT_TAG, SmokeComponent);
|
|
104
|
+
await tick();
|
|
105
|
+
|
|
106
|
+
const root = fabric.appRoot();
|
|
107
|
+
expect(fabric.serialize(root.children)).toBe(
|
|
108
|
+
'RCTView(RCTText(RCTRawText "Hello Angular")RCTView(RCTText(RCTRawText "tapped 0×")))',
|
|
109
|
+
);
|
|
110
|
+
expect(root.children[0]?.props).toMatchObject({ padding: 12 });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('runs Angular change detection after a native press and recommits text', async () => {
|
|
114
|
+
mount(ROOT_TAG, SmokeComponent);
|
|
115
|
+
await tick();
|
|
116
|
+
|
|
117
|
+
const counter = fabric.find(node => node.props.testID === 'counter');
|
|
118
|
+
expect(counter).toBeDefined();
|
|
119
|
+
|
|
120
|
+
fabric.fireEvent(counter?.instanceHandle, 'topTouchStart');
|
|
121
|
+
fabric.fireEvent(counter?.instanceHandle, 'topTouchEnd');
|
|
122
|
+
await drainAngularAndCommit();
|
|
123
|
+
|
|
124
|
+
expect(fabric.serialize(fabric.appRoot().children)).toContain('RCTRawText "tapped 1×"');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('does not re-check a sibling child component on a press inside a different child', async () => {
|
|
128
|
+
unrelatedRenderCount = 0;
|
|
129
|
+
mount(ROOT_TAG, TargetedComponent);
|
|
130
|
+
await tick();
|
|
131
|
+
|
|
132
|
+
const afterFirstPaint = unrelatedRenderCount;
|
|
133
|
+
expect(afterFirstPaint).toBeGreaterThan(0);
|
|
134
|
+
|
|
135
|
+
const counter = fabric.find(node => node.props.testID === 'counter');
|
|
136
|
+
fabric.fireEvent(counter?.instanceHandle, 'topTouchStart');
|
|
137
|
+
fabric.fireEvent(counter?.instanceHandle, 'topTouchEnd');
|
|
138
|
+
await drainAngularAndCommit();
|
|
139
|
+
|
|
140
|
+
expect(fabric.serialize(fabric.appRoot().children)).toContain('RCTRawText "tapped 1×"');
|
|
141
|
+
// UnrelatedSiblingChild has no dirty descendant of its own, so it must not be re-checked
|
|
142
|
+
// just because CounterChild (a completely separate branch) got pressed. See the
|
|
143
|
+
// `angular-adapter` skill §20 for the full investigation, including what this test does
|
|
144
|
+
// NOT prove (the root's own template still re-runs on every press — see render.ts).
|
|
145
|
+
expect(unrelatedRenderCount).toBe(afterFirstPaint);
|
|
146
|
+
});
|
|
147
|
+
});
|