@symbiote-native/angular 0.6.2 → 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/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 +9 -7
- 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/modules/animated/animated-leaf-binder/index.ts +0 -101
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
// ScrollView on Android. An Android ScrollView accepts only ONE child, so a
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// ScrollView on Android. An Android ScrollView accepts only ONE child, so a RefreshControl can't
|
|
2
|
+
// be a sibling of the content the way iOS allows ("addViewAt: failed to insert view ... at index
|
|
3
|
+
// 1"). RN solves this by having the RefreshControl (AndroidSwipeRefreshLayout) WRAP the scroll
|
|
4
|
+
// view (cloneElement(refreshControl, {style}, scrollView)). Angular mirrors that shape by querying
|
|
5
|
+
// a projected <RefreshControl>, re-rendering its native prop surface as the outer wrapper, and
|
|
6
|
+
// projecting every non-RefreshControl child into the inner scroll content.
|
|
7
7
|
//
|
|
8
|
-
// Android
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// four axis x refresh combos, plus a runtime axis switch) and `flat-list-scroll-containment.test.ts`.
|
|
8
|
+
// Android needs a different Fabric view per axis for both the outer scroll container
|
|
9
|
+
// (RCTScrollView is vertical-only; AndroidHorizontalScrollView is a dedicated ViewManager) and the
|
|
10
|
+
// inner content view (horizontal content is AndroidHorizontalScrollContentView, with its own
|
|
11
|
+
// ShadowNode::layout() override) — but Angular only reliably projects into a component's
|
|
12
|
+
// LAST-declared <ng-content> when 2+ DISTINCT declarations exist (angular/angular#22972), which an
|
|
13
|
+
// earlier 4-call-site template (one <ng-content> per axis x refresh-control branch) tripped. Fix:
|
|
14
|
+
// exactly ONE `<ng-content>` textually, inside a single top-level `<ng-template>`, re-instantiated
|
|
15
|
+
// into whichever branch is active via a local template-outlet directive
|
|
16
|
+
// (SymbioteTemplateOutletDirective below, `@angular/common`-free per this adapter's dependency
|
|
17
|
+
// policy — see package.json). Only one branch is ever live, so the declaration count stays at one
|
|
18
|
+
// and the "last one wins" limitation never triggers. Covered by
|
|
19
|
+
// `android-scroll-view-axis-projection.test.ts` and `flat-list-scroll-containment.test.ts`.
|
|
21
20
|
//
|
|
22
21
|
// Metro picks this on an Android host; no Platform.OS read.
|
|
23
22
|
|
|
@@ -26,7 +25,6 @@ import {
|
|
|
26
25
|
ChangeDetectionStrategy,
|
|
27
26
|
Component,
|
|
28
27
|
Directive,
|
|
29
|
-
ElementRef,
|
|
30
28
|
Input,
|
|
31
29
|
TemplateRef,
|
|
32
30
|
ViewContainerRef,
|
|
@@ -37,13 +35,13 @@ import {
|
|
|
37
35
|
} from '@angular/core';
|
|
38
36
|
import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
|
|
39
37
|
import {
|
|
40
|
-
anchorHostStyle,
|
|
41
|
-
HorizontalScrollView,
|
|
42
38
|
HorizontalScrollContentView,
|
|
39
|
+
HorizontalScrollView,
|
|
43
40
|
RefreshControlHost,
|
|
44
41
|
ScrollContentView,
|
|
45
42
|
ScrollViewHost,
|
|
46
43
|
SymbioteHostPropsDirective,
|
|
44
|
+
SymbioteStyleInputDirective,
|
|
47
45
|
} from '../../primitives';
|
|
48
46
|
import { ScrollViewBase, ScrollViewProjectionDirective, SCROLL_VIEW_INPUTS } from './shared';
|
|
49
47
|
export type { IAngularScrollViewProps, IScrollViewHandle } from './shared';
|
|
@@ -89,6 +87,7 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
|
89
87
|
@Component({
|
|
90
88
|
selector: 'ScrollView',
|
|
91
89
|
standalone: true,
|
|
90
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
92
91
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
93
92
|
imports: [
|
|
94
93
|
ScrollViewHost,
|
|
@@ -115,10 +114,10 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
|
115
114
|
>
|
|
116
115
|
<symbiote-horizontal-scroll-view
|
|
117
116
|
#host="symbioteHost"
|
|
118
|
-
[symbioteHostProps]="androidWrappedScrollProps"
|
|
117
|
+
[symbioteHostProps]="androidWrappedScrollProps()"
|
|
119
118
|
>
|
|
120
119
|
<symbiote-horizontal-scroll-content
|
|
121
|
-
[symbioteHostProps]="contentProps"
|
|
120
|
+
[symbioteHostProps]="contentProps()"
|
|
122
121
|
[symbioteScrollViewProjection]="projectionController"
|
|
123
122
|
>
|
|
124
123
|
<ng-container [symbioteTemplateOutlet]="sharedContent"></ng-container>
|
|
@@ -126,9 +125,9 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
|
126
125
|
</symbiote-horizontal-scroll-view>
|
|
127
126
|
</symbiote-refresh-control>
|
|
128
127
|
} @else {
|
|
129
|
-
<symbiote-horizontal-scroll-view #host="symbioteHost" [symbioteHostProps]="scrollProps">
|
|
128
|
+
<symbiote-horizontal-scroll-view #host="symbioteHost" [symbioteHostProps]="scrollProps()">
|
|
130
129
|
<symbiote-horizontal-scroll-content
|
|
131
|
-
[symbioteHostProps]="contentProps"
|
|
130
|
+
[symbioteHostProps]="contentProps()"
|
|
132
131
|
[symbioteScrollViewProjection]="projectionController"
|
|
133
132
|
>
|
|
134
133
|
<ng-container [symbioteTemplateOutlet]="sharedContent"></ng-container>
|
|
@@ -144,10 +143,10 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
|
144
143
|
>
|
|
145
144
|
<symbiote-scroll-view
|
|
146
145
|
#host="symbioteHost"
|
|
147
|
-
[symbioteHostProps]="androidWrappedScrollProps"
|
|
146
|
+
[symbioteHostProps]="androidWrappedScrollProps()"
|
|
148
147
|
>
|
|
149
148
|
<symbiote-scroll-content
|
|
150
|
-
[symbioteHostProps]="contentProps"
|
|
149
|
+
[symbioteHostProps]="contentProps()"
|
|
151
150
|
[symbioteScrollViewProjection]="projectionController"
|
|
152
151
|
>
|
|
153
152
|
<ng-container [symbioteTemplateOutlet]="sharedContent"></ng-container>
|
|
@@ -155,9 +154,9 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
|
155
154
|
</symbiote-scroll-view>
|
|
156
155
|
</symbiote-refresh-control>
|
|
157
156
|
} @else {
|
|
158
|
-
<symbiote-scroll-view #host="symbioteHost" [symbioteHostProps]="scrollProps">
|
|
157
|
+
<symbiote-scroll-view #host="symbioteHost" [symbioteHostProps]="scrollProps()">
|
|
159
158
|
<symbiote-scroll-content
|
|
160
|
-
[symbioteHostProps]="contentProps"
|
|
159
|
+
[symbioteHostProps]="contentProps()"
|
|
161
160
|
[symbioteScrollViewProjection]="projectionController"
|
|
162
161
|
>
|
|
163
162
|
<ng-container [symbioteTemplateOutlet]="sharedContent"></ng-container>
|
|
@@ -168,23 +167,17 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
|
168
167
|
`,
|
|
169
168
|
})
|
|
170
169
|
export class ScrollView extends ScrollViewBase {
|
|
171
|
-
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
172
|
-
// onto (see anchorHostStyle's doc comment) — NOT `#host` in the template above, which targets
|
|
173
|
-
// the real inner `symbiote-scroll-view`/`symbiote-horizontal-scroll-view` one level down.
|
|
174
|
-
private readonly elementRef = inject(ElementRef);
|
|
175
|
-
|
|
176
|
-
override get scrollProps(): Record<string, unknown> {
|
|
177
|
-
const props = super.scrollProps;
|
|
178
|
-
return { ...props, style: [anchorHostStyle(this.elementRef), props.style] };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
170
|
// Feeds the anchor's class-derived style INTO the base class's own `splitLayoutProps` call
|
|
182
171
|
// (shared.ts's `androidWrappedScrollProps`/`androidRefreshControlProps`), not tacked on after —
|
|
183
172
|
// a class-only layout style (flex/height/gap/…) must be visible to the split itself, or the
|
|
184
173
|
// Android outer refresh-control wrapper never receives its share and collapses to zero size
|
|
185
174
|
// (a real device bug: the whole ScrollView renders nothing). See layoutSplitStyle's doc comment
|
|
186
175
|
// in shared.ts and the identical fix already shipped in the Vue adapter's scroll-view.
|
|
176
|
+
//
|
|
177
|
+
// Reads the base's `anchorStyle` SIGNAL rather than calling anchorHostStyle() directly: this
|
|
178
|
+
// getter is evaluated inside the `androidWrappedScrollProps` computed, where a plain read would
|
|
179
|
+
// be untracked and leave the memoized bag holding a stale class.
|
|
187
180
|
protected override get layoutSplitStyle(): IStyleProp<IViewStyle> {
|
|
188
|
-
return [asStyle(
|
|
181
|
+
return [asStyle(this.anchorStyle()), this.style];
|
|
189
182
|
}
|
|
190
183
|
}
|
|
@@ -16,19 +16,13 @@
|
|
|
16
16
|
// a horizontal FlatList's cells landed outside the ScrollView entirely. Fix: single unconditional
|
|
17
17
|
// `<ng-content>` call site.
|
|
18
18
|
|
|
19
|
+
import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
19
20
|
import {
|
|
20
|
-
CUSTOM_ELEMENTS_SCHEMA,
|
|
21
|
-
ChangeDetectionStrategy,
|
|
22
|
-
Component,
|
|
23
|
-
ElementRef,
|
|
24
|
-
inject,
|
|
25
|
-
} from '@angular/core';
|
|
26
|
-
import {
|
|
27
|
-
anchorHostStyle,
|
|
28
21
|
RefreshControlHost,
|
|
29
22
|
ScrollContentView,
|
|
30
23
|
ScrollViewHost,
|
|
31
24
|
SymbioteHostPropsDirective,
|
|
25
|
+
SymbioteStyleInputDirective,
|
|
32
26
|
} from '../../primitives';
|
|
33
27
|
import { ScrollViewBase, ScrollViewProjectionDirective, SCROLL_VIEW_INPUTS } from './shared';
|
|
34
28
|
export type { IAngularScrollViewProps, IScrollViewHandle } from './shared';
|
|
@@ -38,6 +32,7 @@ export type { IAngularScrollViewProps, IScrollViewHandle } from './shared';
|
|
|
38
32
|
@Component({
|
|
39
33
|
selector: 'ScrollView',
|
|
40
34
|
standalone: true,
|
|
35
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
41
36
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
42
37
|
imports: [
|
|
43
38
|
ScrollViewHost,
|
|
@@ -49,7 +44,7 @@ export type { IAngularScrollViewProps, IScrollViewHandle } from './shared';
|
|
|
49
44
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
50
45
|
inputs: SCROLL_VIEW_INPUTS,
|
|
51
46
|
template: `
|
|
52
|
-
<symbiote-scroll-view #host="symbioteHost" [symbioteHostProps]="scrollProps">
|
|
47
|
+
<symbiote-scroll-view #host="symbioteHost" [symbioteHostProps]="scrollProps()">
|
|
53
48
|
@if (hasProjectedRefreshControl) {
|
|
54
49
|
<symbiote-refresh-control
|
|
55
50
|
#refreshHost="symbioteHost"
|
|
@@ -58,7 +53,7 @@ export type { IAngularScrollViewProps, IScrollViewHandle } from './shared';
|
|
|
58
53
|
></symbiote-refresh-control>
|
|
59
54
|
}
|
|
60
55
|
<symbiote-scroll-content
|
|
61
|
-
[symbioteHostProps]="contentProps"
|
|
56
|
+
[symbioteHostProps]="contentProps()"
|
|
62
57
|
[symbioteScrollViewProjection]="projectionController"
|
|
63
58
|
>
|
|
64
59
|
<ng-content></ng-content>
|
|
@@ -66,14 +61,8 @@ export type { IAngularScrollViewProps, IScrollViewHandle } from './shared';
|
|
|
66
61
|
</symbiote-scroll-view>
|
|
67
62
|
`,
|
|
68
63
|
})
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
override get scrollProps(): Record<string, unknown> {
|
|
76
|
-
const props = super.scrollProps;
|
|
77
|
-
return { ...props, style: [anchorHostStyle(this.elementRef), props.style] };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
64
|
+
// Behavior lives entirely in ScrollViewBase; this class exists for the decorator + iOS template.
|
|
65
|
+
// The anchor-style merge that used to be a `scrollProps` override here moved into the base's
|
|
66
|
+
// `scrollProps` computed - a subclass getter cannot override a field, and both platforms merged it
|
|
67
|
+
// identically anyway (see that computed's comment).
|
|
68
|
+
export class ScrollView extends ScrollViewBase {}
|
|
@@ -73,6 +73,9 @@ function isProjectedRefreshControl(node: ISymbioteNode): boolean {
|
|
|
73
73
|
// delay, rebuild ranges, cross-talk record) live there; this runner only EXECUTES the effects on the
|
|
74
74
|
// engine node (build interpolation + wire listener, hold the debounce timer, re-apply the node props).
|
|
75
75
|
// Because it owns the child index, it consumes the reducer's record-header-y effect for cross-talk.
|
|
76
|
+
// Diagnostic-only: a process-wide counter so each controller's log lines are attributable.
|
|
77
|
+
let projectionControllerSeq = 0;
|
|
78
|
+
|
|
76
79
|
class StickyProjectionWrapper {
|
|
77
80
|
private readonly state = createInitialStickyState();
|
|
78
81
|
private animatedTranslateY: AnimatedInterpolation;
|
|
@@ -91,6 +94,9 @@ class StickyProjectionWrapper {
|
|
|
91
94
|
inputRange: [-1, 0],
|
|
92
95
|
outputRange: [0, 0],
|
|
93
96
|
});
|
|
97
|
+
dlog(
|
|
98
|
+
`STICKY[wrap] created index=${childIndex} scrollValueIsNative=${this.controller.config.scrollAnimatedValue.__isNative()}`,
|
|
99
|
+
);
|
|
94
100
|
this.dispatch({ kind: 'inputs-changed' });
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -148,7 +154,7 @@ class StickyProjectionWrapper {
|
|
|
148
154
|
this.animatedTranslateY = next;
|
|
149
155
|
this.applyProps();
|
|
150
156
|
dlog(
|
|
151
|
-
`
|
|
157
|
+
`STICKY[wrap] rebuilt index=${this.childIndex} measured=${this.state.measured} y=${this.state.layoutY}`,
|
|
152
158
|
);
|
|
153
159
|
break;
|
|
154
160
|
}
|
|
@@ -172,6 +178,7 @@ class StickyProjectionWrapper {
|
|
|
172
178
|
private readonly onLayout = (event: ISymbioteEvent): void => {
|
|
173
179
|
const y = readLayoutNumber(event, 'y');
|
|
174
180
|
const height = readLayoutNumber(event, 'height');
|
|
181
|
+
dlog(`STICKY[wrap] index=${this.childIndex} onLayout y=${y} height=${height}`);
|
|
175
182
|
// Keep the previous value when a field is absent (RN sets state only on a defined read).
|
|
176
183
|
this.dispatch({
|
|
177
184
|
kind: 'layout',
|
|
@@ -180,7 +187,12 @@ class StickyProjectionWrapper {
|
|
|
180
187
|
});
|
|
181
188
|
};
|
|
182
189
|
|
|
190
|
+
// The ONLY thing that drives the committed pin on this path: applyProps has no __makeNative(),
|
|
191
|
+
// so the wrapper is JS-driven end to end. If the scroll value has been made native up front the
|
|
192
|
+
// cascade to child listeners stops (AnimatedWithChildren.__callListeners skips children once
|
|
193
|
+
// isNative) and this never fires - the header then renders in place and never moves.
|
|
183
194
|
private readonly animatedValueListener = ({ value }: { value: number | string }): void => {
|
|
195
|
+
dlog(() => `STICKY[wrap] index=${this.childIndex} animated-tick value=${String(value)}`);
|
|
184
196
|
if (typeof value === 'number') this.dispatch({ kind: 'animated-tick', value });
|
|
185
197
|
};
|
|
186
198
|
|
|
@@ -203,14 +215,37 @@ class StickyProjectionWrapper {
|
|
|
203
215
|
private applyProps(): void {
|
|
204
216
|
const props = this.props();
|
|
205
217
|
const reduced = reduceProps(props);
|
|
218
|
+
// Thunk: applyProps is on the scroll-driven path (every animated-tick while the pin is
|
|
219
|
+
// JS-driven), and JSON.stringify of a reduced transform is not free.
|
|
220
|
+
dlog(
|
|
221
|
+
() =>
|
|
222
|
+
`STICKY[wrap] index=${this.childIndex} applyProps translateY=${String(this.state.translateY)} ` +
|
|
223
|
+
`measured=${this.state.measured} layoutY=${this.state.layoutY} ` +
|
|
224
|
+
`scrollValueIsNative=${this.controller.config.scrollAnimatedValue.__isNative()} ` +
|
|
225
|
+
`reducedTransform=${JSON.stringify(reduced.transform ?? reduced.style)}`,
|
|
226
|
+
);
|
|
206
227
|
for (const [key, value] of Object.entries(reduced)) routeProp(this.node, key, value);
|
|
207
228
|
|
|
208
229
|
const next = new AnimatedProps(props);
|
|
209
230
|
next.__attach();
|
|
231
|
+
// No explicit __makeNative() here on purpose: __attach joins the leaf to the interpolation,
|
|
232
|
+
// which sits under the shared scroll value, and AnimatedWithChildren.__addChild promotes a
|
|
233
|
+
// child joining an ALREADY-native parent (graph.ts). So the pin is native exactly when the
|
|
234
|
+
// scroll value is - logged below, because "is this actually on the UI thread" is otherwise
|
|
235
|
+
// invisible until you read three files.
|
|
236
|
+
dlog(
|
|
237
|
+
() =>
|
|
238
|
+
`STICKY[wrap] index=${this.childIndex} leaf attached isNative=${next.__isNative()} ` +
|
|
239
|
+
`interpolationIsNative=${this.animatedTranslateY.__isNative()} ` +
|
|
240
|
+
`scrollValueIsNative=${this.controller.config.scrollAnimatedValue.__isNative()}`,
|
|
241
|
+
);
|
|
210
242
|
if (this.animatedProps !== undefined) this.animatedProps.__detach();
|
|
211
243
|
this.animatedProps = next;
|
|
212
244
|
this.cancelBind?.();
|
|
213
|
-
this.cancelBind = whenCommitted(this.node, () =>
|
|
245
|
+
this.cancelBind = whenCommitted(this.node, () => {
|
|
246
|
+
next.setNativeView(this.node);
|
|
247
|
+
dlog(`STICKY[wrap] index=${this.childIndex} setNativeView isNative=${next.__isNative()}`);
|
|
248
|
+
});
|
|
214
249
|
}
|
|
215
250
|
}
|
|
216
251
|
|
|
@@ -220,6 +255,13 @@ export class ScrollViewProjectionController {
|
|
|
220
255
|
private readonly records: IProjectedRecord[] = [];
|
|
221
256
|
private readonly headerLayoutYs = new Map<number, number>();
|
|
222
257
|
|
|
258
|
+
// Diagnostic-only: one ScrollView can host several projection controllers over its lifetime,
|
|
259
|
+
// and an app has several ScrollViews at once. Without an id every log line below is
|
|
260
|
+
// indistinguishable between instances, which is what made a "logs never stop" report
|
|
261
|
+
// impossible to read as either a real loop or two lists warming up in turn.
|
|
262
|
+
private readonly instanceId = ++projectionControllerSeq;
|
|
263
|
+
private reconcileSeq = 0;
|
|
264
|
+
|
|
223
265
|
constructor(config: IScrollViewProjectionConfig) {
|
|
224
266
|
this.config = config;
|
|
225
267
|
}
|
|
@@ -305,10 +347,13 @@ export class ScrollViewProjectionController {
|
|
|
305
347
|
nextStickyHeaderY(index: number): number | undefined {
|
|
306
348
|
const stickyIndices = this.config.stickyHeaderIndices ?? [];
|
|
307
349
|
const next = stickyIndices.find(entry => entry > index);
|
|
308
|
-
|
|
350
|
+
const y = next === undefined ? undefined : this.headerLayoutYs.get(next);
|
|
351
|
+
dlog(`STICKY[proj] nextStickyHeaderY(index=${index}) next=${next} y=${y}`);
|
|
352
|
+
return y;
|
|
309
353
|
}
|
|
310
354
|
|
|
311
355
|
recordHeaderLayoutY(index: number, y: number): void {
|
|
356
|
+
dlog(`STICKY[proj] recordHeaderLayoutY index=${index} y=${y}`);
|
|
312
357
|
if (this.headerLayoutYs.get(index) === y) return;
|
|
313
358
|
this.headerLayoutYs.set(index, y);
|
|
314
359
|
for (const record of this.records) record.sticky?.rebuild();
|
|
@@ -322,7 +367,8 @@ export class ScrollViewProjectionController {
|
|
|
322
367
|
): void {
|
|
323
368
|
const before = beforeRecord === undefined ? undefined : directNode(beforeRecord);
|
|
324
369
|
dlog(
|
|
325
|
-
`
|
|
370
|
+
`STICKY[proj#${this.instanceId}] insertRecord child=${record.child.component} ` +
|
|
371
|
+
`before=${before ? before.component : 'append'} parentIsContentNode=${parent === this.contentNode}`,
|
|
326
372
|
);
|
|
327
373
|
if (before === undefined) insert(parent, record.child);
|
|
328
374
|
else insert(parent, record.child, before);
|
|
@@ -330,26 +376,36 @@ export class ScrollViewProjectionController {
|
|
|
330
376
|
|
|
331
377
|
private reconcileStickyRecords(): void {
|
|
332
378
|
if (this.contentNode === undefined) return;
|
|
379
|
+
const seq = ++this.reconcileSeq;
|
|
333
380
|
dlog(
|
|
334
|
-
|
|
381
|
+
() =>
|
|
382
|
+
`STICKY[proj#${this.instanceId}] reconcile#${seq} records=${this.records.length} ` +
|
|
383
|
+
`children=${this.contentNode?.children.length} sticky=${JSON.stringify(this.config.stickyHeaderIndices ?? [])}`,
|
|
335
384
|
);
|
|
336
385
|
const stickyIndices = new Set(this.config.stickyHeaderIndices ?? []);
|
|
337
386
|
if (this.config.customStickyHeaderComponent !== undefined && stickyIndices.size > 0) {
|
|
338
387
|
dlog(
|
|
339
|
-
'
|
|
388
|
+
'STICKY[proj] uses built-in sticky wrapper; custom StickyHeaderComponent is explicit-composition only',
|
|
340
389
|
);
|
|
341
390
|
}
|
|
342
391
|
|
|
343
392
|
let paintIndex = 0;
|
|
344
393
|
for (const record of [...this.records]) {
|
|
345
394
|
if (this.config.excludeRefreshControl && isProjectedRefreshControl(record.child)) {
|
|
346
|
-
|
|
395
|
+
// Removed INLINE, not through removeProjectedChild: that method ends with its own
|
|
396
|
+
// reconcileStickyRecords(), so calling it from inside this walk re-enters the walk while
|
|
397
|
+
// this.records is being spliced under it - one refresh control could then drive an
|
|
398
|
+
// unbounded recursion. Same removal, without the re-entry.
|
|
399
|
+
this.dropRecord(record);
|
|
347
400
|
continue;
|
|
348
401
|
}
|
|
349
402
|
const countsAsChild = !isAnchor(record.child) && !isProjectedRefreshControl(record.child);
|
|
350
403
|
const childIndex = paintIndex;
|
|
351
404
|
if (countsAsChild) paintIndex += 1;
|
|
352
405
|
const shouldWrap = countsAsChild && stickyIndices.has(childIndex);
|
|
406
|
+
// Deliberately NOT logged per child per walk: reconcile runs on every projected insert, so
|
|
407
|
+
// that shape is O(children x inserts) and buries the few lines that carry information. Only
|
|
408
|
+
// a state TRANSITION is logged, below.
|
|
353
409
|
if (shouldWrap && record.wrapper === undefined) this.wrapRecord(record, childIndex);
|
|
354
410
|
else if (!shouldWrap && record.wrapper !== undefined) this.unwrapRecord(record);
|
|
355
411
|
else if (shouldWrap && record.stickyIndex !== childIndex) {
|
|
@@ -360,6 +416,18 @@ export class ScrollViewProjectionController {
|
|
|
360
416
|
}
|
|
361
417
|
}
|
|
362
418
|
|
|
419
|
+
// The removal half of removeProjectedChild, without its trailing reconcile - safe to call from
|
|
420
|
+
// inside the reconcile walk itself.
|
|
421
|
+
private dropRecord(record: IProjectedRecord): void {
|
|
422
|
+
if (this.contentNode === undefined) return;
|
|
423
|
+
record.sticky?.destroy();
|
|
424
|
+
projectedWrappers.delete(record.child);
|
|
425
|
+
const direct = directNode(record);
|
|
426
|
+
if (direct.parent === this.contentNode) removeChild(this.contentNode, direct);
|
|
427
|
+
const index = this.records.indexOf(record);
|
|
428
|
+
if (index !== -1) this.records.splice(index, 1);
|
|
429
|
+
}
|
|
430
|
+
|
|
363
431
|
// Auto projection runs after Angular has already created projected child host nodes. At this
|
|
364
432
|
// renderer boundary we can AOT-safely rearrange engine nodes, but we cannot instantiate an
|
|
365
433
|
// arbitrary Angular component class as a wrapper without owning a ViewContainerRef/injector and
|
|
@@ -383,10 +451,14 @@ export class ScrollViewProjectionController {
|
|
|
383
451
|
record.sticky = new StickyProjectionWrapper(this, childIndex, wrapper);
|
|
384
452
|
record.stickyIndex = childIndex;
|
|
385
453
|
projectedWrappers.set(record.child, record);
|
|
454
|
+
dlog(
|
|
455
|
+
`STICKY[proj#${this.instanceId}] WRAP child=${record.child.component} paintIndex=${childIndex}`,
|
|
456
|
+
);
|
|
386
457
|
}
|
|
387
458
|
|
|
388
459
|
private unwrapRecord(record: IProjectedRecord): void {
|
|
389
460
|
if (this.contentNode === undefined || record.wrapper === undefined) return;
|
|
461
|
+
dlog(`STICKY[proj#${this.instanceId}] UNWRAP child=${record.child.component}`);
|
|
390
462
|
record.sticky?.destroy();
|
|
391
463
|
record.sticky = undefined;
|
|
392
464
|
record.stickyIndex = undefined;
|