@symbiote-native/angular 0.0.0-canary-20260724081123
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 +157 -0
- package/babel-linker.cjs +7 -0
- package/babel-register-composed.cjs +127 -0
- package/bin/symbiote-angular-dev.cjs +93 -0
- package/build/angular/anchor-host-registry.d.ts +2 -0
- package/build/angular/anchor-host-registry.js +84 -0
- package/build/angular/anchor-host-registry.js.map +1 -0
- package/build/angular/bootstrap.d.ts +7 -0
- package/build/angular/bootstrap.js +15 -0
- package/build/angular/bootstrap.js.map +1 -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/flat-list/index.d.ts +122 -0
- package/build/angular/components/flat-list/index.js +688 -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 +406 -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/index.d.ts +60 -0
- package/build/angular/components/input-accessory-view/index.js +251 -0
- package/build/angular/components/input-accessory-view/index.js.map +1 -0
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +82 -0
- package/build/angular/components/keyboard-avoiding-view/index.js +358 -0
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -0
- package/build/angular/components/modal/index.d.ts +92 -0
- package/build/angular/components/modal/index.js +360 -0
- package/build/angular/components/modal/index.js.map +1 -0
- package/build/angular/components/pressable/index.d.ts +123 -0
- package/build/angular/components/pressable/index.js +420 -0
- package/build/angular/components/pressable/index.js.map +1 -0
- package/build/angular/components/refresh-control/index.d.ts +86 -0
- package/build/angular/components/refresh-control/index.js +342 -0
- package/build/angular/components/refresh-control/index.js.map +1 -0
- package/build/angular/components/safe-area-view/index.d.ts +57 -0
- package/build/angular/components/safe-area-view/index.js +248 -0
- package/build/angular/components/safe-area-view/index.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 +334 -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 +682 -0
- package/build/angular/components/scroll-view/shared.js.map +1 -0
- package/build/angular/components/scroll-view/sticky-header.d.ts +40 -0
- package/build/angular/components/scroll-view/sticky-header.js +225 -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 +599 -0
- package/build/angular/components/text-input.js.map +1 -0
- package/build/angular/components/touchable/index.d.ts +245 -0
- package/build/angular/components/touchable/index.js +1186 -0
- package/build/angular/components/touchable/index.js.map +1 -0
- package/build/angular/components/touchable-native-feedback/index.d.ts +83 -0
- package/build/angular/components/touchable-native-feedback/index.js +371 -0
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -0
- package/build/angular/components/virtualized-list/directives.d.ts +54 -0
- package/build/angular/components/virtualized-list/directives.js +153 -0
- package/build/angular/components/virtualized-list/directives.js.map +1 -0
- package/build/angular/components/virtualized-list/index.d.ts +253 -0
- package/build/angular/components/virtualized-list/index.js +1031 -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 +76 -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 +720 -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/index.d.ts +22 -0
- package/build/angular/create-portal/index.js +74 -0
- package/build/angular/create-portal/index.js.map +1 -0
- package/build/angular/create-tunnel/index.d.ts +34 -0
- package/build/angular/create-tunnel/index.js +126 -0
- package/build/angular/create-tunnel/index.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 +22 -0
- package/build/angular/index.js +46 -0
- package/build/angular/index.js.map +1 -0
- package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +14 -0
- package/build/angular/modules/animated/animated-leaf-binder/index.js +91 -0
- package/build/angular/modules/animated/animated-leaf-binder/index.js.map +1 -0
- package/build/angular/modules/animated/animated-leaf-binder.d.ts +14 -0
- package/build/angular/modules/animated/animated-leaf-binder.js +91 -0
- package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -0
- package/build/angular/modules/animated/create-animated-component.d.ts +59 -0
- package/build/angular/modules/animated/create-animated-component.js +364 -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/index.d.ts +20 -0
- package/build/angular/modules/status-bar/index.js +59 -0
- package/build/angular/modules/status-bar/index.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 +88 -0
- package/build/angular/primitives/shared.js +171 -0
- package/build/angular/primitives/shared.js.map +1 -0
- package/build/angular/render/index.d.ts +11 -0
- package/build/angular/render/index.js +171 -0
- package/build/angular/render/index.js.map +1 -0
- package/build/angular/renderer/index.d.ts +37 -0
- package/build/angular/renderer/index.js +301 -0
- package/build/angular/renderer/index.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/metro-config.cjs +48 -0
- package/metro-css-parser.cjs +13 -0
- package/package.json +80 -0
- package/src/__tests__/responder-change-detection.test.ts +102 -0
- package/src/__tests__/responder-nested-cd.test.ts +113 -0
- package/src/anchor-host-registry.ts +88 -0
- package/src/angular-gaps.test.ts +142 -0
- package/src/angular-imports.test.ts +100 -0
- package/src/bootstrap.ts +26 -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 +574 -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 +479 -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/index.ts +185 -0
- package/src/components/input-accessory-view/input-accessory-view.test.ts +43 -0
- package/src/components/keyboard-avoiding-view/index.ts +303 -0
- package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +54 -0
- package/src/components/modal/index.ts +299 -0
- package/src/components/modal/modal.test.ts +43 -0
- package/src/components/pressable/index.ts +373 -0
- package/src/components/pressable/pressable.test.ts +85 -0
- package/src/components/refresh-control/index.ts +284 -0
- package/src/components/refresh-control/refresh-control.test.ts +41 -0
- package/src/components/safe-area-view/index.ts +177 -0
- package/src/components/safe-area-view/safe-area-view.test.ts +43 -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 +414 -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 +844 -0
- package/src/components/scroll-view/sticky-header.ts +255 -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 +531 -0
- package/src/components/touchable/index.ts +629 -0
- package/src/components/touchable/touchable.test.ts +102 -0
- package/src/components/touchable-native-feedback/index.ts +254 -0
- package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +49 -0
- package/src/components/virtualized-list/directives.ts +168 -0
- package/src/components/virtualized-list/index.ts +992 -0
- package/src/components/virtualized-list/virtualized-list-class-style.test.ts +76 -0
- package/src/components/virtualized-section-list/directives.ts +90 -0
- package/src/components/virtualized-section-list/index.ts +601 -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/create-portal.test.ts +90 -0
- package/src/create-portal/index.ts +74 -0
- package/src/create-tunnel/create-tunnel.test.ts +120 -0
- package/src/create-tunnel/index.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 +261 -0
- package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +116 -0
- package/src/modules/animated/animated-leaf-binder/index.ts +101 -0
- package/src/modules/animated/animated-leaf-binder.test.ts +116 -0
- package/src/modules/animated/animated-leaf-binder.ts +101 -0
- package/src/modules/animated/animated-scroll-view.test.ts +102 -0
- package/src/modules/animated/create-animated-component.ts +365 -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/index.ts +57 -0
- package/src/modules/status-bar/status-bar.test.ts +50 -0
- package/src/primitives/index.ts +119 -0
- package/src/primitives/shared.ts +190 -0
- package/src/render/index.ts +235 -0
- package/src/render/render.test.ts +147 -0
- package/src/renderer/index.ts +347 -0
- package/src/renderer/renderer.test.ts +203 -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
- package/tsconfig.angular.base.json +30 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnimatedProps,
|
|
3
|
+
type AnimatedValue,
|
|
4
|
+
dlog,
|
|
5
|
+
isAnchor,
|
|
6
|
+
reduceProps,
|
|
7
|
+
routeProp,
|
|
8
|
+
whenCommitted,
|
|
9
|
+
createElement,
|
|
10
|
+
appendChild,
|
|
11
|
+
insertBefore,
|
|
12
|
+
removeChild,
|
|
13
|
+
type AnimatedInterpolation,
|
|
14
|
+
type ISymbioteEvent,
|
|
15
|
+
type ISymbioteNode,
|
|
16
|
+
} from '@symbiote-native/engine';
|
|
17
|
+
import {
|
|
18
|
+
createInitialStickyState,
|
|
19
|
+
readLayoutNumber,
|
|
20
|
+
reduceSticky,
|
|
21
|
+
STICKY_HEADER_Z_INDEX,
|
|
22
|
+
type IStickyAction,
|
|
23
|
+
type IStickyEffect,
|
|
24
|
+
type IStickyReducerInputs,
|
|
25
|
+
} from '@symbiote-native/components';
|
|
26
|
+
import { descriptorFor } from '@symbiote-native/components';
|
|
27
|
+
import { Platform } from '@symbiote-native/engine';
|
|
28
|
+
|
|
29
|
+
type IInsert = (parent: ISymbioteNode, child: ISymbioteNode, beforeChild?: ISymbioteNode) => void;
|
|
30
|
+
type IRemove = (parent: ISymbioteNode, child: ISymbioteNode) => void;
|
|
31
|
+
|
|
32
|
+
const contentProjection = new WeakMap<ISymbioteNode, ScrollViewProjectionController>();
|
|
33
|
+
const projectedWrappers = new WeakMap<ISymbioteNode, IProjectedRecord>();
|
|
34
|
+
|
|
35
|
+
interface IProjectedRecord {
|
|
36
|
+
child: ISymbioteNode;
|
|
37
|
+
wrapper: ISymbioteNode | undefined;
|
|
38
|
+
sticky: StickyProjectionWrapper | undefined;
|
|
39
|
+
stickyIndex: number | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IScrollViewProjectionConfig {
|
|
43
|
+
stickyHeaderIndices: readonly number[] | undefined;
|
|
44
|
+
invertStickyHeaders: boolean | undefined;
|
|
45
|
+
scrollViewHeight: number | undefined;
|
|
46
|
+
scrollAnimatedValue: AnimatedValue;
|
|
47
|
+
customStickyHeaderComponent: unknown;
|
|
48
|
+
excludeRefreshControl: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createViewNode(): ISymbioteNode {
|
|
52
|
+
const descriptor = descriptorFor('symbiote-view');
|
|
53
|
+
return createElement(descriptor.component, descriptor.isText);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function directNode(record: IProjectedRecord): ISymbioteNode {
|
|
57
|
+
return record.wrapper ?? record.child;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isProjectedRefreshControl(node: ISymbioteNode): boolean {
|
|
61
|
+
if (node.component === 'PullToRefreshView' || node.component === 'AndroidSwipeRefreshLayout') {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
// Angular public <RefreshControl> is an anchor host whose real native refresh node lives inside
|
|
65
|
+
// its component view. Projection filtering must recognize that anchor, otherwise iOS re-renders
|
|
66
|
+
// a RefreshControl sibling and also leaves the projected one inside the scroll content.
|
|
67
|
+
return isAnchor(node) && node.children.some(isProjectedRefreshControl);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The SECOND Angular sticky effect-runner (auto-projected children can't be arbitrary Angular
|
|
71
|
+
// component classes, so the controller drives engine nodes directly). It shares the ONE
|
|
72
|
+
// reduceSticky state machine with ScrollViewStickyHeader — the DECISIONS (zero-swallow gate, debounce
|
|
73
|
+
// delay, rebuild ranges, cross-talk record) live there; this runner only EXECUTES the effects on the
|
|
74
|
+
// engine node (build interpolation + wire listener, hold the debounce timer, re-apply the node props).
|
|
75
|
+
// Because it owns the child index, it consumes the reducer's record-header-y effect for cross-talk.
|
|
76
|
+
class StickyProjectionWrapper {
|
|
77
|
+
private readonly state = createInitialStickyState();
|
|
78
|
+
private animatedTranslateY: AnimatedInterpolation;
|
|
79
|
+
private interpolation: AnimatedInterpolation | undefined;
|
|
80
|
+
private animatedProps: AnimatedProps | undefined;
|
|
81
|
+
private cancelBind: (() => void) | undefined;
|
|
82
|
+
private listenerId: string | undefined;
|
|
83
|
+
private debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
84
|
+
|
|
85
|
+
constructor(
|
|
86
|
+
private readonly controller: ScrollViewProjectionController,
|
|
87
|
+
private readonly childIndex: number,
|
|
88
|
+
private readonly node: ISymbioteNode,
|
|
89
|
+
) {
|
|
90
|
+
this.animatedTranslateY = this.controller.config.scrollAnimatedValue.interpolate({
|
|
91
|
+
inputRange: [-1, 0],
|
|
92
|
+
outputRange: [0, 0],
|
|
93
|
+
});
|
|
94
|
+
this.dispatch({ kind: 'inputs-changed' });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
destroy(): void {
|
|
98
|
+
this.cancelBind?.();
|
|
99
|
+
this.cancelBind = undefined;
|
|
100
|
+
if (this.interpolation !== undefined && this.listenerId !== undefined) {
|
|
101
|
+
this.interpolation.removeListener(this.listenerId);
|
|
102
|
+
this.listenerId = undefined;
|
|
103
|
+
}
|
|
104
|
+
if (this.debounceTimer !== undefined) {
|
|
105
|
+
clearTimeout(this.debounceTimer);
|
|
106
|
+
this.debounceTimer = undefined;
|
|
107
|
+
}
|
|
108
|
+
if (this.animatedProps !== undefined) {
|
|
109
|
+
this.animatedProps.__detach();
|
|
110
|
+
this.animatedProps = undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// The controller calls this when a cross-talk input changed (a sibling recorded its y): rebuild the
|
|
115
|
+
// ranges off the new nextStickyHeaderY.
|
|
116
|
+
rebuild(): void {
|
|
117
|
+
this.dispatch({ kind: 'inputs-changed' });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private inputs(): IStickyReducerInputs {
|
|
121
|
+
return {
|
|
122
|
+
os: Platform.OS,
|
|
123
|
+
inverted: this.controller.config.invertStickyHeaders,
|
|
124
|
+
scrollViewHeight: this.controller.config.scrollViewHeight,
|
|
125
|
+
nextHeaderLayoutY: this.controller.nextStickyHeaderY(this.childIndex),
|
|
126
|
+
index: this.childIndex,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private dispatch(action: IStickyAction): void {
|
|
131
|
+
this.runEffects(reduceSticky(this.state, action, this.inputs()).effects);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private runEffects(effects: IStickyEffect[]): void {
|
|
135
|
+
for (const effect of effects) {
|
|
136
|
+
switch (effect.kind) {
|
|
137
|
+
case 'rebuild-interpolation': {
|
|
138
|
+
if (this.interpolation !== undefined && this.listenerId !== undefined) {
|
|
139
|
+
this.interpolation.removeListener(this.listenerId);
|
|
140
|
+
this.listenerId = undefined;
|
|
141
|
+
}
|
|
142
|
+
const next = this.controller.config.scrollAnimatedValue.interpolate({
|
|
143
|
+
inputRange: effect.inputRange,
|
|
144
|
+
outputRange: effect.outputRange,
|
|
145
|
+
});
|
|
146
|
+
this.listenerId = next.addListener(this.animatedValueListener);
|
|
147
|
+
this.interpolation = next;
|
|
148
|
+
this.animatedTranslateY = next;
|
|
149
|
+
this.applyProps();
|
|
150
|
+
dlog(
|
|
151
|
+
`Angular ScrollView projection sticky index=${this.childIndex} measured=${this.state.measured} y=${this.state.layoutY}`,
|
|
152
|
+
);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case 'schedule-debounce':
|
|
156
|
+
if (this.debounceTimer !== undefined) clearTimeout(this.debounceTimer);
|
|
157
|
+
this.debounceTimer = setTimeout(() => {
|
|
158
|
+
this.debounceTimer = undefined;
|
|
159
|
+
this.dispatch({ kind: 'debounce-fired', value: effect.value });
|
|
160
|
+
}, effect.delay);
|
|
161
|
+
break;
|
|
162
|
+
case 'apply-passthrough':
|
|
163
|
+
this.applyProps();
|
|
164
|
+
break;
|
|
165
|
+
case 'record-header-y':
|
|
166
|
+
this.controller.recordHeaderLayoutY(effect.index, effect.y);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private readonly onLayout = (event: ISymbioteEvent): void => {
|
|
173
|
+
const y = readLayoutNumber(event, 'y');
|
|
174
|
+
const height = readLayoutNumber(event, 'height');
|
|
175
|
+
// Keep the previous value when a field is absent (RN sets state only on a defined read).
|
|
176
|
+
this.dispatch({
|
|
177
|
+
kind: 'layout',
|
|
178
|
+
y: y ?? this.state.layoutY,
|
|
179
|
+
height: height ?? this.state.layoutHeight,
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
private readonly animatedValueListener = ({ value }: { value: number | string }): void => {
|
|
184
|
+
if (typeof value === 'number') this.dispatch({ kind: 'animated-tick', value });
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
private props(): Record<string, unknown> {
|
|
188
|
+
const style = {
|
|
189
|
+
transform: [{ translateY: this.animatedTranslateY }],
|
|
190
|
+
zIndex: STICKY_HEADER_Z_INDEX,
|
|
191
|
+
};
|
|
192
|
+
const passthrough =
|
|
193
|
+
this.state.translateY === null
|
|
194
|
+
? undefined
|
|
195
|
+
: { transform: [{ translateY: this.state.translateY }], zIndex: STICKY_HEADER_Z_INDEX };
|
|
196
|
+
return {
|
|
197
|
+
style: passthrough === undefined ? style : [style, passthrough],
|
|
198
|
+
onLayout: this.onLayout,
|
|
199
|
+
collapsable: false,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private applyProps(): void {
|
|
204
|
+
const props = this.props();
|
|
205
|
+
const reduced = reduceProps(props);
|
|
206
|
+
for (const [key, value] of Object.entries(reduced)) routeProp(this.node, key, value);
|
|
207
|
+
|
|
208
|
+
const next = new AnimatedProps(props);
|
|
209
|
+
next.__attach();
|
|
210
|
+
if (this.animatedProps !== undefined) this.animatedProps.__detach();
|
|
211
|
+
this.animatedProps = next;
|
|
212
|
+
this.cancelBind?.();
|
|
213
|
+
this.cancelBind = whenCommitted(this.node, () => next.setNativeView(this.node));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export class ScrollViewProjectionController {
|
|
218
|
+
config: IScrollViewProjectionConfig;
|
|
219
|
+
private contentNode: ISymbioteNode | undefined;
|
|
220
|
+
private readonly records: IProjectedRecord[] = [];
|
|
221
|
+
private readonly headerLayoutYs = new Map<number, number>();
|
|
222
|
+
|
|
223
|
+
constructor(config: IScrollViewProjectionConfig) {
|
|
224
|
+
this.config = config;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
update(config: IScrollViewProjectionConfig): void {
|
|
228
|
+
this.config = config;
|
|
229
|
+
this.reconcileStickyRecords();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
bindContentNode(node: ISymbioteNode): void {
|
|
233
|
+
dlog(
|
|
234
|
+
`Angular ScrollView projection bindContentNode node=${node.component} preExistingChildren=${node.children.length} recordsBefore=${this.records.length}`,
|
|
235
|
+
);
|
|
236
|
+
this.contentNode = node;
|
|
237
|
+
contentProjection.set(node, this);
|
|
238
|
+
if (this.records.length === 0 && node.children.length > 0) {
|
|
239
|
+
for (const child of [...node.children]) {
|
|
240
|
+
this.records.push({ child, wrapper: undefined, sticky: undefined, stickyIndex: undefined });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
this.reconcileStickyRecords();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
appendProjectedChild(parent: ISymbioteNode, child: ISymbioteNode, insert: IInsert): void {
|
|
247
|
+
dlog(
|
|
248
|
+
`Angular ScrollView projection appendProjectedChild parent=${parent.component} child=${child.component} recordsBefore=${this.records.length}`,
|
|
249
|
+
);
|
|
250
|
+
const existing = this.records.find(record => record.child === child);
|
|
251
|
+
if (existing !== undefined) this.records.splice(this.records.indexOf(existing), 1);
|
|
252
|
+
const record: IProjectedRecord = {
|
|
253
|
+
child,
|
|
254
|
+
wrapper: undefined,
|
|
255
|
+
sticky: undefined,
|
|
256
|
+
stickyIndex: undefined,
|
|
257
|
+
};
|
|
258
|
+
this.records.push(record);
|
|
259
|
+
this.insertRecord(parent, record, undefined, insert);
|
|
260
|
+
this.reconcileStickyRecords();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
insertProjectedChild(
|
|
264
|
+
parent: ISymbioteNode,
|
|
265
|
+
child: ISymbioteNode,
|
|
266
|
+
beforeChild: ISymbioteNode | null,
|
|
267
|
+
insert: IInsert,
|
|
268
|
+
): void {
|
|
269
|
+
dlog(
|
|
270
|
+
`Angular ScrollView projection insertProjectedChild parent=${parent.component} child=${child.component} before=${beforeChild ? `${beforeChild.component}` : 'null'} recordsBefore=${this.records.length}`,
|
|
271
|
+
);
|
|
272
|
+
const existing = this.records.find(record => record.child === child);
|
|
273
|
+
if (existing !== undefined) this.records.splice(this.records.indexOf(existing), 1);
|
|
274
|
+
const beforeRecord =
|
|
275
|
+
beforeChild === null
|
|
276
|
+
? undefined
|
|
277
|
+
: this.records.find(
|
|
278
|
+
record => record.child === beforeChild || record.wrapper === beforeChild,
|
|
279
|
+
);
|
|
280
|
+
const record: IProjectedRecord = {
|
|
281
|
+
child,
|
|
282
|
+
wrapper: undefined,
|
|
283
|
+
sticky: undefined,
|
|
284
|
+
stickyIndex: undefined,
|
|
285
|
+
};
|
|
286
|
+
const index =
|
|
287
|
+
beforeRecord === undefined ? this.records.length : this.records.indexOf(beforeRecord);
|
|
288
|
+
this.records.splice(index, 0, record);
|
|
289
|
+
this.insertRecord(parent, record, beforeRecord, insert);
|
|
290
|
+
this.reconcileStickyRecords();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
removeProjectedChild(child: ISymbioteNode, remove: IRemove): boolean {
|
|
294
|
+
const record = this.records.find(entry => entry.child === child || entry.wrapper === child);
|
|
295
|
+
if (record === undefined || this.contentNode === undefined) return false;
|
|
296
|
+
record.sticky?.destroy();
|
|
297
|
+
projectedWrappers.delete(record.child);
|
|
298
|
+
const direct = directNode(record);
|
|
299
|
+
if (direct.parent === this.contentNode) remove(this.contentNode, direct);
|
|
300
|
+
this.records.splice(this.records.indexOf(record), 1);
|
|
301
|
+
this.reconcileStickyRecords();
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
nextStickyHeaderY(index: number): number | undefined {
|
|
306
|
+
const stickyIndices = this.config.stickyHeaderIndices ?? [];
|
|
307
|
+
const next = stickyIndices.find(entry => entry > index);
|
|
308
|
+
return next === undefined ? undefined : this.headerLayoutYs.get(next);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
recordHeaderLayoutY(index: number, y: number): void {
|
|
312
|
+
if (this.headerLayoutYs.get(index) === y) return;
|
|
313
|
+
this.headerLayoutYs.set(index, y);
|
|
314
|
+
for (const record of this.records) record.sticky?.rebuild();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private insertRecord(
|
|
318
|
+
parent: ISymbioteNode,
|
|
319
|
+
record: IProjectedRecord,
|
|
320
|
+
beforeRecord: IProjectedRecord | undefined,
|
|
321
|
+
insert: IInsert,
|
|
322
|
+
): void {
|
|
323
|
+
const before = beforeRecord === undefined ? undefined : directNode(beforeRecord);
|
|
324
|
+
dlog(
|
|
325
|
+
`Angular ScrollView projection insertRecord physicalParent=${parent.component} child=${record.child.component} before=${before ? before.component : 'undefined(append)'} parentIsContentNode=${parent === this.contentNode}`,
|
|
326
|
+
);
|
|
327
|
+
if (before === undefined) insert(parent, record.child);
|
|
328
|
+
else insert(parent, record.child, before);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private reconcileStickyRecords(): void {
|
|
332
|
+
if (this.contentNode === undefined) return;
|
|
333
|
+
dlog(
|
|
334
|
+
`Angular ScrollView projection reconcile contentNode=${this.contentNode.component} records=${this.records.length} contentNodeActualChildren=${this.contentNode.children.length}`,
|
|
335
|
+
);
|
|
336
|
+
const stickyIndices = new Set(this.config.stickyHeaderIndices ?? []);
|
|
337
|
+
if (this.config.customStickyHeaderComponent !== undefined && stickyIndices.size > 0) {
|
|
338
|
+
dlog(
|
|
339
|
+
'Angular ScrollView projection uses built-in sticky wrapper; custom StickyHeaderComponent is explicit-composition only',
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let paintIndex = 0;
|
|
344
|
+
for (const record of [...this.records]) {
|
|
345
|
+
if (this.config.excludeRefreshControl && isProjectedRefreshControl(record.child)) {
|
|
346
|
+
this.removeProjectedChild(record.child, (parent, child) => removeChild(parent, child));
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const countsAsChild = !isAnchor(record.child) && !isProjectedRefreshControl(record.child);
|
|
350
|
+
const childIndex = paintIndex;
|
|
351
|
+
if (countsAsChild) paintIndex += 1;
|
|
352
|
+
const shouldWrap = countsAsChild && stickyIndices.has(childIndex);
|
|
353
|
+
if (shouldWrap && record.wrapper === undefined) this.wrapRecord(record, childIndex);
|
|
354
|
+
else if (!shouldWrap && record.wrapper !== undefined) this.unwrapRecord(record);
|
|
355
|
+
else if (shouldWrap && record.stickyIndex !== childIndex) {
|
|
356
|
+
record.sticky?.destroy();
|
|
357
|
+
record.sticky = new StickyProjectionWrapper(this, childIndex, directNode(record));
|
|
358
|
+
record.stickyIndex = childIndex;
|
|
359
|
+
} else if (shouldWrap) record.sticky?.rebuild();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Auto projection runs after Angular has already created projected child host nodes. At this
|
|
364
|
+
// renderer boundary we can AOT-safely rearrange engine nodes, but we cannot instantiate an
|
|
365
|
+
// arbitrary Angular component class as a wrapper without owning a ViewContainerRef/injector and
|
|
366
|
+
// Angular projectable nodes for its <ng-content>. Therefore the automatic sticky path always uses
|
|
367
|
+
// the built-in engine-node wrapper; custom StickyHeaderComponent wrappers remain supported by
|
|
368
|
+
// explicit Angular composition with <ScrollViewStickyHeader>/<symbiote-sticky-header> or a user
|
|
369
|
+
// component that composes it in the template.
|
|
370
|
+
private wrapRecord(record: IProjectedRecord, childIndex: number): void {
|
|
371
|
+
if (this.contentNode === undefined) return;
|
|
372
|
+
const wrapper = createViewNode();
|
|
373
|
+
const currentParent = record.child.parent;
|
|
374
|
+
const currentIndex = currentParent?.children.indexOf(record.child) ?? -1;
|
|
375
|
+
if (currentParent === this.contentNode && currentIndex >= 0) {
|
|
376
|
+
insertBefore(this.contentNode, wrapper, record.child);
|
|
377
|
+
removeChild(this.contentNode, record.child);
|
|
378
|
+
} else {
|
|
379
|
+
appendChild(this.contentNode, wrapper);
|
|
380
|
+
}
|
|
381
|
+
appendChild(wrapper, record.child);
|
|
382
|
+
record.wrapper = wrapper;
|
|
383
|
+
record.sticky = new StickyProjectionWrapper(this, childIndex, wrapper);
|
|
384
|
+
record.stickyIndex = childIndex;
|
|
385
|
+
projectedWrappers.set(record.child, record);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private unwrapRecord(record: IProjectedRecord): void {
|
|
389
|
+
if (this.contentNode === undefined || record.wrapper === undefined) return;
|
|
390
|
+
record.sticky?.destroy();
|
|
391
|
+
record.sticky = undefined;
|
|
392
|
+
record.stickyIndex = undefined;
|
|
393
|
+
const wrapper = record.wrapper;
|
|
394
|
+
insertBefore(this.contentNode, record.child, wrapper);
|
|
395
|
+
removeChild(this.contentNode, wrapper);
|
|
396
|
+
record.wrapper = undefined;
|
|
397
|
+
projectedWrappers.delete(record.child);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function getScrollViewProjection(
|
|
402
|
+
node: ISymbioteNode,
|
|
403
|
+
): ScrollViewProjectionController | undefined {
|
|
404
|
+
return contentProjection.get(node);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function removeScrollViewProjectedChild(child: ISymbioteNode, remove: IRemove): boolean {
|
|
408
|
+
const record = projectedWrappers.get(child);
|
|
409
|
+
if (record === undefined) return false;
|
|
410
|
+
const controller = record.wrapper?.parent
|
|
411
|
+
? contentProjection.get(record.wrapper.parent)
|
|
412
|
+
: undefined;
|
|
413
|
+
return controller?.removeProjectedChild(child, remove) ?? false;
|
|
414
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Regression test for the anchor/class bug: a composed component's own
|
|
2
|
+
// use-site `class="..."` resolves through Angular's addClass/removeClass onto the component's
|
|
3
|
+
// non-painting ANCHOR host (ANCHOR_HOST_COMPONENTS in renderer.ts), never onto the real Fabric
|
|
4
|
+
// node its own template renders — so the resolved style was silently lost. Fixed via
|
|
5
|
+
// anchorHostStyle(this.elementRef) merged into ScrollView's own scrollProps/androidWrappedScrollProps
|
|
6
|
+
// getters (see index.ios.ts / index.android.ts). Mirrors pressable.test.ts's "resolves a class=" case,
|
|
7
|
+
// covering BOTH platforms and the Android refresh-control-wrapped branch (androidWrappedScrollProps
|
|
8
|
+
// is a SEPARATE override from scrollProps, since it recomputes `style` from scratch rather than
|
|
9
|
+
// inheriting the base getter's result).
|
|
10
|
+
import '@angular/compiler';
|
|
11
|
+
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
13
|
+
import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
|
|
14
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
15
|
+
|
|
16
|
+
import { mount, unmount } from '../../render';
|
|
17
|
+
import { RefreshControl } from '../refresh-control';
|
|
18
|
+
import { ScrollView as AndroidScrollView } from './index.android';
|
|
19
|
+
import { ScrollView as IOSScrollView } from './index.ios';
|
|
20
|
+
|
|
21
|
+
const ROOT_TAG = 950;
|
|
22
|
+
const fabric = installFabric();
|
|
23
|
+
const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
|
|
24
|
+
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'symbiote-scroll-ios-class-host',
|
|
27
|
+
standalone: true,
|
|
28
|
+
imports: [IOSScrollView],
|
|
29
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
30
|
+
template: `
|
|
31
|
+
<ScrollView class="card">
|
|
32
|
+
<symbiote-view testID="cell"></symbiote-view>
|
|
33
|
+
</ScrollView>
|
|
34
|
+
`,
|
|
35
|
+
})
|
|
36
|
+
class IOSScrollViewClassHost {}
|
|
37
|
+
|
|
38
|
+
@Component({
|
|
39
|
+
selector: 'symbiote-scroll-android-class-host',
|
|
40
|
+
standalone: true,
|
|
41
|
+
imports: [AndroidScrollView],
|
|
42
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
43
|
+
template: `
|
|
44
|
+
<ScrollView class="card">
|
|
45
|
+
<symbiote-view testID="cell"></symbiote-view>
|
|
46
|
+
</ScrollView>
|
|
47
|
+
`,
|
|
48
|
+
})
|
|
49
|
+
class AndroidScrollViewClassHost {}
|
|
50
|
+
|
|
51
|
+
@Component({
|
|
52
|
+
selector: 'symbiote-scroll-android-wrapped-class-host',
|
|
53
|
+
standalone: true,
|
|
54
|
+
imports: [AndroidScrollView, RefreshControl],
|
|
55
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
56
|
+
template: `
|
|
57
|
+
<ScrollView class="card">
|
|
58
|
+
<RefreshControl [refreshing]="true" (refresh)="refresh()" />
|
|
59
|
+
<symbiote-view testID="cell"></symbiote-view>
|
|
60
|
+
</ScrollView>
|
|
61
|
+
`,
|
|
62
|
+
})
|
|
63
|
+
class AndroidWrappedScrollViewClassHost {
|
|
64
|
+
refresh = (): void => undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Component({
|
|
68
|
+
selector: 'symbiote-scroll-android-wrapped-layout-class-host',
|
|
69
|
+
standalone: true,
|
|
70
|
+
imports: [AndroidScrollView, RefreshControl],
|
|
71
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
72
|
+
template: `
|
|
73
|
+
<ScrollView class="box">
|
|
74
|
+
<RefreshControl [refreshing]="true" (refresh)="refresh()" />
|
|
75
|
+
<symbiote-view testID="cell"></symbiote-view>
|
|
76
|
+
</ScrollView>
|
|
77
|
+
`,
|
|
78
|
+
})
|
|
79
|
+
class AndroidWrappedScrollViewLayoutClassHost {
|
|
80
|
+
refresh = (): void => undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
fabric.reset();
|
|
85
|
+
registerStyles({ card: { backgroundColor: 'red' }, box: { flex: 1, height: 84 } });
|
|
86
|
+
});
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
unmount(ROOT_TAG);
|
|
89
|
+
clearGlobalStyles();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('ScrollView anchor class= resolution', () => {
|
|
93
|
+
it('resolves a class= on the iOS ScrollView use site onto the real committed scroll host', async () => {
|
|
94
|
+
mount(ROOT_TAG, IOSScrollViewClassHost);
|
|
95
|
+
await tick();
|
|
96
|
+
|
|
97
|
+
const node = fabric.find(n => n.props.backgroundColor === 'red');
|
|
98
|
+
expect(node, 'a real Fabric node carries the class-derived style').toBeDefined();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('resolves a class= on the Android ScrollView use site (no refresh control)', async () => {
|
|
102
|
+
mount(ROOT_TAG, AndroidScrollViewClassHost);
|
|
103
|
+
await tick();
|
|
104
|
+
|
|
105
|
+
const node = fabric.find(n => n.props.backgroundColor === 'red');
|
|
106
|
+
expect(node, 'a real Fabric node carries the class-derived style').toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('resolves a class= on the Android ScrollView use site wrapped by a projected RefreshControl', async () => {
|
|
110
|
+
mount(ROOT_TAG, AndroidWrappedScrollViewClassHost);
|
|
111
|
+
await tick();
|
|
112
|
+
|
|
113
|
+
const node = fabric.find(n => n.props.backgroundColor === 'red');
|
|
114
|
+
expect(node, 'a real Fabric node carries the class-derived style').toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Regression for a real device bug distinct from the one above: a class-derived LAYOUT
|
|
118
|
+
// property (flex/height/…) must reach the OUTER refresh-control wrapper via splitLayoutProps,
|
|
119
|
+
// not just the inner scroll view — a class carrying only a color property (the test above)
|
|
120
|
+
// never exercises this split at all, since color is never a layout key. Without
|
|
121
|
+
// `layoutSplitStyle` feeding the anchor's style INTO the split (not tacked on after), the
|
|
122
|
+
// wrapper never receives its flex/height share and collapses to zero size — the whole
|
|
123
|
+
// ScrollView renders nothing on a real Android device. Mirrors the Vue adapter's identical
|
|
124
|
+
// `layoutSplitStyle` fix (same root cause, same symptom).
|
|
125
|
+
it('resolves a class-derived LAYOUT style onto the Android outer refresh-control wrapper', async () => {
|
|
126
|
+
mount(ROOT_TAG, AndroidWrappedScrollViewLayoutClassHost);
|
|
127
|
+
await tick();
|
|
128
|
+
|
|
129
|
+
const wrapper = fabric.find(n => n.props.refreshing === true);
|
|
130
|
+
expect(wrapper, 'the outer refresh-control wrapper committed').toBeDefined();
|
|
131
|
+
expect(wrapper?.props.flex).toBe(1);
|
|
132
|
+
expect(wrapper?.props.height).toBe(84);
|
|
133
|
+
});
|
|
134
|
+
});
|