@symbiote-native/angular 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/symbiote-angular-dev.cjs +20 -19
- package/build/angular/anchor-host-registry.js +2 -2
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/components/activity-indicator/index.android.d.ts +2 -1
- package/build/angular/components/activity-indicator/index.android.js +6 -3
- package/build/angular/components/activity-indicator/index.android.js.map +1 -1
- package/build/angular/components/activity-indicator/index.ios.d.ts +2 -1
- package/build/angular/components/activity-indicator/index.ios.js +6 -3
- package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
- package/build/angular/components/activity-indicator/shared.js +3 -3
- package/build/angular/components/button.d.ts +4 -2
- package/build/angular/components/button.js +28 -17
- package/build/angular/components/button.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +5 -3
- package/build/angular/components/flat-list/index.js +35 -38
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image/index.android.d.ts +8 -3
- package/build/angular/components/image/index.android.js +39 -9
- package/build/angular/components/image/index.android.js.map +1 -1
- package/build/angular/components/image/index.ios.d.ts +8 -3
- package/build/angular/components/image/index.ios.js +39 -9
- package/build/angular/components/image/index.ios.js.map +1 -1
- package/build/angular/components/image/shared.d.ts +4 -1
- package/build/angular/components/image/shared.js +33 -2
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/image-background.d.ts +2 -1
- package/build/angular/components/image-background.js +6 -4
- package/build/angular/components/image-background.js.map +1 -1
- package/build/angular/components/input-accessory-view/index.d.ts +9 -4
- package/build/angular/components/input-accessory-view/index.js +43 -9
- package/build/angular/components/input-accessory-view/index.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +2 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +6 -4
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +11 -8
- package/build/angular/components/modal/index.js +45 -28
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable/index.d.ts +11 -5
- package/build/angular/components/pressable/index.js +61 -12
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control/index.d.ts +8 -4
- package/build/angular/components/refresh-control/index.js +52 -16
- package/build/angular/components/refresh-control/index.js.map +1 -1
- package/build/angular/components/safe-area-view/index.d.ts +9 -4
- package/build/angular/components/safe-area-view/index.js +46 -9
- package/build/angular/components/safe-area-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/index.android.d.ts +2 -3
- package/build/angular/components/scroll-view/index.android.js +49 -52
- package/build/angular/components/scroll-view/index.android.js.map +1 -1
- package/build/angular/components/scroll-view/index.ios.d.ts +2 -3
- package/build/angular/components/scroll-view/index.ios.js +15 -17
- package/build/angular/components/scroll-view/index.ios.js.map +1 -1
- package/build/angular/components/scroll-view/projection.d.ts +3 -0
- package/build/angular/components/scroll-view/projection.js +68 -7
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.d.ts +14 -5
- package/build/angular/components/scroll-view/shared.js +198 -69
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.js +14 -19
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +2 -1
- package/build/angular/components/section-list/index.js +31 -39
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/switch/index.android.d.ts +7 -3
- package/build/angular/components/switch/index.android.js +38 -10
- package/build/angular/components/switch/index.android.js.map +1 -1
- package/build/angular/components/switch/index.ios.d.ts +7 -3
- package/build/angular/components/switch/index.ios.js +38 -10
- package/build/angular/components/switch/index.ios.js.map +1 -1
- package/build/angular/components/switch/shared.d.ts +12 -3
- package/build/angular/components/switch/shared.js +66 -5
- package/build/angular/components/switch/shared.js.map +1 -1
- package/build/angular/components/text-input.d.ts +10 -2
- package/build/angular/components/text-input.js +46 -6
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +17 -8
- package/build/angular/components/touchable/index.js +78 -19
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +6 -3
- package/build/angular/components/touchable-native-feedback/index.js +61 -25
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +18 -18
- package/build/angular/components/virtualized-list/index.d.ts +12 -4
- package/build/angular/components/virtualized-list/index.js +163 -68
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js +12 -12
- package/build/angular/components/virtualized-section-list/index.d.ts +2 -1
- package/build/angular/components/virtualized-section-list/index.js +6 -4
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +1 -1
- package/build/angular/components.js +5 -0
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.js +29 -35
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js +19 -22
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +3 -3
- package/build/angular/index.d.ts +4 -2
- package/build/angular/index.js +2 -2
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/animated-leaf-binder.d.ts +1 -7
- package/build/angular/modules/animated/animated-leaf-binder.js +22 -72
- package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +11 -6
- package/build/angular/modules/animated/create-animated-component.js +83 -50
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/app-registry/index.js.map +1 -1
- package/build/angular/modules/status-bar/index.js +3 -3
- package/build/angular/primitives/index.d.ts +1 -1
- package/build/angular/primitives/index.js +46 -46
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +39 -1
- package/build/angular/primitives/shared.js +110 -9
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/render/index.js +23 -35
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/services/color-scheme.service.js +3 -3
- package/build/angular/services/window-dimensions.service.js +3 -3
- package/package.json +13 -8
- package/src/anchor-host-registry.ts +2 -2
- package/src/components/activity-indicator/index.android.ts +2 -0
- package/src/components/activity-indicator/index.ios.ts +2 -0
- package/src/components/button.ts +28 -14
- package/src/components/flat-list/index.ts +34 -36
- package/src/components/image/index.android.ts +50 -6
- package/src/components/image/index.ios.ts +50 -6
- package/src/components/image/shared.ts +34 -2
- package/src/components/image-background.ts +2 -1
- package/src/components/input-accessory-view/index.ts +42 -4
- package/src/components/keyboard-avoiding-view/index.ts +7 -1
- package/src/components/modal/index.ts +51 -26
- package/src/components/pressable/index.ts +67 -8
- package/src/components/refresh-control/index.ts +54 -12
- package/src/components/safe-area-view/index.ts +50 -5
- package/src/components/scroll-view/index.android.ts +34 -41
- package/src/components/scroll-view/index.ios.ts +10 -21
- package/src/components/scroll-view/projection.ts +79 -7
- package/src/components/scroll-view/shared.ts +223 -68
- package/src/components/scroll-view/sticky-header.ts +11 -16
- package/src/components/section-list/index.ts +27 -36
- package/src/components/switch/index.android.ts +48 -7
- package/src/components/switch/index.ios.ts +48 -7
- package/src/components/switch/shared.ts +79 -3
- package/src/components/text-input.ts +52 -2
- package/src/components/touchable/index.ts +76 -16
- package/src/components/touchable-native-feedback/index.ts +62 -21
- package/src/components/virtualized-list/index.ts +137 -44
- package/src/components/virtualized-section-list/index.ts +2 -1
- package/src/components.ts +5 -0
- package/src/create-portal/index.ts +23 -29
- package/src/create-tunnel/index.ts +13 -16
- package/src/index.ts +30 -1
- package/src/modules/animated/animated-leaf-binder.ts +28 -77
- package/src/modules/animated/create-animated-component.ts +75 -36
- package/src/modules/app-registry/index.ts +6 -8
- package/src/primitives/index.ts +1 -0
- package/src/primitives/shared.ts +102 -3
- package/src/render/index.ts +23 -35
- package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +0 -14
- package/build/angular/modules/animated/animated-leaf-binder/index.js +0 -91
- package/build/angular/modules/animated/animated-leaf-binder/index.js.map +0 -1
- package/src/__tests__/responder-change-detection.test.ts +0 -102
- package/src/__tests__/responder-nested-cd.test.ts +0 -113
- package/src/angular-gaps.test.ts +0 -142
- package/src/angular-imports.test.ts +0 -100
- package/src/components/activity-indicator/activity-indicator-class-style.test.ts +0 -44
- package/src/components/activity-indicator/activity-indicator.test.ts +0 -131
- package/src/components/flat-list/column-wrapper-style-class.test.ts +0 -116
- package/src/components/flat-list/flat-list-array-style.test.ts +0 -131
- package/src/components/flat-list/flat-list-class-style.test.ts +0 -74
- package/src/components/flat-list/flat-list-scroll-containment.test.ts +0 -107
- package/src/components/flat-list/flat-list.test.ts +0 -99
- package/src/components/image/image-class-style.test.ts +0 -46
- package/src/components/image-background-class-style.test.ts +0 -48
- package/src/components/image-background-style-class.test.ts +0 -79
- package/src/components/input-accessory-view/input-accessory-view.test.ts +0 -43
- package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +0 -54
- package/src/components/modal/modal.test.ts +0 -43
- package/src/components/pressable/pressable.test.ts +0 -85
- package/src/components/refresh-control/refresh-control.test.ts +0 -41
- package/src/components/safe-area-view/safe-area-view.test.ts +0 -43
- package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +0 -123
- package/src/components/scroll-view/content-container-style-class.test.ts +0 -75
- package/src/components/scroll-view/scroll-view-class-style.test.ts +0 -134
- package/src/components/scroll-view/scroll-view-projection.test.ts +0 -194
- package/src/components/switch/android-switch-class.test.ts +0 -42
- package/src/components/switch/switch.test.ts +0 -92
- package/src/components/text-input-class-style.test.ts +0 -54
- package/src/components/touchable/touchable.test.ts +0 -102
- package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +0 -49
- package/src/components/virtualized-list/virtualized-list-class-style.test.ts +0 -76
- package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +0 -60
- package/src/create-portal/create-portal.test.ts +0 -90
- package/src/create-tunnel/create-tunnel.test.ts +0 -120
- package/src/descriptor-to-angular/descriptor-outlet.test.ts +0 -94
- package/src/host-instance/host-instance.test.ts +0 -49
- package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +0 -116
- package/src/modules/animated/animated-leaf-binder/index.ts +0 -101
- package/src/modules/animated/animated-leaf-binder.test.ts +0 -116
- package/src/modules/animated/animated-scroll-view.test.ts +0 -102
- package/src/modules/app-registry/app-registry.test.ts +0 -106
- package/src/modules/status-bar/status-bar.test.ts +0 -50
- package/src/render/render.test.ts +0 -147
- package/src/renderer/renderer.test.ts +0 -203
- package/src/services/services.test.ts +0 -163
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
// ScrollView, the Angular lifecycle half. The Fabric tree is nested: a scroll view
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// flashScrollIndicators), the content-size dedupe, and the native sticky scroll-attach wired
|
|
9
|
-
// through whenCommitted. This is the Angular twin of the React useRef + Vue shallowRef host.
|
|
1
|
+
// ScrollView, the Angular lifecycle half. The Fabric tree is nested: a scroll view wraps a
|
|
2
|
+
// content view holding the children (RN's ScrollView.js shape). The platform-invariant math
|
|
3
|
+
// (decelerationRate, per-axis intrinsics, content-size dedupe, imperative handle, native sticky
|
|
4
|
+
// scroll-attach, aria/role fold) lives in @symbiote-native/components, shared with React/Vue.
|
|
5
|
+
// Angular supplies only the lifecycle: the host node held by IDENTITY through a directive's
|
|
6
|
+
// ElementRef, a forwarded-prop bag set via Renderer2 (-> routeProp), and the native sticky attach
|
|
7
|
+
// wired through whenCommitted.
|
|
10
8
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
9
|
+
// RefreshControl integration diverges per platform (.ios/.android, Metro filename-selected): iOS
|
|
10
|
+
// renders it as a SIBLING before the content container; Android WRAPS the scroll view in an
|
|
11
|
+
// AndroidSwipeRefreshLayout.
|
|
14
12
|
//
|
|
15
|
-
// Angular cannot transform <ng-content> children
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// (RN's Android shape), while excluding the original projected node from the content slot.
|
|
13
|
+
// Angular cannot transform <ng-content> children the way React children.map / Vue slots() can, so
|
|
14
|
+
// ScrollView owns a projection bridge: the content host registers a ScrollViewProjectionController
|
|
15
|
+
// with the Angular renderer, which wraps projected children whose indices match
|
|
16
|
+
// stickyHeaderIndices. Android RefreshControl projection re-renders the projected component as the
|
|
17
|
+
// wrapper around the scroll view (RN's Android shape), excluding it from the content slot.
|
|
21
18
|
|
|
22
19
|
import {
|
|
23
20
|
Directive,
|
|
21
|
+
computed,
|
|
22
|
+
signal,
|
|
24
23
|
ElementRef,
|
|
25
24
|
EventEmitter,
|
|
26
25
|
Output,
|
|
27
26
|
ViewChild,
|
|
28
27
|
ContentChild,
|
|
29
28
|
inject,
|
|
29
|
+
type AfterContentChecked,
|
|
30
30
|
type AfterViewInit,
|
|
31
|
+
type DoCheck,
|
|
32
|
+
type OnChanges,
|
|
31
33
|
type OnDestroy,
|
|
32
34
|
} from '@angular/core';
|
|
33
35
|
import {
|
|
@@ -60,7 +62,7 @@ import {
|
|
|
60
62
|
type ISymbioteNode,
|
|
61
63
|
type IViewStyle,
|
|
62
64
|
} from '@symbiote-native/engine';
|
|
63
|
-
import { SymbioteHostPropsDirective } from '../../primitives';
|
|
65
|
+
import { anchorHostStyle, SymbioteHostPropsDirective } from '../../primitives';
|
|
64
66
|
import { RefreshControl } from '../refresh-control';
|
|
65
67
|
import { ScrollViewProjectionController } from './projection';
|
|
66
68
|
|
|
@@ -158,12 +160,10 @@ export const SCROLL_VIEW_INPUTS = [
|
|
|
158
160
|
'aria-valuetext',
|
|
159
161
|
];
|
|
160
162
|
|
|
161
|
-
// The Angular-facing prop surface
|
|
162
|
-
// ReactElement refreshControl)
|
|
163
|
-
// RefreshControl through projection
|
|
164
|
-
//
|
|
165
|
-
// field can't live in a shared agnostic type. Every prop is accepted and typed against the full
|
|
166
|
-
// React surface so app code type-checks against parity now.
|
|
163
|
+
// The Angular-facing prop surface: React's IScrollViewProps minus the React-coupled fields
|
|
164
|
+
// (ReactNode children, ReactElement refreshControl) — Angular takes children via <ng-content> and
|
|
165
|
+
// composes RefreshControl through projection instead. Declared per-adapter over the shared
|
|
166
|
+
// accessibility base since a framework element/ref field can't live in a shared agnostic type.
|
|
167
167
|
export interface IAngularScrollViewProps extends IAccessibilityProps, IAriaProps {
|
|
168
168
|
style?: IStyleProp<IViewStyle>;
|
|
169
169
|
// A bare string resolves through the shared style registry, like `class` on the host node.
|
|
@@ -287,7 +287,15 @@ export class ScrollViewProjectionDirective {
|
|
|
287
287
|
// ngtsc rejects the inherited hooks/queries (NG2007). The concrete platform @Components add ONLY a
|
|
288
288
|
// decorator + their platform template; all behavior lives here.
|
|
289
289
|
@Directive()
|
|
290
|
-
export abstract class ScrollViewBase
|
|
290
|
+
export abstract class ScrollViewBase
|
|
291
|
+
implements
|
|
292
|
+
IAngularScrollViewInputs,
|
|
293
|
+
AfterContentChecked,
|
|
294
|
+
AfterViewInit,
|
|
295
|
+
DoCheck,
|
|
296
|
+
OnChanges,
|
|
297
|
+
OnDestroy
|
|
298
|
+
{
|
|
291
299
|
style: IStyleProp<IViewStyle> | undefined;
|
|
292
300
|
contentContainerStyle: IStyleProp<IViewStyle> | string | undefined;
|
|
293
301
|
horizontal: boolean | undefined;
|
|
@@ -393,6 +401,20 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
393
401
|
|
|
394
402
|
@ContentChild(RefreshControl) protected projectedRefreshControl?: RefreshControl;
|
|
395
403
|
|
|
404
|
+
// This component's OWN anchor host — where a `class="..."` at the use site resolves (see
|
|
405
|
+
// anchorHostStyle's doc comment) — NOT `#host` in the platform templates, which targets the real
|
|
406
|
+
// inner scroll-view primitive one level down. Injected in the BASE because both platform
|
|
407
|
+
// subclasses merged the identical anchor style into their `scrollProps` override.
|
|
408
|
+
private readonly elementRef = inject<ElementRef<unknown>>(ElementRef);
|
|
409
|
+
|
|
410
|
+
// The anchor's class-derived style is written by the RENDERER (addClass/removeClass ->
|
|
411
|
+
// commitClassStyle), never through an @Input, so `inputsRevision` cannot cover it and a bag
|
|
412
|
+
// memoized on plain reads of it would keep a stale class forever. It gets its own signal, polled
|
|
413
|
+
// in ngDoCheck below: that hook runs on exactly the checks that could have changed it (Angular
|
|
414
|
+
// applies the parent's class bindings before flushing this directive's pre-order hooks), which is
|
|
415
|
+
// the same cadence the old `scrollProps` getter was re-read at.
|
|
416
|
+
protected readonly anchorStyle = signal<unknown>(undefined);
|
|
417
|
+
|
|
396
418
|
// A single AnimatedValue tracks the scroll offset and drives every sticky header's translateY
|
|
397
419
|
// (RN's _scrollAnimatedValue). A stable field (allocated once, held by identity — Angular does
|
|
398
420
|
// not proxy class fields, so no markRaw needed); the native attach below feeds it on the UI
|
|
@@ -404,6 +426,9 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
404
426
|
private lastContentSize: IContentSize | null = null;
|
|
405
427
|
// Inverted sticky headers stick to the BOTTOM, so they need the viewport height (RN _handleLayout).
|
|
406
428
|
private viewportHeight: number | undefined = undefined;
|
|
429
|
+
// What the projection controller was last told about the projected RefreshControl, so
|
|
430
|
+
// ngAfterContentChecked can tell a real content change from an ordinary check.
|
|
431
|
+
private lastHasProjectedRefreshControl = false;
|
|
407
432
|
|
|
408
433
|
protected readonly projectionController = new ScrollViewProjectionController({
|
|
409
434
|
stickyHeaderIndices: undefined,
|
|
@@ -421,6 +446,9 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
421
446
|
// bind runs now if committed, else after the commit that assigns the tag. Detached on destroy.
|
|
422
447
|
private detachStickyScroll: (() => void) | undefined;
|
|
423
448
|
private cancelStickyBind: (() => void) | undefined;
|
|
449
|
+
// What the current attach was made for, so a re-run that changes nothing stays a no-op.
|
|
450
|
+
private stickyAttachedEnabled = false;
|
|
451
|
+
private stickyAttachedNode: ISymbioteNode | null = null;
|
|
424
452
|
|
|
425
453
|
private lastLoggedIsHorizontal: boolean | undefined = undefined;
|
|
426
454
|
|
|
@@ -433,7 +461,14 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
433
461
|
// (see AnimatedComponentBase.reconcile()'s identical warning).
|
|
434
462
|
private readonly handleInvertedStickyLayout = (event: ISymbioteEvent): void => {
|
|
435
463
|
const height = readLayoutDimension(event, 'height');
|
|
436
|
-
|
|
464
|
+
// The projection controller is pushed from here rather than picked up on the next prop-bag
|
|
465
|
+
// read: viewportHeight is internal mutable state, so once `scrollProps` became a memoized
|
|
466
|
+
// computed nothing would re-read it. Guarded on a real change - an onLayout that reports the
|
|
467
|
+
// same height must not cost a sticky reconcile walk.
|
|
468
|
+
if (height !== undefined && height !== this.viewportHeight) {
|
|
469
|
+
this.viewportHeight = height;
|
|
470
|
+
this.updateProjectionController();
|
|
471
|
+
}
|
|
437
472
|
this.layout.emit(event);
|
|
438
473
|
};
|
|
439
474
|
|
|
@@ -449,14 +484,11 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
449
484
|
this.contentSizeChange.emit({ width, height });
|
|
450
485
|
};
|
|
451
486
|
|
|
452
|
-
// Memoizes the wrapper each `emitterCallback` builds, keyed by the emitter it closes over
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
// level further removed. `.observed` is checked BEFORE the cache so an unbound event still
|
|
458
|
-
// resolves to `undefined` (the "nobody cares" contract `emitterCallback`'s own comment documents),
|
|
459
|
-
// not a stale cached wrapper from before the last subscriber unsubscribed.
|
|
487
|
+
// Memoizes the wrapper each `emitterCallback` builds, keyed by the emitter it closes over.
|
|
488
|
+
// Without this, `scrollProps`/`refreshControlProps` would hand a fresh closure to the forwarded
|
|
489
|
+
// prop bag on every evaluation — the same jsonEqual/Object.is function-leaf problem as the arrow
|
|
490
|
+
// fields above. `.observed` is checked BEFORE the cache so an unbound event still resolves to
|
|
491
|
+
// `undefined` rather than a stale wrapper from before the last unsubscribe.
|
|
460
492
|
private readonly emitterCallbackCache = new WeakMap<
|
|
461
493
|
EventEmitter<ISymbioteEvent>,
|
|
462
494
|
IScrollHandler
|
|
@@ -500,12 +532,35 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
500
532
|
.scrollViewBaseStyle;
|
|
501
533
|
}
|
|
502
534
|
|
|
535
|
+
// Bridges non-reactive @Input fields into the reactive graph, so a computed() can memoize a
|
|
536
|
+
// bag derived from them. Signal inputs would make this unnecessary, but `input()` is visible
|
|
537
|
+
// only to the AOT compiler and this package's unit suite runs on JIT - see the
|
|
538
|
+
// `angular-adapter-change-detection` skill, §6. `signal()`/`computed()` themselves are plain
|
|
539
|
+
// runtime APIs and need no compiler support, which is what makes this bridge possible at all.
|
|
540
|
+
//
|
|
541
|
+
// ONLY safe for a bag whose every dependency is an @Input. A bag that also reads internal
|
|
542
|
+
// mutable state (viewportHeight, lastContentSize, the anchor's class-derived style) would go
|
|
543
|
+
// stale, because nothing bumps this for those - make that state its own signal (see
|
|
544
|
+
// `anchorStyle`) instead of widening this one.
|
|
545
|
+
private readonly inputsRevision = signal(0);
|
|
546
|
+
|
|
503
547
|
// The forwarded host-prop bag for the scroll-view node: aria/role folded, the native pass-through
|
|
504
548
|
// families, the scroll-event callbacks, then the lifecycle-managed overrides (nestedScrollEnabled
|
|
505
549
|
// default ON, horizontal when defined, decelerationRate resolved per-platform, the sticky onScroll
|
|
506
550
|
// path). Base style UNDER user style so an explicit user value still wins. Mirrors Vue's outerProps
|
|
507
551
|
// + scrollProps assembly.
|
|
508
|
-
|
|
552
|
+
//
|
|
553
|
+
// MEASURED 2026-08-16: as a getter this rebuilt the whole bag on every refresh of this view, so
|
|
554
|
+
// the `symbioteHostProps` reference check failed every time and every key was re-pushed through
|
|
555
|
+
// the renderer - once per scroll frame per host binding (2 before `contentProps` was memoized,
|
|
556
|
+
// 1 after, 0 now; the pin lives in prop-bag-stability.test.ts).
|
|
557
|
+
//
|
|
558
|
+
// It is a `computed` on the BASE and no longer overridable: a subclass getter cannot override a
|
|
559
|
+
// field. The platform subclasses used to override it with byte-identical bodies (merge the
|
|
560
|
+
// anchor's class-derived style ahead of the resolved style), so that merge moved down here
|
|
561
|
+
// rather than into a `decorateScrollProps` hook both of them would implement the same way.
|
|
562
|
+
readonly scrollProps = computed<Record<string, unknown>>(() => {
|
|
563
|
+
this.inputsRevision();
|
|
509
564
|
const bag: Record<string, unknown> = compact({
|
|
510
565
|
...resolveAccessibilityProps(this.accessibilityInputs()),
|
|
511
566
|
scrollEnabled: this.scrollEnabled,
|
|
@@ -563,14 +618,11 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
563
618
|
}
|
|
564
619
|
|
|
565
620
|
// onScroll: when sticky headers are active, the offset must reach the AnimatedValue (RN
|
|
566
|
-
// _scrollAnimatedValueAttachment). With the native module, the
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
// capture the viewport height) are folded out to the shared resolveScrollForwarding; here Angular
|
|
572
|
-
// only EXECUTES them, keeping its stable-reference handlers (stickyOnScrollHandler /
|
|
573
|
-
// handleInvertedStickyLayout / emitterCallback) so a fresh closure never forces a re-clone cascade.
|
|
621
|
+
// _scrollAnimatedValueAttachment). With the native module, the post-commit attach below drives
|
|
622
|
+
// it on the UI thread (throttle 1, ScrollView.js:1798); without it, Animated.event drives the
|
|
623
|
+
// value each frame (JS jitter). The DECISIONS (which path, throttle defaults, whether to
|
|
624
|
+
// capture viewport height) are folded out to the shared resolveScrollForwarding; Angular only
|
|
625
|
+
// EXECUTES them, keeping stable-reference handlers so a fresh closure never forces a re-clone.
|
|
574
626
|
const nativeStickyAvailable = this.hasStickyHeaders && isNativeAnimatedAvailable();
|
|
575
627
|
const forwarding = resolveScrollForwarding({
|
|
576
628
|
hasStickyHeaders: this.hasStickyHeaders,
|
|
@@ -599,41 +651,62 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
599
651
|
if (layoutCallback !== undefined) bag.onLayout = layoutCallback;
|
|
600
652
|
}
|
|
601
653
|
|
|
654
|
+
// Thunks, not strings: an eagerly built message would be paid with logging off (see dlog's own
|
|
655
|
+
// comment). Less often than it used to fire now that this bag is memoized, but the recompute
|
|
656
|
+
// still rides every real input change.
|
|
657
|
+
dlog(
|
|
658
|
+
() =>
|
|
659
|
+
`Angular ScrollView -> ${this.isHorizontal ? 'horizontal' : 'vertical'} (sticky=${this.hasStickyHeaders})`,
|
|
660
|
+
);
|
|
602
661
|
dlog(
|
|
603
|
-
|
|
662
|
+
() =>
|
|
663
|
+
`STICKY[sv] forwarding mode=${forwarding.mode} throttle=${forwarding.scrollEventThrottle} ` +
|
|
664
|
+
`nativeStickyAvailable=${nativeStickyAvailable} onScrollBound=${bag.onScroll !== undefined}`,
|
|
604
665
|
);
|
|
605
666
|
|
|
606
|
-
|
|
607
|
-
|
|
667
|
+
// The anchor's class-derived style goes FIRST: flattenStyle's later-wins collapse keeps an
|
|
668
|
+
// explicit [style] winning over the ambient class. Flat rather than the nested
|
|
669
|
+
// `[anchor, [base, style]]` the platform overrides used to build - flattenStyle folds nested
|
|
670
|
+
// arrays in the same order, so the resolved value is unchanged.
|
|
671
|
+
bag.style = [this.anchorStyle(), this.scrollViewBaseStyle, this.style];
|
|
608
672
|
return bag;
|
|
609
|
-
}
|
|
673
|
+
});
|
|
610
674
|
|
|
611
675
|
// Overridable hook, NOT `this.style` directly: `splitLayoutProps` below decides which layout
|
|
612
676
|
// properties (flex/height/gap/…) go on the Android outer refresh-control wrapper vs. the inner
|
|
613
|
-
// scroll view. `this.style` alone only
|
|
614
|
-
// component's
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
// `layoutSplitStyle` field and the real Android device bug it fixes; the Angular Android
|
|
619
|
-
// ScrollView (index.android.ts) overrides this to merge in `anchorHostStyle`. Default here is
|
|
620
|
-
// `this.style` alone, preserving current behavior for anything that doesn't override it.
|
|
677
|
+
// scroll view. `this.style` alone only carries the explicit `[style]` @Input — a composed
|
|
678
|
+
// component's own anchor host can hold ADDITIONAL class-derived layout style invisible to this
|
|
679
|
+
// split, so the wrapper collapses to zero size on real Android devices. Angular's Android
|
|
680
|
+
// ScrollView (index.android.ts) overrides this to merge in `anchorHostStyle` (mirrors Vue's
|
|
681
|
+
// identical `layoutSplitStyle` field and the device bug it fixes).
|
|
621
682
|
protected get layoutSplitStyle(): IStyleProp<IViewStyle> {
|
|
622
683
|
return this.style;
|
|
623
684
|
}
|
|
624
685
|
|
|
625
|
-
|
|
626
|
-
|
|
686
|
+
// Memoizable for the same reasons `scrollProps` is: `layoutSplitStyle` resolves to @Input `style`
|
|
687
|
+
// plus (on Android) the `anchorStyle` signal, both covered. The anchor style is deliberately NOT
|
|
688
|
+
// re-merged into `props.style` here - `splitLayoutProps` already routed its layout half to the
|
|
689
|
+
// outer refresh-control wrapper and left the rest in `inner`.
|
|
690
|
+
readonly androidWrappedScrollProps = computed<Record<string, unknown>>(() => {
|
|
691
|
+
this.inputsRevision();
|
|
692
|
+
const props = { ...this.scrollProps() };
|
|
627
693
|
const { inner, outer } = splitLayoutProps(this.layoutSplitStyle);
|
|
628
694
|
dlog(
|
|
629
|
-
|
|
695
|
+
() =>
|
|
696
|
+
`Angular ScrollView splitProbe layoutSplitStyle=${JSON.stringify(this.layoutSplitStyle)} ` +
|
|
630
697
|
`outer=${JSON.stringify(outer)} inner=${JSON.stringify(inner)}`,
|
|
631
698
|
);
|
|
632
699
|
props.style = [this.scrollViewBaseStyle, inner];
|
|
633
700
|
props.nestedScrollEnabled = true;
|
|
634
701
|
return props;
|
|
635
|
-
}
|
|
702
|
+
});
|
|
636
703
|
|
|
704
|
+
// Deliberately still GETTERS, unlike every other bag on this class. Their whole content comes
|
|
705
|
+
// from the PROJECTED RefreshControl's own @Input()s (refreshing, tintColor, colors, …), which
|
|
706
|
+
// Angular routes through THAT component's ngOnChanges - `inputsRevision` here never moves for
|
|
707
|
+
// them. Memoizing on it would freeze `refreshing` at its first value and strand the native
|
|
708
|
+
// spinner forever, which is exactly the silent-staleness class this file avoids elsewhere.
|
|
709
|
+
// Making them memoizable means giving RefreshControl its own revision signal to read here.
|
|
637
710
|
get iosRefreshControlProps(): Record<string, unknown> {
|
|
638
711
|
return this.refreshControlProps();
|
|
639
712
|
}
|
|
@@ -641,7 +714,8 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
641
714
|
get androidRefreshControlProps(): Record<string, unknown> {
|
|
642
715
|
const { outer } = splitLayoutProps(this.layoutSplitStyle);
|
|
643
716
|
dlog(
|
|
644
|
-
|
|
717
|
+
() =>
|
|
718
|
+
`Angular ScrollView refreshControlProbe outer=${JSON.stringify(outer)} ` +
|
|
645
719
|
`hasRefresh=${this.projectedRefreshControl !== undefined}`,
|
|
646
720
|
);
|
|
647
721
|
return this.refreshControlProps(outer);
|
|
@@ -685,7 +759,8 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
685
759
|
// children of a scroll view that hosts exactly one — an addViewAt crash). collapsableChildren
|
|
686
760
|
// false preserves the cell views maintainVisibleContentPosition/snapToAlignment anchor against.
|
|
687
761
|
// onLayout synthesizes onContentSizeChange (deduped). iOS never flattens; both are no-ops there.
|
|
688
|
-
|
|
762
|
+
readonly contentProps = computed<Record<string, unknown>>(() => {
|
|
763
|
+
this.inputsRevision();
|
|
689
764
|
const { contentStyle } = selectScrollIntrinsics(
|
|
690
765
|
this.isHorizontal,
|
|
691
766
|
this.resolvedContentContainerStyle,
|
|
@@ -696,13 +771,18 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
696
771
|
}
|
|
697
772
|
bag.onLayout = this.handleContentLayout;
|
|
698
773
|
return bag;
|
|
699
|
-
}
|
|
774
|
+
});
|
|
700
775
|
|
|
701
776
|
// Wraps an @Output() as a plain callback only while it has a subscriber, so an unbound event
|
|
702
777
|
// still leaves the forwarded prop bag key absent — the same "undefined means nobody cares"
|
|
703
778
|
// contract the old @Input() callbacks had. Mirrors Pressable's emitterHandler. The wrapper itself
|
|
704
779
|
// is memoized per emitter (emitterCallbackCache) rather than rebuilt on every call: see that
|
|
705
780
|
// field's comment for why an unstable closure here breaks the forwarded prop bag.
|
|
781
|
+
//
|
|
782
|
+
// `.observed` is NOT reactive, and `scrollProps` memoizes over it. That is sound because Angular
|
|
783
|
+
// subscribes a template's (event) listeners in its CREATION pass, before any input is written and
|
|
784
|
+
// before this component's own view is first refreshed - so `observed` is already settled at the
|
|
785
|
+
// first evaluation and cannot flip afterwards without destroying the whole component.
|
|
706
786
|
private emitterCallback(emitter: EventEmitter<ISymbioteEvent>): IScrollHandler | undefined {
|
|
707
787
|
if (!emitter.observed) return undefined;
|
|
708
788
|
let cached = this.emitterCallbackCache.get(emitter);
|
|
@@ -762,23 +842,98 @@ export abstract class ScrollViewBase implements IAngularScrollViewInputs, AfterV
|
|
|
762
842
|
}
|
|
763
843
|
|
|
764
844
|
ngAfterViewInit(): void {
|
|
845
|
+
dlog('STICKY[sv] ngAfterViewInit');
|
|
846
|
+
// Runs once the template has bound the content node, so this is the reconcile that actually
|
|
847
|
+
// wraps the sticky children (the ngOnChanges one above fires before the content node exists).
|
|
848
|
+
this.updateProjectionController();
|
|
849
|
+
this.attachSticky();
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Re-read the anchor's class-derived style. It is written by the renderer's addClass/removeClass,
|
|
853
|
+
// never through an @Input, so this poll is the only thing that can invalidate a bag holding it -
|
|
854
|
+
// ngOnChanges alone would strand a class toggled after mount (covered by
|
|
855
|
+
// scroll-view-class-style.test.ts's toggle case, which goes red the moment this hook stops
|
|
856
|
+
// polling). ngDoCheck is the right hook, not a near-enough one: render3's `callHooks` flushes a
|
|
857
|
+
// node's pre-order hooks at the next `ɵɵadvance` PAST it, which is after the parent wrote this
|
|
858
|
+
// element's `ɵɵclassProp` and before this component's own view refreshes. It also runs under
|
|
859
|
+
// `setActiveConsumer(null)`, so the write registers no dependency on the caller's view and cannot
|
|
860
|
+
// throw NG0600. And `commitClassStyle` allocates a fresh style array only when a class token
|
|
861
|
+
// actually moved, so `signal.set`'s own Object.is check makes an unchanged poll dirty nothing.
|
|
862
|
+
ngDoCheck(): void {
|
|
863
|
+
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// The projected RefreshControl is a @ContentChild, not an @Input, so ngOnChanges never fires when
|
|
867
|
+
// one is added or removed at the use site - and `excludeRefreshControl` decides whether the
|
|
868
|
+
// projection walk pulls it out of the scroll content. Angular resolves the query before this
|
|
869
|
+
// hook. Guarded on the flag itself so a check that changed nothing skips the reconcile walk,
|
|
870
|
+
// which the old getter-driven update could not do.
|
|
871
|
+
ngAfterContentChecked(): void {
|
|
872
|
+
if (this.hasProjectedRefreshControl === this.lastHasProjectedRefreshControl) return;
|
|
873
|
+
this.lastHasProjectedRefreshControl = this.hasProjectedRefreshControl;
|
|
874
|
+
this.updateProjectionController();
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// `stickyHeaderIndices` is an ordinary @Input, so it can arrive AFTER the first change-detection
|
|
878
|
+
// pass - any app deriving it from data rather than writing a literal in the template does exactly
|
|
879
|
+
// that. ngAfterViewInit runs once, so without this the native attach below would be skipped on
|
|
880
|
+
// that first pass and never retried: the wrapper still projects (that half self-heals), the
|
|
881
|
+
// header sits at the right place with the right z-index, and simply never moves. Device-reported
|
|
882
|
+
// 2026-08-16, regression-covered in sticky-native-attach.test.ts. The other three adapters never
|
|
883
|
+
// had this hole because they re-run the attach from a reactive effect keyed on the same
|
|
884
|
+
// condition (Svelte's `$effect` in index.svelte, React's useEffect deps, Vue's watcher).
|
|
885
|
+
ngOnChanges(): void {
|
|
886
|
+
dlog('STICKY[sv] ngOnChanges');
|
|
887
|
+
// Every computed() prop bag below reads plain @Input fields, which are NOT reactive on their
|
|
888
|
+
// own - this bump is what tells them an input changed. It must stay in ngOnChanges: that is
|
|
889
|
+
// the single moment Angular has finished writing every changed input for this pass.
|
|
890
|
+
this.inputsRevision.update(revision => revision + 1);
|
|
891
|
+
// stickyHeaderIndices / invertStickyHeaders / StickyHeaderComponent are @Inputs, so this is
|
|
892
|
+
// where the projection controller learns about them. It used to be a side effect of reading
|
|
893
|
+
// the `scrollProps` getter - which a memoized computed no longer performs per pass.
|
|
765
894
|
this.updateProjectionController();
|
|
766
895
|
this.attachSticky();
|
|
767
896
|
}
|
|
768
897
|
|
|
769
|
-
// Wire the native sticky-scroll attach
|
|
770
|
-
//
|
|
898
|
+
// Wire the native sticky-scroll attach once the view exists AND sticky is actually on. Re-runs
|
|
899
|
+
// on every input change, so it must be idempotent: see the guard below.
|
|
771
900
|
private attachSticky(): void {
|
|
901
|
+
const wantsAttach = this.hasStickyHeaders && isNativeAnimatedAvailable();
|
|
902
|
+
const node = wantsAttach ? this.hostNode : null;
|
|
903
|
+
dlog(
|
|
904
|
+
() =>
|
|
905
|
+
`STICKY[sv] attachSticky indices=${JSON.stringify(this.stickyHeaderIndices)} ` +
|
|
906
|
+
`hasSticky=${this.hasStickyHeaders} nativeAvailable=${isNativeAnimatedAvailable()} ` +
|
|
907
|
+
`wantsAttach=${wantsAttach} hasNode=${node !== null} ` +
|
|
908
|
+
`wasEnabled=${this.stickyAttachedEnabled} nodeChanged=${node !== this.stickyAttachedNode}`,
|
|
909
|
+
);
|
|
910
|
+
// Nothing that decides the attach has changed - return rather than detach and rebind the
|
|
911
|
+
// native scroll event, which every unrelated @Input change would otherwise churn now that
|
|
912
|
+
// ngOnChanges drives this too.
|
|
913
|
+
if (wantsAttach === this.stickyAttachedEnabled && node === this.stickyAttachedNode) {
|
|
914
|
+
dlog('STICKY[sv] attachSticky skipped (nothing about the attach changed)');
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
772
918
|
this.cancelStickyBind?.();
|
|
773
919
|
this.cancelStickyBind = undefined;
|
|
774
920
|
if (this.detachStickyScroll !== undefined) {
|
|
775
921
|
this.detachStickyScroll();
|
|
776
922
|
this.detachStickyScroll = undefined;
|
|
777
923
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
924
|
+
// Recorded even when the node is not resolved yet, so the NEXT call (which will see a real
|
|
925
|
+
// node) reads as a change and attaches.
|
|
926
|
+
this.stickyAttachedEnabled = wantsAttach;
|
|
927
|
+
this.stickyAttachedNode = node;
|
|
928
|
+
if (!wantsAttach || node === null) {
|
|
929
|
+
dlog(
|
|
930
|
+
`STICKY[sv] attachSticky NOT attaching (wantsAttach=${wantsAttach} hasNode=${node !== null})`,
|
|
931
|
+
);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
dlog('STICKY[sv] attachSticky waiting for commit to bind the native scroll event');
|
|
781
935
|
this.cancelStickyBind = whenCommitted(node, () => {
|
|
936
|
+
dlog('STICKY[sv] attachSticky node committed -> attachStickyScroll');
|
|
782
937
|
this.detachStickyScroll = attachStickyScroll(node, this.scrollAnimatedValue);
|
|
783
938
|
});
|
|
784
939
|
}
|
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
// Sticky headers:
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// interpolation math (computeStickyInterpolation) lives framework-agnostic in @symbiote-native/components
|
|
9
|
-
// 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).
|
|
1
|
+
// Sticky headers: RN does stickiness PURELY IN JS (ScrollView.js / ScrollViewStickyHeader.js).
|
|
2
|
+
// A single `_scrollAnimatedValue` an Animated.event drives from `onScroll` feeds each flagged
|
|
3
|
+
// header's translateY through an interpolation that pins it to the top (or bottom, inverted)
|
|
4
|
+
// until the next header collides with it — the native Fabric scroll view does NOT honor
|
|
5
|
+
// `stickyHeaderIndices` on its own. The interpolation math (computeStickyInterpolation) lives
|
|
6
|
+
// framework-agnostic in @symbiote-native/components, shared verbatim with React/Vue; this file
|
|
7
|
+
// holds the Angular component shell, layout state, and interpolation build.
|
|
12
8
|
//
|
|
13
9
|
// This is the public sticky-header WRAPPER for explicit composition. ScrollView also auto-wraps
|
|
14
|
-
// projected
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// custom component classes.
|
|
10
|
+
// projected children named by stickyHeaderIndices through its projection bridge (projection.ts),
|
|
11
|
+
// because Angular templates cannot map <ng-content> children directly; that auto path
|
|
12
|
+
// intentionally reuses this built-in wrapper rather than instantiating arbitrary component types.
|
|
18
13
|
|
|
19
14
|
import {
|
|
20
15
|
ChangeDetectionStrategy,
|
|
@@ -198,7 +193,7 @@ export class ScrollViewStickyHeader
|
|
|
198
193
|
this.interpolation = next;
|
|
199
194
|
this.animatedTranslateY = next;
|
|
200
195
|
dlog(
|
|
201
|
-
`
|
|
196
|
+
`STICKY[header] interpolation measured=${this.state.measured} y=${this.state.layoutY}`,
|
|
202
197
|
);
|
|
203
198
|
break;
|
|
204
199
|
}
|
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
// SectionList, the Angular public list-of-sections component. A thin convenience surface over
|
|
2
2
|
// VirtualizedSectionList, mirroring RN's layering (SectionList -> VirtualizedSectionList ->
|
|
3
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
|
|
5
|
-
// handle
|
|
6
|
-
// adapters/vue/src/components/section-list/index.ts.
|
|
4
|
+
// lives below; this layer re-exposes the same surface under the SectionList name and delegates
|
|
5
|
+
// the handle to the inner VirtualizedSectionList.
|
|
7
6
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
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
|
|
7
|
+
// SectionList is a PURE FORWARDER: its public prop surface IS VirtualizedSectionList's (RN layers
|
|
8
|
+
// them one-for-one), and the SectionList-flavour defaults (stickySectionHeadersEnabled per
|
|
9
|
+
// Platform.OS, the section entry keyExtractor) already live INSIDE VirtualizedSectionList — double-
|
|
10
|
+
// applying them here would fight it. Angular has no attrs-spread (unlike Vue's $attrs), so every
|
|
11
|
+
// prop is forwarded as an explicit @Input binding, except the seven list-lifecycle events
|
|
16
12
|
// (endReached/startReached/refresh/accessibilityAction/accessibilityTap/magicTap/
|
|
17
13
|
// accessibilityEscape), which are real @Output() EventEmitters re-emitted via a listener binding.
|
|
18
14
|
//
|
|
19
|
-
// TEMPLATE FORWARDING — RE-STAMP, the same pattern FlatList's single-column branch
|
|
20
|
-
//
|
|
21
|
-
// VirtualizedSectionList's own @ContentChild resolve directives across
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// templateRef + context through VListOutletDirective, exactly mirroring how VirtualizedSectionList
|
|
30
|
-
// itself re-stamps its own captured directives onto its inner VirtualizedList.
|
|
15
|
+
// TEMPLATE FORWARDING — RE-STAMP, the same pattern FlatList's single-column branch uses (see
|
|
16
|
+
// flat-list/index.ts). A bare `<ng-content></ng-content>` passthrough does NOT let
|
|
17
|
+
// VirtualizedSectionList's own @ContentChild resolve directives across a SECOND projection hop —
|
|
18
|
+
// Angular's content queries resolve only against what was projected directly onto the querying
|
|
19
|
+
// component's OWN tag. So SectionList captures the app's `<ng-template vSectionItem>` /
|
|
20
|
+
// vSectionHeader / vSectionFooter / vSectionSeparator / vListHeader / vListFooter / vListEmpty /
|
|
21
|
+
// vListSeparator with its OWN @ContentChild (a single, direct hop — always resolves) and
|
|
22
|
+
// re-authors equivalent `<ng-template>`s on `<VirtualizedSectionList>`, forwarding the captured
|
|
23
|
+
// templateRef + context through VListOutletDirective — mirroring how VirtualizedSectionList itself
|
|
24
|
+
// re-stamps its own captured directives onto its inner VirtualizedList.
|
|
31
25
|
|
|
32
26
|
import {
|
|
33
27
|
ChangeDetectionStrategy,
|
|
@@ -62,7 +56,7 @@ import {
|
|
|
62
56
|
type IVListSeparatorContext,
|
|
63
57
|
} from '../virtualized-list';
|
|
64
58
|
import { VListOutletDirective } from '../virtualized-list/directives';
|
|
65
|
-
import { stableAnchorStyle } from '../../primitives';
|
|
59
|
+
import { stableAnchorStyle, SymbioteStyleInputDirective } from '../../primitives';
|
|
66
60
|
import {
|
|
67
61
|
VSectionFooterDirective,
|
|
68
62
|
VSectionHeaderDirective,
|
|
@@ -126,6 +120,7 @@ export type ISectionListInputs<ItemT> = Omit<
|
|
|
126
120
|
@Component({
|
|
127
121
|
selector: 'SectionList',
|
|
128
122
|
standalone: true,
|
|
123
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
129
124
|
imports: [
|
|
130
125
|
VirtualizedSectionList,
|
|
131
126
|
VSectionItemDirective,
|
|
@@ -311,18 +306,14 @@ export class SectionList<ItemT = unknown>
|
|
|
311
306
|
@Input() testID?: string;
|
|
312
307
|
@Input() nativeID?: string;
|
|
313
308
|
|
|
314
|
-
// Bound to
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
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.
|
|
309
|
+
// Bound to `[style]="resolvedStyle"`, which Angular compiles to ɵɵstyleMap — it only
|
|
310
|
+
// understands a flat object, never an array (RN's `style={[a, b]}` idiom crashes deep inside
|
|
311
|
+
// Angular's styling engine), so this flattens `style` first, merging in this component's OWN
|
|
312
|
+
// anchor's class-derived style via anchorHostStyle (`elementRef` here is SectionList's own
|
|
313
|
+
// host, not `list`'s inner VirtualizedSectionList). A plain `flattenStyle([...])` would
|
|
314
|
+
// allocate a fresh object on every CD check, defeating VirtualizedList's `ngDoCheck` dedup gate
|
|
315
|
+
// and free-running change detection forever — the bug `stableAnchorStyle` exists to prevent
|
|
316
|
+
// (see its doc comment). `cachedResolvedStyle` is the getter's own persisted "previous" value.
|
|
326
317
|
private cachedResolvedStyle: Record<string, unknown> | undefined;
|
|
327
318
|
get resolvedStyle(): IViewStyle {
|
|
328
319
|
this.cachedResolvedStyle = stableAnchorStyle(
|