@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,230 @@
|
|
|
1
|
+
// Sticky headers: the Angular twin of adapters/{react,vue}/src/components/scroll-view/
|
|
2
|
+
// sticky-header.{tsx,ts}, the JS layer RN implements in ScrollView.js / ScrollViewStickyHeader.js.
|
|
3
|
+
//
|
|
4
|
+
// Source-based: RN does stickiness PURELY IN JS. A single `_scrollAnimatedValue` an Animated.event
|
|
5
|
+
// drives from `onScroll` feeds each flagged header's translateY through an interpolation that pins
|
|
6
|
+
// it to the top (or bottom, inverted) until the next header collides with it. The native Fabric
|
|
7
|
+
// scroll view does NOT honor `stickyHeaderIndices` on its own. The load-bearing top/inverted
|
|
8
|
+
// interpolation math (computeStickyInterpolation) lives framework-agnostic in @symbiote-native/components
|
|
9
|
+
// (ADR 0024); this file holds the Angular component shell, the layout state, and the interpolation
|
|
10
|
+
// build, sharing the math verbatim with React/Vue. Angular supplies only the lifecycle (inputs +
|
|
11
|
+
// manual change detection instead of useState/useEffect or refs/watch).
|
|
12
|
+
//
|
|
13
|
+
// This is the public sticky-header WRAPPER for explicit composition. ScrollView also auto-wraps
|
|
14
|
+
// projected direct children named by stickyHeaderIndices through its projection bridge (see
|
|
15
|
+
// projection.ts), because Angular templates cannot map <ng-content> children directly; that auto
|
|
16
|
+
// path intentionally uses this built-in wrapper rather than dynamically instantiating arbitrary
|
|
17
|
+
// custom component classes.
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
ChangeDetectionStrategy,
|
|
21
|
+
ChangeDetectorRef,
|
|
22
|
+
Component,
|
|
23
|
+
ElementRef,
|
|
24
|
+
EventEmitter,
|
|
25
|
+
Input,
|
|
26
|
+
Output,
|
|
27
|
+
inject,
|
|
28
|
+
type OnChanges,
|
|
29
|
+
type OnDestroy,
|
|
30
|
+
type OnInit,
|
|
31
|
+
} from '@angular/core';
|
|
32
|
+
import {
|
|
33
|
+
type AnimatedInterpolation,
|
|
34
|
+
AnimatedValue,
|
|
35
|
+
Platform,
|
|
36
|
+
dlog,
|
|
37
|
+
type ISymbioteEvent,
|
|
38
|
+
} from '@symbiote-native/engine';
|
|
39
|
+
import {
|
|
40
|
+
computeStickyInterpolation,
|
|
41
|
+
readLayoutNumber,
|
|
42
|
+
stickyDebounceMs,
|
|
43
|
+
STICKY_HEADER_Z_INDEX,
|
|
44
|
+
type IStickyHeaderProps,
|
|
45
|
+
} from '@symbiote-native/components';
|
|
46
|
+
import { AnimatedView } from '../../modules/animated';
|
|
47
|
+
import { anchorHostStyle } from '../../primitives';
|
|
48
|
+
|
|
49
|
+
// A custom sticky wrapper must accept the same shape the built-in does: the IStickyHeaderProps
|
|
50
|
+
// fields as inputs + the wrapped child via <ng-content>. Unlike React/Vue, Angular ScrollView cannot
|
|
51
|
+
// AOT-safely instantiate an arbitrary component Type while auto-projecting stickyHeaderIndices,
|
|
52
|
+
// because that renderer-level bridge only sees committed engine nodes, not a ViewContainerRef with
|
|
53
|
+
// Angular projectable nodes. This type remains public for explicit composition: compose your wrapper
|
|
54
|
+
// around <ScrollViewStickyHeader>/<symbiote-sticky-header> in the template when custom visuals are
|
|
55
|
+
// needed. Auto projection intentionally uses the built-in wrapper.
|
|
56
|
+
export type IStickyHeaderComponentType = unknown;
|
|
57
|
+
|
|
58
|
+
// One sticky header. Measures its own y/height via onLayout, interpolates the shared scroll offset
|
|
59
|
+
// into a translateY that keeps it pinned to the top (or bottom, inverted) until the next header
|
|
60
|
+
// collides with it, and drives that translate through the native driver when available so the pin
|
|
61
|
+
// tracks scroll on the UI thread (no JS jitter). Ported from ScrollViewStickyHeader.js, including
|
|
62
|
+
// the Fabric ShadowTree debounce path. The inputs (scrollAnimatedValue/nextHeaderLayoutY/…) never
|
|
63
|
+
// reach the host node: they configure the interpolation; only the resolved transform reaches
|
|
64
|
+
// AnimatedView (a scrollAnimatedValue on a host node would crash Android's folly::dynamic).
|
|
65
|
+
@Component({
|
|
66
|
+
selector: 'ScrollViewStickyHeader, symbiote-sticky-header',
|
|
67
|
+
standalone: true,
|
|
68
|
+
imports: [AnimatedView],
|
|
69
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
70
|
+
template: `
|
|
71
|
+
<AnimatedView
|
|
72
|
+
[style]="headerStyle"
|
|
73
|
+
[animatedProps]="headerAnimatedProps"
|
|
74
|
+
[passthroughAnimatedPropExplicitValues]="passthrough"
|
|
75
|
+
>
|
|
76
|
+
<ng-content></ng-content>
|
|
77
|
+
</AnimatedView>
|
|
78
|
+
`,
|
|
79
|
+
})
|
|
80
|
+
export class ScrollViewStickyHeader
|
|
81
|
+
implements Omit<IStickyHeaderProps, 'onLayout'>, OnInit, OnChanges, OnDestroy
|
|
82
|
+
{
|
|
83
|
+
// A fresh fallback AnimatedValue keeps the component working if the parent ever fails to supply
|
|
84
|
+
// one (the wrap invariant); held by IDENTITY in a plain field (Angular does not proxy fields).
|
|
85
|
+
@Input() scrollAnimatedValue: AnimatedValue = new AnimatedValue(0);
|
|
86
|
+
@Input() nextHeaderLayoutY: number | undefined = undefined;
|
|
87
|
+
@Input() inverted: boolean | undefined = undefined;
|
|
88
|
+
@Input() scrollViewHeight: number | undefined = undefined;
|
|
89
|
+
// The parent's layout recorder (RN _onStickyHeaderLayout): records this header's own y so the
|
|
90
|
+
// previous header learns its collision point. Called after the header measures.
|
|
91
|
+
@Output() readonly layout = new EventEmitter<ISymbioteEvent>();
|
|
92
|
+
|
|
93
|
+
private measured = false;
|
|
94
|
+
private layoutY = 0;
|
|
95
|
+
private layoutHeight = 0;
|
|
96
|
+
// The animated node driving the transform (RN's animatedTranslateY). When the scroll value is
|
|
97
|
+
// native (attachStickyScroll), this interpolation runs on the UI thread: the smooth pin.
|
|
98
|
+
private animatedTranslateY: AnimatedInterpolation = new AnimatedValue(0).interpolate({
|
|
99
|
+
inputRange: [-1, 0],
|
|
100
|
+
outputRange: [0, 0],
|
|
101
|
+
});
|
|
102
|
+
// The debounced EXPLICIT translateY pushed to the committed transform via the passthrough, so the
|
|
103
|
+
// Fabric ShadowTree (hit-testing) knows the pinned position while the native driver animates.
|
|
104
|
+
private translateY: number | null = null;
|
|
105
|
+
private haveReceivedInitialZeroTranslateY = true;
|
|
106
|
+
private debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
107
|
+
private listenerId: string | undefined;
|
|
108
|
+
|
|
109
|
+
private readonly changeDetector = inject(ChangeDetectorRef);
|
|
110
|
+
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
111
|
+
// onto (see anchorHostStyle's doc comment in primitives/shared.ts) — NOT the inner AnimatedView
|
|
112
|
+
// one level down.
|
|
113
|
+
private readonly elementRef = inject(ElementRef);
|
|
114
|
+
|
|
115
|
+
ngOnInit(): void {
|
|
116
|
+
this.animatedTranslateY = this.scrollAnimatedValue.interpolate({
|
|
117
|
+
inputRange: [-1, 0],
|
|
118
|
+
outputRange: [0, 0],
|
|
119
|
+
});
|
|
120
|
+
this.rebuildInterpolation();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Rebuild on any layout/collision input change (RN's effect deps: measured, layoutY, layoutHeight,
|
|
124
|
+
// scrollViewHeight, nextHeaderLayoutY, inverted). scrollAnimatedValue is stable for one ScrollView.
|
|
125
|
+
ngOnChanges(): void {
|
|
126
|
+
this.rebuildInterpolation();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ngOnDestroy(): void {
|
|
130
|
+
this.teardownInterpolation();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get headerStyle(): unknown {
|
|
134
|
+
// collapsable:false keeps the wrapper a real Yoga node; zIndex paints the pinned header OVER the
|
|
135
|
+
// rows scrolling under it. The interpolation node rides inside the transform; AnimatedView's
|
|
136
|
+
// reduceProps rasterizes it into a numeric translateY for the committed tree. This component is
|
|
137
|
+
// its own ANCHOR_HOST_COMPONENTS entry, so a `class="..."` at its use site resolves onto
|
|
138
|
+
// `elementRef`, not the inner AnimatedView — merge it back in (anchorHostStyle's doc comment),
|
|
139
|
+
// anchor style first so the fixed transform/zIndex below still wins on conflict.
|
|
140
|
+
return [
|
|
141
|
+
anchorHostStyle(this.elementRef),
|
|
142
|
+
{ transform: [{ translateY: this.animatedTranslateY }], zIndex: STICKY_HEADER_Z_INDEX },
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Stable reference (bound once) so AnimatedView's directive does not re-wire the layout listener
|
|
147
|
+
// every change-detection pass.
|
|
148
|
+
private readonly onHostLayout = (event: ISymbioteEvent): void => this.handleLayout(event);
|
|
149
|
+
|
|
150
|
+
// Also a stable reference, not a getter: jsonEqual (commit.ts) can't structurally compare the
|
|
151
|
+
// embedded onLayout function, so a fresh object here would read as "props changed" on every
|
|
152
|
+
// change-detection pass and force a real Fabric re-clone that cascades up every ancestor to the
|
|
153
|
+
// root (see AnimatedComponentBase.reconcile()'s identical warning) — every unrelated press
|
|
154
|
+
// anywhere in the app, not just this header's own layout events. Not `private`: the template
|
|
155
|
+
// binds it directly (`strictTemplates` requires template-bound members stay accessible).
|
|
156
|
+
readonly headerAnimatedProps: Record<string, unknown> = {
|
|
157
|
+
onLayout: this.onHostLayout,
|
|
158
|
+
collapsable: false,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// The EXPLICIT debounced translateY overrides the committed transform for hit-testing while
|
|
162
|
+
// animatedTranslateY does the smooth (native-driven) pin (RN ScrollViewStickyHeader.js).
|
|
163
|
+
get passthrough(): { style: { transform: Array<{ translateY: number }> } } | null {
|
|
164
|
+
return this.translateY !== null
|
|
165
|
+
? { style: { transform: [{ translateY: this.translateY }] } }
|
|
166
|
+
: null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Record own y/height, mark measured, rebuild the interpolation, then fire the parent's recorder
|
|
170
|
+
// (RN ScrollViewStickyHeader.js._onLayout order). The wrapped child keeps its own onLayout binding
|
|
171
|
+
// (it is projected with its bindings intact), so unlike React/Vue we do not forward to it here.
|
|
172
|
+
private handleLayout(event: ISymbioteEvent): void {
|
|
173
|
+
const y = readLayoutNumber(event, 'y');
|
|
174
|
+
const height = readLayoutNumber(event, 'height');
|
|
175
|
+
if (y !== undefined) this.layoutY = y;
|
|
176
|
+
if (height !== undefined) this.layoutHeight = height;
|
|
177
|
+
this.measured = true;
|
|
178
|
+
this.rebuildInterpolation();
|
|
179
|
+
this.layout.emit(event);
|
|
180
|
+
this.changeDetector.markForCheck();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The animated value updates several times per frame during scroll; debounce it and push the
|
|
184
|
+
// settled value into the committed transform so hit detection stays current (a Fabric-only issue,
|
|
185
|
+
// worse on Android). A freshly-rebuilt interpolation re-emits 0; swallow that first zero (RN).
|
|
186
|
+
private readonly animatedValueListener = ({ value }: { value: number | string }): void => {
|
|
187
|
+
if (typeof value !== 'number') return;
|
|
188
|
+
if (value === 0 && !this.haveReceivedInitialZeroTranslateY) {
|
|
189
|
+
this.haveReceivedInitialZeroTranslateY = true;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (this.debounceTimer !== undefined) clearTimeout(this.debounceTimer);
|
|
193
|
+
this.debounceTimer = setTimeout(() => {
|
|
194
|
+
this.translateY = value;
|
|
195
|
+
if (value !== 0) this.haveReceivedInitialZeroTranslateY = false;
|
|
196
|
+
this.changeDetector.markForCheck();
|
|
197
|
+
}, stickyDebounceMs(Platform.OS));
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
private rebuildInterpolation(): void {
|
|
201
|
+
this.teardownInterpolation();
|
|
202
|
+
const { inputRange, outputRange } = computeStickyInterpolation({
|
|
203
|
+
measured: this.measured,
|
|
204
|
+
inverted: this.inverted,
|
|
205
|
+
scrollViewHeight: this.scrollViewHeight,
|
|
206
|
+
layoutY: this.layoutY,
|
|
207
|
+
layoutHeight: this.layoutHeight,
|
|
208
|
+
nextHeaderLayoutY: this.nextHeaderLayoutY,
|
|
209
|
+
});
|
|
210
|
+
const interpolation = this.scrollAnimatedValue.interpolate({ inputRange, outputRange });
|
|
211
|
+
// symbiote is always Fabric: listen to the settled value to keep the ShadowTree transform
|
|
212
|
+
// current for hit-testing (RN attaches this listener only under Fabric).
|
|
213
|
+
this.listenerId = interpolation.addListener(this.animatedValueListener);
|
|
214
|
+
this.animatedTranslateY = interpolation;
|
|
215
|
+
dlog(
|
|
216
|
+
`Angular ScrollViewStickyHeader interpolation measured=${this.measured} y=${this.layoutY}`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private teardownInterpolation(): void {
|
|
221
|
+
if (this.listenerId !== undefined) {
|
|
222
|
+
this.animatedTranslateY.removeListener(this.listenerId);
|
|
223
|
+
this.listenerId = undefined;
|
|
224
|
+
}
|
|
225
|
+
if (this.debounceTimer !== undefined) {
|
|
226
|
+
clearTimeout(this.debounceTimer);
|
|
227
|
+
this.debounceTimer = undefined;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
// SectionList, the Angular public list-of-sections component. A thin convenience surface over
|
|
2
|
+
// VirtualizedSectionList, mirroring RN's layering (SectionList -> VirtualizedSectionList ->
|
|
3
|
+
// VirtualizedList). All section-flattening / windowing / sticky-header / imperative-scroll logic
|
|
4
|
+
// lives below; this layer re-exposes the same surface under the SectionList name and re-exposes the
|
|
5
|
+
// handle (delegating to the inner VirtualizedSectionList). The Angular twin of
|
|
6
|
+
// adapters/vue/src/components/section-list/index.ts.
|
|
7
|
+
//
|
|
8
|
+
// As in the Vue twin, SectionList is a PURE FORWARDER: its public prop surface IS
|
|
9
|
+
// VirtualizedSectionList's (RN layers them one-for-one), and the SectionList-flavour defaults
|
|
10
|
+
// (stickySectionHeadersEnabled per Platform.OS, the section entry keyExtractor) already live INSIDE
|
|
11
|
+
// VirtualizedSectionList — it applies `stickySectionHeadersEnabled ?? (Platform.OS === 'ios')` in
|
|
12
|
+
// its ngDoCheck and routes keyExtractor through the shared sectionEntryKey. So SectionList forwards
|
|
13
|
+
// each prop unchanged (undefined included) and the default lands one layer down; double-applying
|
|
14
|
+
// here would fight VSL. Vue spreads $attrs to do this in one line — Angular has no attrs-spread, so
|
|
15
|
+
// every prop is forwarded as an explicit @Input binding, except the seven list-lifecycle events
|
|
16
|
+
// (endReached/startReached/refresh/accessibilityAction/accessibilityTap/magicTap/
|
|
17
|
+
// accessibilityEscape), which are real @Output() EventEmitters re-emitted via a listener binding.
|
|
18
|
+
//
|
|
19
|
+
// TEMPLATE FORWARDING — RE-STAMP, the same pattern FlatList's single-column branch was fixed to use
|
|
20
|
+
// (see flat-list/index.ts). A bare `<ng-content></ng-content>` passthrough does NOT let
|
|
21
|
+
// VirtualizedSectionList's own @ContentChild resolve directives across the SECOND projection hop
|
|
22
|
+
// (SectionList's own `<ng-content>` re-projecting content that was actually authored on `<SectionList>`
|
|
23
|
+
// by the app, one level further out) — Angular's content queries resolve against what was projected
|
|
24
|
+
// directly onto the querying component's OWN tag, not transitively through a nested `<ng-content>`
|
|
25
|
+
// relay. So SectionList captures the app's `<ng-template vSectionItem>` / vSectionHeader /
|
|
26
|
+
// vSectionFooter / vSectionSeparator / vListHeader / vListFooter / vListEmpty / vListSeparator with
|
|
27
|
+
// its OWN @ContentChild (a single, direct projection hop — this always resolves) and re-authors
|
|
28
|
+
// equivalent `<ng-template>`s on `<VirtualizedSectionList>`, each forwarding the captured
|
|
29
|
+
// templateRef + context through VListOutletDirective, exactly mirroring how VirtualizedSectionList
|
|
30
|
+
// itself re-stamps its own captured directives onto its inner VirtualizedList.
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
ChangeDetectionStrategy,
|
|
34
|
+
Component,
|
|
35
|
+
ContentChild,
|
|
36
|
+
ElementRef,
|
|
37
|
+
EventEmitter,
|
|
38
|
+
Input,
|
|
39
|
+
Output,
|
|
40
|
+
ViewChild,
|
|
41
|
+
inject,
|
|
42
|
+
} from '@angular/core';
|
|
43
|
+
import type {
|
|
44
|
+
IAccessibilityProps,
|
|
45
|
+
IAccessibilityStateValue,
|
|
46
|
+
IAriaProps,
|
|
47
|
+
IScrollViewHandle,
|
|
48
|
+
ISection,
|
|
49
|
+
IVirtualizedSectionListHandle,
|
|
50
|
+
} from '@symbiote-native/components';
|
|
51
|
+
import {
|
|
52
|
+
type IStyleProp,
|
|
53
|
+
type ISymbioteEvent,
|
|
54
|
+
type ISymbioteNode,
|
|
55
|
+
type IViewStyle,
|
|
56
|
+
} from '@symbiote-native/engine';
|
|
57
|
+
import {
|
|
58
|
+
VListEmptyDirective,
|
|
59
|
+
VListFooterDirective,
|
|
60
|
+
VListHeaderDirective,
|
|
61
|
+
VListSeparatorDirective,
|
|
62
|
+
type IVListSeparatorContext,
|
|
63
|
+
} from '../virtualized-list';
|
|
64
|
+
import { VListOutletDirective } from '../virtualized-list/directives';
|
|
65
|
+
import { stableAnchorStyle } from '../../primitives';
|
|
66
|
+
import {
|
|
67
|
+
VSectionFooterDirective,
|
|
68
|
+
VSectionHeaderDirective,
|
|
69
|
+
VSectionItemDirective,
|
|
70
|
+
VSectionSeparatorDirective,
|
|
71
|
+
VirtualizedSectionList,
|
|
72
|
+
type IVirtualizedSectionListProps,
|
|
73
|
+
} from '../virtualized-section-list';
|
|
74
|
+
|
|
75
|
+
// Angular cannot preserve VListSeparatorDirective<ItemT>'s type parameter across this re-stamp
|
|
76
|
+
// reuse (the directive is matched structurally in SectionList's own template with no explicit type
|
|
77
|
+
// argument to pin ItemT to, so `let-leadingItem`/`let-trailingItem` arrive typed `unknown`) — even
|
|
78
|
+
// though VirtualizedSectionList's own unwrap always supplies a real ItemT value. The narrowest
|
|
79
|
+
// possible I/O-boundary cast for that gap; not a general-purpose unknown-to-T narrowing (mirrors
|
|
80
|
+
// flat-list/index.ts's identical asItem helper).
|
|
81
|
+
function asItem<ItemT>(value: unknown): ItemT | undefined {
|
|
82
|
+
return value as ItemT | undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Re-export the shared section type + the imperative handle so app code (and any later layer) imports
|
|
86
|
+
// them from SectionList, mirroring how the Vue twin re-exposes ISection + ISectionListHandle.
|
|
87
|
+
export type { ISection } from '@symbiote-native/components';
|
|
88
|
+
export type ISectionListHandle = IVirtualizedSectionListHandle;
|
|
89
|
+
|
|
90
|
+
// Re-export the section authoring directives + the list-level slot directives so app code importing
|
|
91
|
+
// from '@symbiote-native/angular' gets the full `<ng-template vSection*>` / `vList*` authoring surface
|
|
92
|
+
// alongside SectionList (mirrors how virtualized-section-list/index re-exports the section ones and
|
|
93
|
+
// FlatList re-exports the list-level ones).
|
|
94
|
+
export {
|
|
95
|
+
VSectionFooterDirective,
|
|
96
|
+
VSectionHeaderDirective,
|
|
97
|
+
VSectionItemDirective,
|
|
98
|
+
VSectionSeparatorDirective,
|
|
99
|
+
} from '../virtualized-section-list';
|
|
100
|
+
export {
|
|
101
|
+
VListEmptyDirective,
|
|
102
|
+
VListFooterDirective,
|
|
103
|
+
VListHeaderDirective,
|
|
104
|
+
VListSeparatorDirective,
|
|
105
|
+
} from '../virtualized-list';
|
|
106
|
+
|
|
107
|
+
// SectionList's public surface is exactly VirtualizedSectionList's (RN layers them one-for-one), so
|
|
108
|
+
// the prop type is shared verbatim — no SectionList-only field exists.
|
|
109
|
+
export type ISectionListProps<ItemT> = IVirtualizedSectionListProps<ItemT>;
|
|
110
|
+
|
|
111
|
+
// What the SectionList component itself takes as plain @Input()s: the full surface minus the
|
|
112
|
+
// list-lifecycle events, which it exposes as real @Output() EventEmitters instead (see the class
|
|
113
|
+
// below) — mirrors IAngularPressableInputs in pressable/index.ts and VirtualizedSectionList's own
|
|
114
|
+
// IVirtualizedSectionListInputs.
|
|
115
|
+
export type ISectionListInputs<ItemT> = Omit<
|
|
116
|
+
ISectionListProps<ItemT>,
|
|
117
|
+
| 'onEndReached'
|
|
118
|
+
| 'onStartReached'
|
|
119
|
+
| 'onRefresh'
|
|
120
|
+
| 'onAccessibilityAction'
|
|
121
|
+
| 'onAccessibilityTap'
|
|
122
|
+
| 'onMagicTap'
|
|
123
|
+
| 'onAccessibilityEscape'
|
|
124
|
+
>;
|
|
125
|
+
|
|
126
|
+
@Component({
|
|
127
|
+
selector: 'SectionList',
|
|
128
|
+
standalone: true,
|
|
129
|
+
imports: [
|
|
130
|
+
VirtualizedSectionList,
|
|
131
|
+
VSectionItemDirective,
|
|
132
|
+
VSectionHeaderDirective,
|
|
133
|
+
VSectionFooterDirective,
|
|
134
|
+
VSectionSeparatorDirective,
|
|
135
|
+
VListHeaderDirective,
|
|
136
|
+
VListFooterDirective,
|
|
137
|
+
VListEmptyDirective,
|
|
138
|
+
VListSeparatorDirective,
|
|
139
|
+
VListOutletDirective,
|
|
140
|
+
],
|
|
141
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
142
|
+
template: `
|
|
143
|
+
<VirtualizedSectionList
|
|
144
|
+
[sections]="sections"
|
|
145
|
+
[keyExtractor]="keyExtractor"
|
|
146
|
+
[stickySectionHeadersEnabled]="stickySectionHeadersEnabled"
|
|
147
|
+
[extraData]="extraData"
|
|
148
|
+
(endReached)="endReached.emit($event)"
|
|
149
|
+
[onEndReachedThreshold]="onEndReachedThreshold"
|
|
150
|
+
(startReached)="startReached.emit($event)"
|
|
151
|
+
[onStartReachedThreshold]="onStartReachedThreshold"
|
|
152
|
+
(refresh)="refresh.emit()"
|
|
153
|
+
[refreshRequested]="refresh.observed"
|
|
154
|
+
[refreshing]="refreshing"
|
|
155
|
+
[progressViewOffset]="progressViewOffset"
|
|
156
|
+
[initialNumToRender]="initialNumToRender"
|
|
157
|
+
[initialScrollIndex]="initialScrollIndex"
|
|
158
|
+
[maxToRenderPerBatch]="maxToRenderPerBatch"
|
|
159
|
+
[updateCellsBatchingPeriod]="updateCellsBatchingPeriod"
|
|
160
|
+
[windowSize]="windowSize"
|
|
161
|
+
[inverted]="inverted"
|
|
162
|
+
[maintainVisibleContentPosition]="maintainVisibleContentPosition"
|
|
163
|
+
[onScroll]="onScroll"
|
|
164
|
+
[onScrollBeginDrag]="onScrollBeginDrag"
|
|
165
|
+
[onScrollEndDrag]="onScrollEndDrag"
|
|
166
|
+
[onMomentumScrollBegin]="onMomentumScrollBegin"
|
|
167
|
+
[onMomentumScrollEnd]="onMomentumScrollEnd"
|
|
168
|
+
[scrollEventThrottle]="scrollEventThrottle"
|
|
169
|
+
[keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
|
|
170
|
+
[keyboardDismissMode]="keyboardDismissMode"
|
|
171
|
+
[style]="resolvedStyle"
|
|
172
|
+
[contentContainerStyle]="contentContainerStyle"
|
|
173
|
+
[testID]="testID"
|
|
174
|
+
[nativeID]="nativeID"
|
|
175
|
+
[accessible]="accessible"
|
|
176
|
+
[accessibilityLabel]="accessibilityLabel"
|
|
177
|
+
[accessibilityHint]="accessibilityHint"
|
|
178
|
+
[accessibilityRole]="accessibilityRole"
|
|
179
|
+
[accessibilityState]="accessibilityState"
|
|
180
|
+
[accessibilityValue]="accessibilityValue"
|
|
181
|
+
[accessibilityActions]="accessibilityActions"
|
|
182
|
+
[accessibilityLabelledBy]="accessibilityLabelledBy"
|
|
183
|
+
[importantForAccessibility]="importantForAccessibility"
|
|
184
|
+
[accessibilityLiveRegion]="accessibilityLiveRegion"
|
|
185
|
+
[screenReaderFocusable]="screenReaderFocusable"
|
|
186
|
+
[accessibilityViewIsModal]="accessibilityViewIsModal"
|
|
187
|
+
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
188
|
+
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
189
|
+
[accessibilityLanguage]="accessibilityLanguage"
|
|
190
|
+
[accessibilityRespondsToUserInteraction]="accessibilityRespondsToUserInteraction"
|
|
191
|
+
[accessibilityShowsLargeContentViewer]="accessibilityShowsLargeContentViewer"
|
|
192
|
+
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
193
|
+
(accessibilityAction)="accessibilityAction.emit($event)"
|
|
194
|
+
(accessibilityTap)="accessibilityTap.emit($event)"
|
|
195
|
+
(magicTap)="magicTap.emit($event)"
|
|
196
|
+
(accessibilityEscape)="accessibilityEscape.emit($event)"
|
|
197
|
+
[role]="role"
|
|
198
|
+
[ariaLabel]="ariaLabel"
|
|
199
|
+
[ariaLabelledBy]="ariaLabelledBy"
|
|
200
|
+
[ariaLive]="ariaLive"
|
|
201
|
+
[ariaHidden]="ariaHidden"
|
|
202
|
+
[ariaBusy]="ariaBusy"
|
|
203
|
+
[ariaChecked]="ariaChecked"
|
|
204
|
+
[ariaDisabled]="ariaDisabled"
|
|
205
|
+
[ariaExpanded]="ariaExpanded"
|
|
206
|
+
[ariaSelected]="ariaSelected"
|
|
207
|
+
[ariaModal]="ariaModal"
|
|
208
|
+
[ariaValueMax]="ariaValueMax"
|
|
209
|
+
[ariaValueMin]="ariaValueMin"
|
|
210
|
+
[ariaValueNow]="ariaValueNow"
|
|
211
|
+
[ariaValueText]="ariaValueText"
|
|
212
|
+
>
|
|
213
|
+
<ng-template
|
|
214
|
+
vSectionItem
|
|
215
|
+
let-item
|
|
216
|
+
let-index="index"
|
|
217
|
+
let-section="section"
|
|
218
|
+
let-separators="separators"
|
|
219
|
+
>
|
|
220
|
+
<ng-container
|
|
221
|
+
[vListOutlet]="sectionItemDir?.templateRef"
|
|
222
|
+
[vListOutletContext]="{ $implicit: item, item, index, section, separators }"
|
|
223
|
+
></ng-container>
|
|
224
|
+
</ng-template>
|
|
225
|
+
@if (sectionHeaderDir !== undefined) {
|
|
226
|
+
<ng-template vSectionHeader let-section>
|
|
227
|
+
<ng-container
|
|
228
|
+
[vListOutlet]="sectionHeaderDir.templateRef"
|
|
229
|
+
[vListOutletContext]="{ $implicit: section, section }"
|
|
230
|
+
></ng-container>
|
|
231
|
+
</ng-template>
|
|
232
|
+
}
|
|
233
|
+
@if (sectionFooterDir !== undefined) {
|
|
234
|
+
<ng-template vSectionFooter let-section>
|
|
235
|
+
<ng-container
|
|
236
|
+
[vListOutlet]="sectionFooterDir.templateRef"
|
|
237
|
+
[vListOutletContext]="{ $implicit: section, section }"
|
|
238
|
+
></ng-container>
|
|
239
|
+
</ng-template>
|
|
240
|
+
}
|
|
241
|
+
@if (sectionSeparatorDir !== undefined) {
|
|
242
|
+
<ng-template vSectionSeparator>
|
|
243
|
+
<ng-container [vListOutlet]="sectionSeparatorDir.templateRef"></ng-container>
|
|
244
|
+
</ng-template>
|
|
245
|
+
}
|
|
246
|
+
@if (listHeaderDir !== undefined) {
|
|
247
|
+
<ng-template vListHeader>
|
|
248
|
+
<ng-container [vListOutlet]="listHeaderDir.templateRef"></ng-container>
|
|
249
|
+
</ng-template>
|
|
250
|
+
}
|
|
251
|
+
@if (listFooterDir !== undefined) {
|
|
252
|
+
<ng-template vListFooter>
|
|
253
|
+
<ng-container [vListOutlet]="listFooterDir.templateRef"></ng-container>
|
|
254
|
+
</ng-template>
|
|
255
|
+
}
|
|
256
|
+
@if (listEmptyDir !== undefined) {
|
|
257
|
+
<ng-template vListEmpty>
|
|
258
|
+
<ng-container [vListOutlet]="listEmptyDir.templateRef"></ng-container>
|
|
259
|
+
</ng-template>
|
|
260
|
+
}
|
|
261
|
+
@if (itemSeparatorDir !== undefined) {
|
|
262
|
+
<ng-template
|
|
263
|
+
vListSeparator
|
|
264
|
+
let-highlighted="highlighted"
|
|
265
|
+
let-leadingItem="leadingItem"
|
|
266
|
+
let-trailingItem="trailingItem"
|
|
267
|
+
>
|
|
268
|
+
<ng-container
|
|
269
|
+
[vListOutlet]="itemSeparatorDir.templateRef"
|
|
270
|
+
[vListOutletContext]="itemSeparatorContext(highlighted, leadingItem, trailingItem)"
|
|
271
|
+
></ng-container>
|
|
272
|
+
</ng-template>
|
|
273
|
+
}
|
|
274
|
+
</VirtualizedSectionList>
|
|
275
|
+
`,
|
|
276
|
+
})
|
|
277
|
+
export class SectionList<ItemT = unknown>
|
|
278
|
+
implements ISectionListInputs<ItemT>, IVirtualizedSectionListHandle
|
|
279
|
+
{
|
|
280
|
+
@Input({ required: true }) sections!: ReadonlyArray<ISection<ItemT>>;
|
|
281
|
+
@Input() keyExtractor?: (item: ItemT, index: number) => string;
|
|
282
|
+
@Input() stickySectionHeadersEnabled?: boolean;
|
|
283
|
+
@Input() extraData?: unknown;
|
|
284
|
+
@Output() readonly endReached = new EventEmitter<{ distanceFromEnd: number }>();
|
|
285
|
+
@Input() onEndReachedThreshold?: number;
|
|
286
|
+
@Output() readonly startReached = new EventEmitter<{ distanceFromStart: number }>();
|
|
287
|
+
@Input() onStartReachedThreshold?: number;
|
|
288
|
+
@Output() readonly refresh = new EventEmitter<void>();
|
|
289
|
+
@Input() refreshing?: boolean | null;
|
|
290
|
+
@Input() progressViewOffset?: number;
|
|
291
|
+
@Input() initialNumToRender?: number;
|
|
292
|
+
@Input() initialScrollIndex?: number;
|
|
293
|
+
@Input() maxToRenderPerBatch?: number;
|
|
294
|
+
@Input() updateCellsBatchingPeriod?: number;
|
|
295
|
+
@Input() windowSize?: number;
|
|
296
|
+
@Input() inverted?: boolean;
|
|
297
|
+
@Input() maintainVisibleContentPosition?: {
|
|
298
|
+
minIndexForVisible: number;
|
|
299
|
+
autoscrollToTopThreshold?: number;
|
|
300
|
+
};
|
|
301
|
+
@Input() onScroll?: (event: ISymbioteEvent) => void;
|
|
302
|
+
@Input() onScrollBeginDrag?: (event: ISymbioteEvent) => void;
|
|
303
|
+
@Input() onScrollEndDrag?: (event: ISymbioteEvent) => void;
|
|
304
|
+
@Input() onMomentumScrollBegin?: (event: ISymbioteEvent) => void;
|
|
305
|
+
@Input() onMomentumScrollEnd?: (event: ISymbioteEvent) => void;
|
|
306
|
+
@Input() scrollEventThrottle?: number;
|
|
307
|
+
@Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
|
|
308
|
+
@Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
|
|
309
|
+
@Input() style?: IStyleProp<IViewStyle>;
|
|
310
|
+
@Input() contentContainerStyle?: IStyleProp<IViewStyle>;
|
|
311
|
+
@Input() testID?: string;
|
|
312
|
+
@Input() nativeID?: string;
|
|
313
|
+
|
|
314
|
+
// Bound to the template's `[style]="resolvedStyle"`, which Angular compiles to the built-in
|
|
315
|
+
// ɵɵstyleMap instruction — it only understands a flat object, never an array (RN's own
|
|
316
|
+
// `style={[a, b]}` composition idiom crashes deep inside Angular's styling engine), so this
|
|
317
|
+
// flattens `style` via the engine's own flattenStyle before it ever reaches that binding.
|
|
318
|
+
// anchorHostStyle merges in this component's OWN anchor's class-derived style (see its doc
|
|
319
|
+
// comment) — `elementRef` below is SectionList's OWN host, not `list`'s inner VirtualizedSectionList.
|
|
320
|
+
// A plain `flattenStyle([...])` here would allocate a FRESH object on every getter read (every CD
|
|
321
|
+
// check), which — bound onto the nested VirtualizedSectionList/VirtualizedList's own `@Input()
|
|
322
|
+
// style` — defeats VirtualizedList's `ngDoCheck` dedup gate and free-runs change detection
|
|
323
|
+
// forever (the exact bug `stableAnchorStyle` exists to prevent, see its doc comment and the
|
|
324
|
+
// `flat-list-array-style.test.ts` regression it fixes). `cachedResolvedStyle` is the getter's own
|
|
325
|
+
// persisted "previous" value across reads, since a getter has no natural field to compare against.
|
|
326
|
+
private cachedResolvedStyle: Record<string, unknown> | undefined;
|
|
327
|
+
get resolvedStyle(): IViewStyle {
|
|
328
|
+
this.cachedResolvedStyle = stableAnchorStyle(
|
|
329
|
+
this.elementRef,
|
|
330
|
+
this.style,
|
|
331
|
+
this.cachedResolvedStyle,
|
|
332
|
+
);
|
|
333
|
+
return this.cachedResolvedStyle;
|
|
334
|
+
}
|
|
335
|
+
@Input() accessible?: boolean;
|
|
336
|
+
@Input() accessibilityLabel?: string;
|
|
337
|
+
@Input() accessibilityHint?: string;
|
|
338
|
+
@Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
|
|
339
|
+
@Input() accessibilityState?: IAccessibilityStateValue;
|
|
340
|
+
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
341
|
+
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
342
|
+
@Input() accessibilityLabelledBy?: string | string[];
|
|
343
|
+
@Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
344
|
+
@Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
345
|
+
@Input() screenReaderFocusable?: boolean;
|
|
346
|
+
@Input() accessibilityViewIsModal?: boolean;
|
|
347
|
+
@Input() accessibilityElementsHidden?: boolean;
|
|
348
|
+
@Input() accessibilityIgnoresInvertColors?: boolean;
|
|
349
|
+
@Input() accessibilityLanguage?: string;
|
|
350
|
+
@Input() accessibilityRespondsToUserInteraction?: boolean;
|
|
351
|
+
@Input() accessibilityShowsLargeContentViewer?: boolean;
|
|
352
|
+
@Input() accessibilityLargeContentTitle?: string;
|
|
353
|
+
@Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
|
|
354
|
+
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
355
|
+
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
356
|
+
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
357
|
+
@Input() role?: IAriaProps['role'];
|
|
358
|
+
@Input() ariaLabel?: string;
|
|
359
|
+
@Input() ariaLabelledBy?: string;
|
|
360
|
+
@Input() ariaLive?: IAriaProps['aria-live'];
|
|
361
|
+
@Input() ariaHidden?: boolean;
|
|
362
|
+
@Input() ariaBusy?: boolean;
|
|
363
|
+
@Input() ariaChecked?: boolean | 'mixed';
|
|
364
|
+
@Input() ariaDisabled?: boolean;
|
|
365
|
+
@Input() ariaExpanded?: boolean;
|
|
366
|
+
@Input() ariaSelected?: boolean;
|
|
367
|
+
@Input() ariaModal?: boolean;
|
|
368
|
+
@Input() ariaValueMax?: number;
|
|
369
|
+
@Input() ariaValueMin?: number;
|
|
370
|
+
@Input() ariaValueNow?: number;
|
|
371
|
+
@Input() ariaValueText?: string;
|
|
372
|
+
|
|
373
|
+
// The app's section/list authoring templates, captured from the app's DIRECT projected content
|
|
374
|
+
// (single hop — always resolves) and re-stamped onto the inner VirtualizedSectionList above.
|
|
375
|
+
@ContentChild(VSectionItemDirective) sectionItemDir?: VSectionItemDirective<ItemT>;
|
|
376
|
+
@ContentChild(VSectionHeaderDirective) sectionHeaderDir?: VSectionHeaderDirective<ItemT>;
|
|
377
|
+
@ContentChild(VSectionFooterDirective) sectionFooterDir?: VSectionFooterDirective<ItemT>;
|
|
378
|
+
@ContentChild(VSectionSeparatorDirective) sectionSeparatorDir?: VSectionSeparatorDirective;
|
|
379
|
+
@ContentChild(VListHeaderDirective) listHeaderDir?: VListHeaderDirective;
|
|
380
|
+
@ContentChild(VListFooterDirective) listFooterDir?: VListFooterDirective;
|
|
381
|
+
@ContentChild(VListEmptyDirective) listEmptyDir?: VListEmptyDirective;
|
|
382
|
+
@ContentChild(VListSeparatorDirective) itemSeparatorDir?: VListSeparatorDirective<ItemT>;
|
|
383
|
+
|
|
384
|
+
// The composed inner VirtualizedSectionList. Its instance IS an IVirtualizedSectionListHandle, so
|
|
385
|
+
// SectionList's handle delegates straight to it. Available from ngAfterViewInit; reads lazily.
|
|
386
|
+
@ViewChild(VirtualizedSectionList) private list?: VirtualizedSectionList<ItemT>;
|
|
387
|
+
|
|
388
|
+
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
389
|
+
// onto (see anchorHostStyle's doc comment) — NOT `list` above, which targets the real inner
|
|
390
|
+
// `<VirtualizedSectionList>` one level down (itself its own separate anchor host).
|
|
391
|
+
private readonly elementRef = inject(ElementRef);
|
|
392
|
+
|
|
393
|
+
// The item separator context: leadingItem/trailingItem arrive `unknown` from the template's `let`
|
|
394
|
+
// bindings (see the asItem boundary comment above), already-unwrapped real items (no envelope to
|
|
395
|
+
// narrow through, unlike a section header/footer entry).
|
|
396
|
+
itemSeparatorContext(
|
|
397
|
+
highlighted: unknown,
|
|
398
|
+
leadingItem: unknown,
|
|
399
|
+
trailingItem: unknown,
|
|
400
|
+
): IVListSeparatorContext<ItemT> {
|
|
401
|
+
const isHighlighted = highlighted === true;
|
|
402
|
+
return {
|
|
403
|
+
$implicit: isHighlighted,
|
|
404
|
+
highlighted: isHighlighted,
|
|
405
|
+
leadingItem: asItem<ItemT>(leadingItem),
|
|
406
|
+
trailingItem: asItem<ItemT>(trailingItem),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ---- imperative handle (the shared IVirtualizedSectionListHandle surface) — delegates down ----
|
|
411
|
+
|
|
412
|
+
scrollToLocation(params: {
|
|
413
|
+
sectionIndex: number;
|
|
414
|
+
itemIndex: number;
|
|
415
|
+
viewOffset?: number;
|
|
416
|
+
viewPosition?: number;
|
|
417
|
+
animated?: boolean;
|
|
418
|
+
}): void {
|
|
419
|
+
this.list?.scrollToLocation(params);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
flashScrollIndicators(): void {
|
|
423
|
+
this.list?.flashScrollIndicators();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
getNativeScrollRef(): IScrollViewHandle | null {
|
|
427
|
+
return this.list?.getNativeScrollRef() ?? null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
getScrollableNode(): IScrollViewHandle | null {
|
|
431
|
+
return this.list?.getScrollableNode() ?? null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
getScrollResponder(): IScrollViewHandle | null {
|
|
435
|
+
return this.list?.getScrollResponder() ?? null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
getScrollNode(): ISymbioteNode | null {
|
|
439
|
+
return this.list?.getScrollNode() ?? null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
recordInteraction(): void {
|
|
443
|
+
this.list?.recordInteraction();
|
|
444
|
+
}
|
|
445
|
+
}
|