@symbiote-native/angular 0.7.0 → 0.8.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 +3 -3
- package/build/angular/bootstrap.js +1 -1
- package/build/angular/bootstrap.js.map +1 -1
- package/build/angular/components/activity-indicator/index.android.js +7 -2
- package/build/angular/components/activity-indicator/index.android.js.map +1 -1
- package/build/angular/components/activity-indicator/index.ios.js +3 -1
- package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
- package/build/angular/components/activity-indicator/shared.d.ts +1 -1
- package/build/angular/components/activity-indicator/shared.js +1 -1
- package/build/angular/components/activity-indicator/shared.js.map +1 -1
- package/build/angular/components/button.js +13 -5
- package/build/angular/components/button.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +1 -1
- package/build/angular/components/flat-list/index.js +47 -14
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image/index.android.js +4 -2
- package/build/angular/components/image/index.android.js.map +1 -1
- package/build/angular/components/image/index.ios.js +4 -2
- package/build/angular/components/image/index.ios.js.map +1 -1
- package/build/angular/components/image/shared.js +18 -6
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/image-background.js +22 -8
- package/build/angular/components/image-background.js.map +1 -1
- package/build/angular/components/input-accessory-view/index.js +7 -2
- package/build/angular/components/input-accessory-view/index.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +1 -0
- package/build/angular/components/keyboard-avoiding-view/index.js +39 -9
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.js +7 -2
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable/index.d.ts +1 -1
- package/build/angular/components/pressable/index.js +6 -2
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control/index.js +3 -1
- package/build/angular/components/refresh-control/index.js.map +1 -1
- package/build/angular/components/safe-area-view/index.js +3 -1
- package/build/angular/components/safe-area-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/index.android.js +44 -14
- package/build/angular/components/scroll-view/index.android.js.map +1 -1
- package/build/angular/components/scroll-view/index.ios.js +13 -5
- package/build/angular/components/scroll-view/index.ios.js.map +1 -1
- package/build/angular/components/scroll-view/projection.d.ts +32 -0
- package/build/angular/components/scroll-view/projection.js +212 -47
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.js +28 -7
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.js +7 -3
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +6 -1
- package/build/angular/components/section-list/index.js +63 -18
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/switch/index.android.js +17 -3
- package/build/angular/components/switch/index.android.js.map +1 -1
- package/build/angular/components/switch/index.ios.js +17 -3
- package/build/angular/components/switch/index.ios.js.map +1 -1
- package/build/angular/components/switch/shared.d.ts +1 -1
- package/build/angular/components/switch/shared.js +5 -2
- package/build/angular/components/switch/shared.js.map +1 -1
- package/build/angular/components/text-input.d.ts +1 -1
- package/build/angular/components/text-input.js +25 -5
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +29 -7
- package/build/angular/components/touchable/index.js +246 -69
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/touchable-native-feedback/index.js +2 -2
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +4 -0
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +5 -2
- package/build/angular/components/virtualized-list/index.js +197 -60
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +18 -1
- package/build/angular/components/virtualized-section-list/index.js +90 -21
- 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.map +1 -1
- package/build/angular/create-portal/index.js +5 -1
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +3 -1
- package/build/angular/descriptor-to-angular/index.js.map +1 -1
- package/build/angular/diagnostics.d.ts +33 -0
- package/build/angular/diagnostics.js +82 -0
- package/build/angular/diagnostics.js.map +1 -0
- package/build/angular/host-instance/index.js +1 -1
- package/build/angular/host-instance/index.js.map +1 -1
- package/build/angular/index.d.ts +5 -2
- package/build/angular/index.js +15 -1
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.js +27 -9
- 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 +1 -1
- package/build/angular/modules/status-bar/index.js.map +1 -1
- package/build/angular/primitives/index.d.ts +19 -2
- package/build/angular/primitives/index.js +39 -3
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +9 -1
- package/build/angular/primitives/shared.js +49 -3
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/render/index.js +48 -5
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.d.ts +1 -0
- package/build/angular/renderer/index.js +37 -2
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/services/color-scheme.service.js.map +1 -1
- package/build/angular/services/window-dimensions.service.js +1 -1
- package/build/angular/services/window-dimensions.service.js.map +1 -1
- package/package.json +5 -5
- package/src/bootstrap.ts +4 -1
- package/src/components/activity-indicator/index.android.ts +7 -2
- package/src/components/activity-indicator/index.ios.ts +3 -1
- package/src/components/activity-indicator/shared.ts +32 -13
- package/src/components/button.ts +19 -6
- package/src/components/flat-list/index.ts +91 -29
- package/src/components/image/index.android.ts +9 -2
- package/src/components/image/index.ios.ts +9 -2
- package/src/components/image/shared.ts +58 -19
- package/src/components/image-background.ts +29 -10
- package/src/components/input-accessory-view/index.ts +27 -10
- package/src/components/keyboard-avoiding-view/index.ts +55 -15
- package/src/components/modal/index.ts +22 -8
- package/src/components/pressable/index.ts +51 -21
- package/src/components/refresh-control/index.ts +22 -8
- package/src/components/safe-area-view/index.ts +16 -7
- package/src/components/scroll-view/index.android.ts +31 -9
- package/src/components/scroll-view/index.ios.ts +17 -4
- package/src/components/scroll-view/projection.ts +290 -63
- package/src/components/scroll-view/shared.ts +93 -26
- package/src/components/scroll-view/sticky-header.ts +25 -8
- package/src/components/section-list/index.ts +61 -19
- package/src/components/switch/index.android.ts +14 -3
- package/src/components/switch/index.ios.ts +14 -3
- package/src/components/switch/shared.ts +15 -4
- package/src/components/text-input.ts +45 -13
- package/src/components/touchable/index.ts +296 -72
- package/src/components/touchable-native-feedback/index.ts +32 -10
- package/src/components/virtualized-list/directives.ts +19 -5
- package/src/components/virtualized-list/index.ts +274 -72
- package/src/components/virtualized-section-list/directives.ts +6 -3
- package/src/components/virtualized-section-list/index.ts +146 -34
- package/src/components.ts +4 -1
- package/src/create-portal/index.ts +8 -2
- package/src/create-tunnel/index.ts +3 -1
- package/src/descriptor-to-angular/index.ts +29 -8
- package/src/diagnostics.ts +138 -0
- package/src/host-instance/index.ts +9 -3
- package/src/index.ts +31 -2
- package/src/modules/animated/create-animated-component.ts +44 -15
- package/src/modules/app-registry/index.ts +3 -1
- package/src/modules/status-bar/index.ts +13 -4
- package/src/primitives/index.ts +46 -2
- package/src/primitives/shared.ts +70 -9
- package/src/render/index.ts +56 -6
- package/src/renderer/index.ts +76 -14
- package/src/services/color-scheme.service.ts +3 -1
- package/src/services/window-dimensions.service.ts +11 -4
|
@@ -164,7 +164,8 @@ export const SCROLL_VIEW_INPUTS = [
|
|
|
164
164
|
// (ReactNode children, ReactElement refreshControl) — Angular takes children via <ng-content> and
|
|
165
165
|
// composes RefreshControl through projection instead. Declared per-adapter over the shared
|
|
166
166
|
// accessibility base since a framework element/ref field can't live in a shared agnostic type.
|
|
167
|
-
export interface IAngularScrollViewProps
|
|
167
|
+
export interface IAngularScrollViewProps
|
|
168
|
+
extends IAccessibilityProps, IAriaProps {
|
|
168
169
|
style?: IStyleProp<IViewStyle>;
|
|
169
170
|
// A bare string resolves through the shared style registry, like `class` on the host node.
|
|
170
171
|
contentContainerStyle?: IStyleProp<IViewStyle> | string;
|
|
@@ -176,7 +177,12 @@ export interface IAngularScrollViewProps extends IAccessibilityProps, IAriaProps
|
|
|
176
177
|
bounces?: boolean;
|
|
177
178
|
decelerationRate?: 'normal' | 'fast' | number;
|
|
178
179
|
scrollEventThrottle?: number;
|
|
179
|
-
contentInset?: {
|
|
180
|
+
contentInset?: {
|
|
181
|
+
top?: number;
|
|
182
|
+
left?: number;
|
|
183
|
+
bottom?: number;
|
|
184
|
+
right?: number;
|
|
185
|
+
};
|
|
180
186
|
contentOffset?: { x: number; y: number };
|
|
181
187
|
removeClippedSubviews?: boolean;
|
|
182
188
|
snapToInterval?: number;
|
|
@@ -204,11 +210,17 @@ export interface IAngularScrollViewProps extends IAccessibilityProps, IAriaProps
|
|
|
204
210
|
alwaysBounceHorizontal?: boolean;
|
|
205
211
|
alwaysBounceVertical?: boolean;
|
|
206
212
|
centerContent?: boolean;
|
|
207
|
-
scrollIndicatorInsets?: {
|
|
213
|
+
scrollIndicatorInsets?: {
|
|
214
|
+
top?: number;
|
|
215
|
+
left?: number;
|
|
216
|
+
bottom?: number;
|
|
217
|
+
right?: number;
|
|
218
|
+
};
|
|
208
219
|
indicatorStyle?: 'default' | 'black' | 'white';
|
|
209
220
|
directionalLockEnabled?: boolean;
|
|
210
221
|
automaticallyAdjustKeyboardInsets?: boolean;
|
|
211
|
-
contentInsetAdjustmentBehavior?:
|
|
222
|
+
contentInsetAdjustmentBehavior?:
|
|
223
|
+
'automatic' | 'scrollableAxes' | 'never' | 'always';
|
|
212
224
|
minimumZoomScale?: number;
|
|
213
225
|
maximumZoomScale?: number;
|
|
214
226
|
zoomScale?: number;
|
|
@@ -399,7 +411,8 @@ export abstract class ScrollViewBase
|
|
|
399
411
|
@ViewChild('host', { read: SymbioteHostPropsDirective })
|
|
400
412
|
private hostDirective?: SymbioteHostPropsDirective;
|
|
401
413
|
|
|
402
|
-
@ContentChild(RefreshControl)
|
|
414
|
+
@ContentChild(RefreshControl)
|
|
415
|
+
protected projectedRefreshControl?: RefreshControl;
|
|
403
416
|
|
|
404
417
|
// This component's OWN anchor host — where a `class="..."` at the use site resolves (see
|
|
405
418
|
// anchorHostStyle's doc comment) — NOT `#host` in the platform templates, which targets the real
|
|
@@ -459,7 +472,9 @@ export abstract class ScrollViewBase
|
|
|
459
472
|
// (commit.ts) falls back to Object.is on a function leaf, so even a structurally-identical bag
|
|
460
473
|
// reads as "props changed" and forces a real Fabric re-clone that cascades up every ancestor
|
|
461
474
|
// (see AnimatedComponentBase.reconcile()'s identical warning).
|
|
462
|
-
private readonly handleInvertedStickyLayout = (
|
|
475
|
+
private readonly handleInvertedStickyLayout = (
|
|
476
|
+
event: ISymbioteEvent,
|
|
477
|
+
): void => {
|
|
463
478
|
const height = readLayoutDimension(event, 'height');
|
|
464
479
|
// The projection controller is pushed from here rather than picked up on the next prop-bag
|
|
465
480
|
// read: viewportHeight is internal mutable state, so once `scrollProps` became a memoized
|
|
@@ -502,7 +517,8 @@ export abstract class ScrollViewBase
|
|
|
502
517
|
// new listener wrapper (and a new AnimatedEvent walking `collectMappedValues`) on every unrelated
|
|
503
518
|
// change-detection pass while sticky headers are active.
|
|
504
519
|
private cachedStickyOnScrollSource: IScrollHandler | undefined;
|
|
505
|
-
private cachedStickyOnScrollHandler:
|
|
520
|
+
private cachedStickyOnScrollHandler:
|
|
521
|
+
((...args: readonly unknown[]) => void) | undefined;
|
|
506
522
|
|
|
507
523
|
get isHorizontal(): boolean {
|
|
508
524
|
const value = this.horizontal === true;
|
|
@@ -516,20 +532,26 @@ export abstract class ScrollViewBase
|
|
|
516
532
|
}
|
|
517
533
|
|
|
518
534
|
private get hasStickyHeaders(): boolean {
|
|
519
|
-
return
|
|
535
|
+
return (
|
|
536
|
+
this.stickyHeaderIndices !== undefined &&
|
|
537
|
+
this.stickyHeaderIndices.length > 0
|
|
538
|
+
);
|
|
520
539
|
}
|
|
521
540
|
|
|
522
541
|
// A class-name string resolves through the shared registry before it reaches
|
|
523
542
|
// selectScrollIntrinsics, which only understands style objects/arrays.
|
|
524
|
-
private get resolvedContentContainerStyle():
|
|
543
|
+
private get resolvedContentContainerStyle():
|
|
544
|
+
IStyleProp<IViewStyle> | undefined {
|
|
525
545
|
return typeof this.contentContainerStyle === 'string'
|
|
526
546
|
? resolveClassName(this.contentContainerStyle)
|
|
527
547
|
: this.contentContainerStyle;
|
|
528
548
|
}
|
|
529
549
|
|
|
530
550
|
private get scrollViewBaseStyle(): IViewStyle {
|
|
531
|
-
return selectScrollIntrinsics(
|
|
532
|
-
.
|
|
551
|
+
return selectScrollIntrinsics(
|
|
552
|
+
this.isHorizontal,
|
|
553
|
+
this.resolvedContentContainerStyle,
|
|
554
|
+
).scrollViewBaseStyle;
|
|
533
555
|
}
|
|
534
556
|
|
|
535
557
|
// Bridges non-reactive @Input fields into the reactive graph, so a computed() can memoize a
|
|
@@ -623,7 +645,8 @@ export abstract class ScrollViewBase
|
|
|
623
645
|
// value each frame (JS jitter). The DECISIONS (which path, throttle defaults, whether to
|
|
624
646
|
// capture viewport height) are folded out to the shared resolveScrollForwarding; Angular only
|
|
625
647
|
// EXECUTES them, keeping stable-reference handlers so a fresh closure never forces a re-clone.
|
|
626
|
-
const nativeStickyAvailable =
|
|
648
|
+
const nativeStickyAvailable =
|
|
649
|
+
this.hasStickyHeaders && isNativeAnimatedAvailable();
|
|
627
650
|
const forwarding = resolveScrollForwarding({
|
|
628
651
|
hasStickyHeaders: this.hasStickyHeaders,
|
|
629
652
|
nativeStickyAvailable,
|
|
@@ -725,7 +748,9 @@ export abstract class ScrollViewBase
|
|
|
725
748
|
this.projectedRefreshControl?.handleRefresh(nativeNode);
|
|
726
749
|
}
|
|
727
750
|
|
|
728
|
-
private refreshControlProps(
|
|
751
|
+
private refreshControlProps(
|
|
752
|
+
style?: IStyleProp<IViewStyle>,
|
|
753
|
+
): Record<string, unknown> {
|
|
729
754
|
const refresh = this.projectedRefreshControl;
|
|
730
755
|
if (refresh === undefined) return {};
|
|
731
756
|
return compact({
|
|
@@ -765,8 +790,14 @@ export abstract class ScrollViewBase
|
|
|
765
790
|
this.isHorizontal,
|
|
766
791
|
this.resolvedContentContainerStyle,
|
|
767
792
|
);
|
|
768
|
-
const bag: Record<string, unknown> = {
|
|
769
|
-
|
|
793
|
+
const bag: Record<string, unknown> = {
|
|
794
|
+
style: contentStyle,
|
|
795
|
+
collapsable: false,
|
|
796
|
+
};
|
|
797
|
+
if (
|
|
798
|
+
this.maintainVisibleContentPosition !== undefined ||
|
|
799
|
+
this.snapToAlignment !== undefined
|
|
800
|
+
) {
|
|
770
801
|
bag.collapsableChildren = false;
|
|
771
802
|
}
|
|
772
803
|
bag.onLayout = this.handleContentLayout;
|
|
@@ -783,7 +814,9 @@ export abstract class ScrollViewBase
|
|
|
783
814
|
// subscribes a template's (event) listeners in its CREATION pass, before any input is written and
|
|
784
815
|
// before this component's own view is first refreshed - so `observed` is already settled at the
|
|
785
816
|
// first evaluation and cannot flip afterwards without destroying the whole component.
|
|
786
|
-
private emitterCallback(
|
|
817
|
+
private emitterCallback(
|
|
818
|
+
emitter: EventEmitter<ISymbioteEvent>,
|
|
819
|
+
): IScrollHandler | undefined {
|
|
787
820
|
if (!emitter.observed) return undefined;
|
|
788
821
|
let cached = this.emitterCallbackCache.get(emitter);
|
|
789
822
|
if (cached === undefined) {
|
|
@@ -807,7 +840,10 @@ export abstract class ScrollViewBase
|
|
|
807
840
|
[{ nativeEvent: { contentOffset: { y: this.scrollAnimatedValue } } }],
|
|
808
841
|
userOnScroll === undefined
|
|
809
842
|
? undefined
|
|
810
|
-
: {
|
|
843
|
+
: {
|
|
844
|
+
listener: (...args: readonly unknown[]) =>
|
|
845
|
+
forwardScrollEvent(userOnScroll, args),
|
|
846
|
+
},
|
|
811
847
|
);
|
|
812
848
|
}
|
|
813
849
|
return this.cachedStickyOnScrollHandler;
|
|
@@ -823,7 +859,9 @@ export abstract class ScrollViewBase
|
|
|
823
859
|
// The imperative API a parent reaches via @ViewChild(ScrollView). buildScrollViewHandle is the
|
|
824
860
|
// shared, proven handle (React/Vue use it verbatim); it reads the node through the LAZY getter on
|
|
825
861
|
// every call, so a command before commit no-ops rather than freezing a null node.
|
|
826
|
-
private readonly handle: IScrollViewHandle = buildScrollViewHandle(
|
|
862
|
+
private readonly handle: IScrollViewHandle = buildScrollViewHandle(
|
|
863
|
+
() => this.hostNode,
|
|
864
|
+
);
|
|
827
865
|
|
|
828
866
|
scrollTo(options?: { x?: number; y?: number; animated?: boolean }): void {
|
|
829
867
|
this.handle.scrollTo(options);
|
|
@@ -861,6 +899,20 @@ export abstract class ScrollViewBase
|
|
|
861
899
|
// actually moved, so `signal.set`'s own Object.is check makes an unchanged poll dirty nothing.
|
|
862
900
|
ngDoCheck(): void {
|
|
863
901
|
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
902
|
+
// The attach can want a node that does not exist yet, and nothing else would ask again.
|
|
903
|
+
// ngOnChanges covers "indices arrived late"; it does NOT cover "indices arrived late AND the host
|
|
904
|
+
// node had not committed yet", which is exactly what a VirtualizedList does - it derives
|
|
905
|
+
// stickyHeaderIndices from the measured window, so they land after this component's
|
|
906
|
+
// ngAfterViewInit and before its host node resolves. Device-diagnosed 2026-08-18 on
|
|
907
|
+
// examples/angular BenchmarkScreen STICKY PATH B, whose log ends on `attachSticky NOT attaching
|
|
908
|
+
// (wantsAttach=true hasNode=false)` - sticky never turned on, while PATH A (literal indices in
|
|
909
|
+
// the template) worked because ngAfterViewInit already saw a node. Retried ONLY from that stuck
|
|
910
|
+
// state, so a settled ScrollView pays nothing per check. Device-verified the same day.
|
|
911
|
+
// Not covered headless: sticky-native-attach.test.ts's late-indices case has its node resolved by
|
|
912
|
+
// the time the indices land, and the deferred-creation ordering under `@if` never rendered the
|
|
913
|
+
// ScrollView at all.
|
|
914
|
+
if (this.stickyAttachedEnabled && this.stickyAttachedNode === null)
|
|
915
|
+
this.attachSticky();
|
|
864
916
|
}
|
|
865
917
|
|
|
866
918
|
// The projected RefreshControl is a @ContentChild, not an @Input, so ngOnChanges never fires when
|
|
@@ -869,7 +921,8 @@ export abstract class ScrollViewBase
|
|
|
869
921
|
// hook. Guarded on the flag itself so a check that changed nothing skips the reconcile walk,
|
|
870
922
|
// which the old getter-driven update could not do.
|
|
871
923
|
ngAfterContentChecked(): void {
|
|
872
|
-
if (this.hasProjectedRefreshControl === this.lastHasProjectedRefreshControl)
|
|
924
|
+
if (this.hasProjectedRefreshControl === this.lastHasProjectedRefreshControl)
|
|
925
|
+
return;
|
|
873
926
|
this.lastHasProjectedRefreshControl = this.hasProjectedRefreshControl;
|
|
874
927
|
this.updateProjectionController();
|
|
875
928
|
}
|
|
@@ -910,8 +963,13 @@ export abstract class ScrollViewBase
|
|
|
910
963
|
// Nothing that decides the attach has changed - return rather than detach and rebind the
|
|
911
964
|
// native scroll event, which every unrelated @Input change would otherwise churn now that
|
|
912
965
|
// ngOnChanges drives this too.
|
|
913
|
-
if (
|
|
914
|
-
|
|
966
|
+
if (
|
|
967
|
+
wantsAttach === this.stickyAttachedEnabled &&
|
|
968
|
+
node === this.stickyAttachedNode
|
|
969
|
+
) {
|
|
970
|
+
dlog(
|
|
971
|
+
'STICKY[sv] attachSticky skipped (nothing about the attach changed)',
|
|
972
|
+
);
|
|
915
973
|
return;
|
|
916
974
|
}
|
|
917
975
|
|
|
@@ -931,10 +989,15 @@ export abstract class ScrollViewBase
|
|
|
931
989
|
);
|
|
932
990
|
return;
|
|
933
991
|
}
|
|
934
|
-
dlog(
|
|
992
|
+
dlog(
|
|
993
|
+
'STICKY[sv] attachSticky waiting for commit to bind the native scroll event',
|
|
994
|
+
);
|
|
935
995
|
this.cancelStickyBind = whenCommitted(node, () => {
|
|
936
996
|
dlog('STICKY[sv] attachSticky node committed -> attachStickyScroll');
|
|
937
|
-
this.detachStickyScroll = attachStickyScroll(
|
|
997
|
+
this.detachStickyScroll = attachStickyScroll(
|
|
998
|
+
node,
|
|
999
|
+
this.scrollAnimatedValue,
|
|
1000
|
+
);
|
|
938
1001
|
});
|
|
939
1002
|
}
|
|
940
1003
|
|
|
@@ -957,7 +1020,9 @@ export abstract class ScrollViewBase
|
|
|
957
1020
|
|
|
958
1021
|
// Typed as the a11y intersection WITH the string index so resolveAccessibilityProps's result
|
|
959
1022
|
// stays assignable into the forwarded bag (a genuine narrowing, built at that type — no cast).
|
|
960
|
-
private accessibilityInputs(): IAccessibilityProps &
|
|
1023
|
+
private accessibilityInputs(): IAccessibilityProps &
|
|
1024
|
+
IAriaProps &
|
|
1025
|
+
Record<string, unknown> {
|
|
961
1026
|
return {
|
|
962
1027
|
testID: this.testID,
|
|
963
1028
|
nativeID: this.nativeID,
|
|
@@ -976,8 +1041,10 @@ export abstract class ScrollViewBase
|
|
|
976
1041
|
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
977
1042
|
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
978
1043
|
accessibilityLanguage: this.accessibilityLanguage,
|
|
979
|
-
accessibilityRespondsToUserInteraction:
|
|
980
|
-
|
|
1044
|
+
accessibilityRespondsToUserInteraction:
|
|
1045
|
+
this.accessibilityRespondsToUserInteraction,
|
|
1046
|
+
accessibilityShowsLargeContentViewer:
|
|
1047
|
+
this.accessibilityShowsLargeContentViewer,
|
|
981
1048
|
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
982
1049
|
role: this.role,
|
|
983
1050
|
'aria-label': this['aria-label'],
|
|
@@ -96,7 +96,9 @@ export class ScrollViewStickyHeader
|
|
|
96
96
|
// The animated node driving the transform (RN's animatedTranslateY), replaced by the
|
|
97
97
|
// rebuild-interpolation effect. When the scroll value is native, this interpolation runs on the UI
|
|
98
98
|
// thread: the smooth pin.
|
|
99
|
-
private animatedTranslateY: AnimatedInterpolation = new AnimatedValue(
|
|
99
|
+
private animatedTranslateY: AnimatedInterpolation = new AnimatedValue(
|
|
100
|
+
0,
|
|
101
|
+
).interpolate({
|
|
100
102
|
inputRange: [-1, 0],
|
|
101
103
|
outputRange: [0, 0],
|
|
102
104
|
});
|
|
@@ -135,13 +137,17 @@ export class ScrollViewStickyHeader
|
|
|
135
137
|
// anchor style first so the fixed transform/zIndex below still wins on conflict.
|
|
136
138
|
return [
|
|
137
139
|
anchorHostStyle(this.elementRef),
|
|
138
|
-
{
|
|
140
|
+
{
|
|
141
|
+
transform: [{ translateY: this.animatedTranslateY }],
|
|
142
|
+
zIndex: STICKY_HEADER_Z_INDEX,
|
|
143
|
+
},
|
|
139
144
|
];
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
// Stable reference (bound once) so AnimatedView's directive does not re-wire the layout listener
|
|
143
148
|
// every change-detection pass.
|
|
144
|
-
private readonly onHostLayout = (event: ISymbioteEvent): void =>
|
|
149
|
+
private readonly onHostLayout = (event: ISymbioteEvent): void =>
|
|
150
|
+
this.handleLayout(event);
|
|
145
151
|
|
|
146
152
|
// Also a stable reference, not a getter: jsonEqual (commit.ts) can't structurally compare the
|
|
147
153
|
// embedded onLayout function, so a fresh object here would read as "props changed" on every
|
|
@@ -156,7 +162,9 @@ export class ScrollViewStickyHeader
|
|
|
156
162
|
|
|
157
163
|
// The EXPLICIT debounced translateY overrides the committed transform for hit-testing while
|
|
158
164
|
// animatedTranslateY does the smooth (native-driven) pin (RN ScrollViewStickyHeader.js).
|
|
159
|
-
get passthrough(): {
|
|
165
|
+
get passthrough(): {
|
|
166
|
+
style: { transform: Array<{ translateY: number }> };
|
|
167
|
+
} | null {
|
|
160
168
|
return this.state.translateY !== null
|
|
161
169
|
? { style: { transform: [{ translateY: this.state.translateY }] } }
|
|
162
170
|
: null;
|
|
@@ -181,7 +189,10 @@ export class ScrollViewStickyHeader
|
|
|
181
189
|
case 'rebuild-interpolation': {
|
|
182
190
|
// Detach the old listener, build a fresh interpolation onto the shared scroll value, and
|
|
183
191
|
// wire the settled-value listener (symbiote is always Fabric; RN attaches it only there).
|
|
184
|
-
if (
|
|
192
|
+
if (
|
|
193
|
+
this.interpolation !== undefined &&
|
|
194
|
+
this.listenerId !== undefined
|
|
195
|
+
) {
|
|
185
196
|
this.interpolation.removeListener(this.listenerId);
|
|
186
197
|
this.listenerId = undefined;
|
|
187
198
|
}
|
|
@@ -200,7 +211,8 @@ export class ScrollViewStickyHeader
|
|
|
200
211
|
case 'schedule-debounce':
|
|
201
212
|
// The animated value updates several times per frame; debounce the settled value into the
|
|
202
213
|
// committed transform so hit detection stays current (a Fabric issue, worse on Android).
|
|
203
|
-
if (this.debounceTimer !== undefined)
|
|
214
|
+
if (this.debounceTimer !== undefined)
|
|
215
|
+
clearTimeout(this.debounceTimer);
|
|
204
216
|
this.debounceTimer = setTimeout(() => {
|
|
205
217
|
this.debounceTimer = undefined;
|
|
206
218
|
this.dispatch({ kind: 'debounce-fired', value: effect.value });
|
|
@@ -233,8 +245,13 @@ export class ScrollViewStickyHeader
|
|
|
233
245
|
this.changeDetector.markForCheck();
|
|
234
246
|
}
|
|
235
247
|
|
|
236
|
-
private readonly animatedValueListener = ({
|
|
237
|
-
|
|
248
|
+
private readonly animatedValueListener = ({
|
|
249
|
+
value,
|
|
250
|
+
}: {
|
|
251
|
+
value: number | string;
|
|
252
|
+
}): void => {
|
|
253
|
+
if (typeof value === 'number')
|
|
254
|
+
this.dispatch({ kind: 'animated-tick', value });
|
|
238
255
|
};
|
|
239
256
|
|
|
240
257
|
private teardown(): void {
|
|
@@ -56,7 +56,10 @@ import {
|
|
|
56
56
|
type IVListSeparatorContext,
|
|
57
57
|
} from '../virtualized-list';
|
|
58
58
|
import { VListOutletDirective } from '../virtualized-list/directives';
|
|
59
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
stableAnchorStyle,
|
|
61
|
+
SymbioteStyleInputDirective,
|
|
62
|
+
} from '../../primitives';
|
|
60
63
|
import {
|
|
61
64
|
VSectionFooterDirective,
|
|
62
65
|
VSectionHeaderDirective,
|
|
@@ -120,7 +123,9 @@ export type ISectionListInputs<ItemT> = Omit<
|
|
|
120
123
|
@Component({
|
|
121
124
|
selector: 'SectionList',
|
|
122
125
|
standalone: true,
|
|
123
|
-
hostDirectives: [
|
|
126
|
+
hostDirectives: [
|
|
127
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
128
|
+
],
|
|
124
129
|
imports: [
|
|
125
130
|
VirtualizedSectionList,
|
|
126
131
|
VSectionItemDirective,
|
|
@@ -138,6 +143,7 @@ export type ISectionListInputs<ItemT> = Omit<
|
|
|
138
143
|
<VirtualizedSectionList
|
|
139
144
|
[sections]="sections"
|
|
140
145
|
[keyExtractor]="keyExtractor"
|
|
146
|
+
[getItemLayout]="getItemLayout"
|
|
141
147
|
[stickySectionHeadersEnabled]="stickySectionHeadersEnabled"
|
|
142
148
|
[extraData]="extraData"
|
|
143
149
|
(endReached)="endReached.emit($event)"
|
|
@@ -182,8 +188,12 @@ export type ISectionListInputs<ItemT> = Omit<
|
|
|
182
188
|
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
183
189
|
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
184
190
|
[accessibilityLanguage]="accessibilityLanguage"
|
|
185
|
-
[accessibilityRespondsToUserInteraction]="
|
|
186
|
-
|
|
191
|
+
[accessibilityRespondsToUserInteraction]="
|
|
192
|
+
accessibilityRespondsToUserInteraction
|
|
193
|
+
"
|
|
194
|
+
[accessibilityShowsLargeContentViewer]="
|
|
195
|
+
accessibilityShowsLargeContentViewer
|
|
196
|
+
"
|
|
187
197
|
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
188
198
|
(accessibilityAction)="accessibilityAction.emit($event)"
|
|
189
199
|
(accessibilityTap)="accessibilityTap.emit($event)"
|
|
@@ -214,7 +224,13 @@ export type ISectionListInputs<ItemT> = Omit<
|
|
|
214
224
|
>
|
|
215
225
|
<ng-container
|
|
216
226
|
[vListOutlet]="sectionItemDir?.templateRef"
|
|
217
|
-
[vListOutletContext]="{
|
|
227
|
+
[vListOutletContext]="{
|
|
228
|
+
$implicit: item,
|
|
229
|
+
item,
|
|
230
|
+
index,
|
|
231
|
+
section,
|
|
232
|
+
separators,
|
|
233
|
+
}"
|
|
218
234
|
></ng-container>
|
|
219
235
|
</ng-template>
|
|
220
236
|
@if (sectionHeaderDir !== undefined) {
|
|
@@ -235,17 +251,23 @@ export type ISectionListInputs<ItemT> = Omit<
|
|
|
235
251
|
}
|
|
236
252
|
@if (sectionSeparatorDir !== undefined) {
|
|
237
253
|
<ng-template vSectionSeparator>
|
|
238
|
-
<ng-container
|
|
254
|
+
<ng-container
|
|
255
|
+
[vListOutlet]="sectionSeparatorDir.templateRef"
|
|
256
|
+
></ng-container>
|
|
239
257
|
</ng-template>
|
|
240
258
|
}
|
|
241
259
|
@if (listHeaderDir !== undefined) {
|
|
242
260
|
<ng-template vListHeader>
|
|
243
|
-
<ng-container
|
|
261
|
+
<ng-container
|
|
262
|
+
[vListOutlet]="listHeaderDir.templateRef"
|
|
263
|
+
></ng-container>
|
|
244
264
|
</ng-template>
|
|
245
265
|
}
|
|
246
266
|
@if (listFooterDir !== undefined) {
|
|
247
267
|
<ng-template vListFooter>
|
|
248
|
-
<ng-container
|
|
268
|
+
<ng-container
|
|
269
|
+
[vListOutlet]="listFooterDir.templateRef"
|
|
270
|
+
></ng-container>
|
|
249
271
|
</ng-template>
|
|
250
272
|
}
|
|
251
273
|
@if (listEmptyDir !== undefined) {
|
|
@@ -262,7 +284,9 @@ export type ISectionListInputs<ItemT> = Omit<
|
|
|
262
284
|
>
|
|
263
285
|
<ng-container
|
|
264
286
|
[vListOutlet]="itemSeparatorDir.templateRef"
|
|
265
|
-
[vListOutletContext]="
|
|
287
|
+
[vListOutletContext]="
|
|
288
|
+
itemSeparatorContext(highlighted, leadingItem, trailingItem)
|
|
289
|
+
"
|
|
266
290
|
></ng-container>
|
|
267
291
|
</ng-template>
|
|
268
292
|
}
|
|
@@ -274,11 +298,21 @@ export class SectionList<ItemT = unknown>
|
|
|
274
298
|
{
|
|
275
299
|
@Input({ required: true }) sections!: ReadonlyArray<ISection<ItemT>>;
|
|
276
300
|
@Input() keyExtractor?: (item: ItemT, index: number) => string;
|
|
301
|
+
// Passed through FLAT (sections array + flat entry index); VirtualizedSectionList owns the
|
|
302
|
+
// sections-vs-entries wrapper - see its entryItemLayout getter.
|
|
303
|
+
@Input() getItemLayout?: (
|
|
304
|
+
data: ReadonlyArray<ISection<ItemT>> | null,
|
|
305
|
+
index: number,
|
|
306
|
+
) => { length: number; offset: number; index: number };
|
|
277
307
|
@Input() stickySectionHeadersEnabled?: boolean;
|
|
278
308
|
@Input() extraData?: unknown;
|
|
279
|
-
@Output() readonly endReached = new EventEmitter<{
|
|
309
|
+
@Output() readonly endReached = new EventEmitter<{
|
|
310
|
+
distanceFromEnd: number;
|
|
311
|
+
}>();
|
|
280
312
|
@Input() onEndReachedThreshold?: number;
|
|
281
|
-
@Output() readonly startReached = new EventEmitter<{
|
|
313
|
+
@Output() readonly startReached = new EventEmitter<{
|
|
314
|
+
distanceFromStart: number;
|
|
315
|
+
}>();
|
|
282
316
|
@Input() onStartReachedThreshold?: number;
|
|
283
317
|
@Output() readonly refresh = new EventEmitter<void>();
|
|
284
318
|
@Input() refreshing?: boolean | null;
|
|
@@ -331,8 +365,10 @@ export class SectionList<ItemT = unknown>
|
|
|
331
365
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
332
366
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
333
367
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
334
|
-
@Input()
|
|
335
|
-
|
|
368
|
+
@Input()
|
|
369
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
370
|
+
@Input()
|
|
371
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
336
372
|
@Input() screenReaderFocusable?: boolean;
|
|
337
373
|
@Input() accessibilityViewIsModal?: boolean;
|
|
338
374
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -363,18 +399,24 @@ export class SectionList<ItemT = unknown>
|
|
|
363
399
|
|
|
364
400
|
// The app's section/list authoring templates, captured from the app's DIRECT projected content
|
|
365
401
|
// (single hop — always resolves) and re-stamped onto the inner VirtualizedSectionList above.
|
|
366
|
-
@ContentChild(VSectionItemDirective)
|
|
367
|
-
|
|
368
|
-
@ContentChild(
|
|
369
|
-
|
|
402
|
+
@ContentChild(VSectionItemDirective)
|
|
403
|
+
sectionItemDir?: VSectionItemDirective<ItemT>;
|
|
404
|
+
@ContentChild(VSectionHeaderDirective)
|
|
405
|
+
sectionHeaderDir?: VSectionHeaderDirective<ItemT>;
|
|
406
|
+
@ContentChild(VSectionFooterDirective)
|
|
407
|
+
sectionFooterDir?: VSectionFooterDirective<ItemT>;
|
|
408
|
+
@ContentChild(VSectionSeparatorDirective)
|
|
409
|
+
sectionSeparatorDir?: VSectionSeparatorDirective;
|
|
370
410
|
@ContentChild(VListHeaderDirective) listHeaderDir?: VListHeaderDirective;
|
|
371
411
|
@ContentChild(VListFooterDirective) listFooterDir?: VListFooterDirective;
|
|
372
412
|
@ContentChild(VListEmptyDirective) listEmptyDir?: VListEmptyDirective;
|
|
373
|
-
@ContentChild(VListSeparatorDirective)
|
|
413
|
+
@ContentChild(VListSeparatorDirective)
|
|
414
|
+
itemSeparatorDir?: VListSeparatorDirective<ItemT>;
|
|
374
415
|
|
|
375
416
|
// The composed inner VirtualizedSectionList. Its instance IS an IVirtualizedSectionListHandle, so
|
|
376
417
|
// SectionList's handle delegates straight to it. Available from ngAfterViewInit; reads lazily.
|
|
377
|
-
@ViewChild(VirtualizedSectionList)
|
|
418
|
+
@ViewChild(VirtualizedSectionList)
|
|
419
|
+
private list?: VirtualizedSectionList<ItemT>;
|
|
378
420
|
|
|
379
421
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
380
422
|
// onto (see anchorHostStyle's doc comment) — NOT `list` above, which targets the real inner
|
|
@@ -20,10 +20,18 @@ export type { ISwitchProps, ISwitchTrackColor } from './shared';
|
|
|
20
20
|
@Component({
|
|
21
21
|
selector: 'Switch',
|
|
22
22
|
standalone: true,
|
|
23
|
-
hostDirectives: [
|
|
23
|
+
hostDirectives: [
|
|
24
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
25
|
+
],
|
|
24
26
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
25
27
|
imports: [SwitchHost, SymbioteHostPropsDirective],
|
|
26
|
-
providers: [
|
|
28
|
+
providers: [
|
|
29
|
+
{
|
|
30
|
+
provide: NG_VALUE_ACCESSOR,
|
|
31
|
+
useExisting: forwardRef(() => Switch),
|
|
32
|
+
multi: true,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
27
35
|
inputs: [
|
|
28
36
|
'value',
|
|
29
37
|
'disabled',
|
|
@@ -60,7 +68,10 @@ export class Switch extends SwitchBase implements DoCheck {
|
|
|
60
68
|
|
|
61
69
|
protected readonly platform = {
|
|
62
70
|
snapBackCommand: 'setNativeValue',
|
|
63
|
-
trackColorProps: (
|
|
71
|
+
trackColorProps: (
|
|
72
|
+
value: boolean,
|
|
73
|
+
trackColor?: { false?: string; true?: string },
|
|
74
|
+
) => ({
|
|
64
75
|
trackColorForFalse: trackColor?.false,
|
|
65
76
|
trackColorForTrue: trackColor?.true,
|
|
66
77
|
trackTintColor: value ? trackColor?.true : trackColor?.false,
|
|
@@ -20,10 +20,18 @@ export type { ISwitchProps, ISwitchTrackColor } from './shared';
|
|
|
20
20
|
@Component({
|
|
21
21
|
selector: 'Switch',
|
|
22
22
|
standalone: true,
|
|
23
|
-
hostDirectives: [
|
|
23
|
+
hostDirectives: [
|
|
24
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
25
|
+
],
|
|
24
26
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
25
27
|
imports: [SwitchHost, SymbioteHostPropsDirective],
|
|
26
|
-
providers: [
|
|
28
|
+
providers: [
|
|
29
|
+
{
|
|
30
|
+
provide: NG_VALUE_ACCESSOR,
|
|
31
|
+
useExisting: forwardRef(() => Switch),
|
|
32
|
+
multi: true,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
27
35
|
inputs: [
|
|
28
36
|
'value',
|
|
29
37
|
'disabled',
|
|
@@ -60,7 +68,10 @@ export class Switch extends SwitchBase implements DoCheck {
|
|
|
60
68
|
|
|
61
69
|
protected readonly platform = {
|
|
62
70
|
snapBackCommand: 'setValue',
|
|
63
|
-
trackColorProps: (
|
|
71
|
+
trackColorProps: (
|
|
72
|
+
_value: boolean,
|
|
73
|
+
trackColor?: { false?: string; true?: string },
|
|
74
|
+
) => ({
|
|
64
75
|
onTintColor: trackColor?.true,
|
|
65
76
|
tintColor: trackColor?.false,
|
|
66
77
|
}),
|
|
@@ -9,7 +9,10 @@ import {
|
|
|
9
9
|
type OnChanges,
|
|
10
10
|
} from '@angular/core';
|
|
11
11
|
import type { ControlValueAccessor } from '@angular/forms';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
renderSwitch,
|
|
14
|
+
resolveAccessibilityProps,
|
|
15
|
+
} from '@symbiote-native/components';
|
|
13
16
|
import type {
|
|
14
17
|
ISwitchPlatform,
|
|
15
18
|
ISwitchProps,
|
|
@@ -33,7 +36,10 @@ import {
|
|
|
33
36
|
type IViewStyle,
|
|
34
37
|
} from '@symbiote-native/engine';
|
|
35
38
|
|
|
36
|
-
export type {
|
|
39
|
+
export type {
|
|
40
|
+
ISwitchProps,
|
|
41
|
+
ISwitchTrackColor,
|
|
42
|
+
} from '@symbiote-native/components';
|
|
37
43
|
|
|
38
44
|
export type ISwitchInputs = Pick<
|
|
39
45
|
ISwitchProps,
|
|
@@ -88,7 +94,9 @@ function isSwitchEvent(event: unknown): event is ISymbioteEvent {
|
|
|
88
94
|
// concrete subclasses are real @Components — ngc's AOT build (NG2007) requires it the moment the
|
|
89
95
|
// base itself carries decorated members (@Output() here), mirroring ScrollViewBase.
|
|
90
96
|
@Directive()
|
|
91
|
-
export abstract class SwitchBase
|
|
97
|
+
export abstract class SwitchBase
|
|
98
|
+
implements ISwitchInputs, ControlValueAccessor, OnChanges
|
|
99
|
+
{
|
|
92
100
|
// The value/change lifecycle as real Angular events: `(valueChange)="onToggle($event)"`, not
|
|
93
101
|
// `[onValueChange]="onToggle"` — mirrors Pressable's press/hover @Output() conversion. Unlike
|
|
94
102
|
// Pressable's createPressHandlers, handleChange() below calls `.emit()` unconditionally: it needs
|
|
@@ -191,7 +199,10 @@ export abstract class SwitchBase implements ISwitchInputs, ControlValueAccessor,
|
|
|
191
199
|
if (next === undefined) return;
|
|
192
200
|
|
|
193
201
|
this.valueChange.emit(next);
|
|
194
|
-
this.switchState = switchReducer(this.switchState, {
|
|
202
|
+
this.switchState = switchReducer(this.switchState, {
|
|
203
|
+
type: 'native-reported',
|
|
204
|
+
value: next,
|
|
205
|
+
});
|
|
195
206
|
|
|
196
207
|
const hostNode = readSwitchNode(node);
|
|
197
208
|
if (hostNode === null) return;
|