@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,284 @@
|
|
|
1
|
+
// RefreshControl, the Angular lifecycle half. On iOS this is the PullToRefreshView Fabric node that
|
|
2
|
+
// lives INSIDE a ScrollView (a childless sibling before the content container); on Android it is
|
|
3
|
+
// AndroidSwipeRefreshLayout and WRAPS the scroll view, receiving it through <ng-content>.
|
|
4
|
+
// The Angular twin of the React/Vue RefreshControl. There is no JS-side platform renaming and no
|
|
5
|
+
// shared render fn — every prop forwards straight to the native node, which reads the ones it
|
|
6
|
+
// understands and ignores the rest, so the Android-only and iOS-only families ride down harmlessly
|
|
7
|
+
// on both. So this folds aria/role through the shared resolveAccessibilityProps and maps the native
|
|
8
|
+
// props + a11y + onRefresh straight onto the symbiote-refresh-control host, children via <ng-content>.
|
|
9
|
+
// No platform branch (one composed component both platforms), so this stays a flat single file.
|
|
10
|
+
//
|
|
11
|
+
// `refreshing` is a controlled prop: the parent owns it and pushes it down each commit; native
|
|
12
|
+
// reports the gesture via the direct `topRefresh` event, which the engine routes to the host's
|
|
13
|
+
// `refresh` listener (Angular blocks [onX] property bindings; events flow through (event) only).
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
CUSTOM_ELEMENTS_SCHEMA,
|
|
17
|
+
ChangeDetectionStrategy,
|
|
18
|
+
Component,
|
|
19
|
+
ElementRef,
|
|
20
|
+
EventEmitter,
|
|
21
|
+
inject,
|
|
22
|
+
Input,
|
|
23
|
+
Output,
|
|
24
|
+
ViewChild,
|
|
25
|
+
type AfterViewInit,
|
|
26
|
+
type OnChanges,
|
|
27
|
+
type OnInit,
|
|
28
|
+
type SimpleChanges,
|
|
29
|
+
} from '@angular/core';
|
|
30
|
+
import {
|
|
31
|
+
resolveAccessibilityProps,
|
|
32
|
+
type IAccessibilityProps,
|
|
33
|
+
type IAccessibilityStateValue,
|
|
34
|
+
type IAriaProps,
|
|
35
|
+
} from '@symbiote-native/components';
|
|
36
|
+
import {
|
|
37
|
+
dispatchViewCommand,
|
|
38
|
+
dlog,
|
|
39
|
+
isSymbioteEvent,
|
|
40
|
+
isSymbioteNode,
|
|
41
|
+
type IStyleProp,
|
|
42
|
+
type ISymbioteEvent,
|
|
43
|
+
type IViewStyle,
|
|
44
|
+
} from '@symbiote-native/engine';
|
|
45
|
+
import { anchorHostStyle, RefreshControlHost, SymbioteHostPropsDirective } from '../../primitives';
|
|
46
|
+
|
|
47
|
+
// Record<string, unknown> already tolerates `style` holding the [anchorHostStyle, this.style]
|
|
48
|
+
// array anchorHostStyle's merge produces (see hostProps below) — no widening needed.
|
|
49
|
+
type IHostProps = Record<string, unknown>;
|
|
50
|
+
|
|
51
|
+
// Mirrors React's IRefreshControlProps minus children (Angular takes the Android-wrapped scroll view
|
|
52
|
+
// via <ng-content>), declared per-adapter over the shared accessibility base since the framework-specific
|
|
53
|
+
// children slot keeps it from being fully shared across adapters. Read the React reference for the
|
|
54
|
+
// native prop names.
|
|
55
|
+
export interface IAngularRefreshControlProps extends IAccessibilityProps, IAriaProps {
|
|
56
|
+
refreshing: boolean;
|
|
57
|
+
// RN's onRefresh is `() => void | Promise<void>`, the handler may be async; the promise is
|
|
58
|
+
// fire-and-forget (native already starts refreshing on the gesture).
|
|
59
|
+
onRefresh?: () => void | Promise<void>;
|
|
60
|
+
tintColor?: string;
|
|
61
|
+
title?: string;
|
|
62
|
+
titleColor?: string;
|
|
63
|
+
progressViewOffset?: number;
|
|
64
|
+
// Android-only spinner styling (RN RefreshControlPropsAndroid): `colors` are the indicator's
|
|
65
|
+
// animated stroke colors, `progressBackgroundColor` the disc behind it, `size` the diameter preset.
|
|
66
|
+
// AndroidSwipeRefreshLayout reads them; PullToRefreshView on iOS ignores unknown props.
|
|
67
|
+
colors?: readonly string[];
|
|
68
|
+
progressBackgroundColor?: string;
|
|
69
|
+
size?: 'default' | 'large';
|
|
70
|
+
// Android-only native prop forwarded to AndroidSwipeRefreshLayout; iOS native never reads it.
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
// The Android scroll-view wrap injects the layout half of the style onto this host; iOS leaves it
|
|
73
|
+
// unset (the RefreshControl is a childless sibling). Harmless to forward on both.
|
|
74
|
+
style?: IStyleProp<IViewStyle>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// What the RefreshControl component itself takes as plain @Input()s: the full surface minus the
|
|
78
|
+
// refresh/accessibility events, which it exposes as real @Output() EventEmitters instead (mirrors
|
|
79
|
+
// Pressable's IAngularPressableInputs in pressable/index.ts).
|
|
80
|
+
export type IAngularRefreshControlInputs = Omit<
|
|
81
|
+
IAngularRefreshControlProps,
|
|
82
|
+
| 'onRefresh'
|
|
83
|
+
| 'onAccessibilityAction'
|
|
84
|
+
| 'onAccessibilityTap'
|
|
85
|
+
| 'onMagicTap'
|
|
86
|
+
| 'onAccessibilityEscape'
|
|
87
|
+
>;
|
|
88
|
+
|
|
89
|
+
@Component({
|
|
90
|
+
selector: 'RefreshControl',
|
|
91
|
+
standalone: true,
|
|
92
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
93
|
+
imports: [RefreshControlHost, SymbioteHostPropsDirective],
|
|
94
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
95
|
+
template: `
|
|
96
|
+
<symbiote-refresh-control
|
|
97
|
+
#host
|
|
98
|
+
[symbioteHostProps]="hostProps"
|
|
99
|
+
(refresh)="handleRefresh()"
|
|
100
|
+
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
101
|
+
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
102
|
+
(magicTap)="emit(magicTap, $event)"
|
|
103
|
+
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
104
|
+
>
|
|
105
|
+
<ng-content></ng-content>
|
|
106
|
+
</symbiote-refresh-control>
|
|
107
|
+
`,
|
|
108
|
+
})
|
|
109
|
+
export class RefreshControl
|
|
110
|
+
implements IAngularRefreshControlInputs, OnInit, OnChanges, AfterViewInit
|
|
111
|
+
{
|
|
112
|
+
// Controlled prop the parent owns; required to match the React reference surface.
|
|
113
|
+
@Input({ required: true }) refreshing!: boolean;
|
|
114
|
+
// RN's onRefresh is `() => void | Promise<void>`, so the callback shape allows an async handler;
|
|
115
|
+
// the @Output() itself only signals the gesture (fire-and-forget, native already starts refreshing).
|
|
116
|
+
@Output() readonly refresh = new EventEmitter<void>();
|
|
117
|
+
@Input() tintColor?: string;
|
|
118
|
+
@Input() title?: string;
|
|
119
|
+
@Input() titleColor?: string;
|
|
120
|
+
@Input() progressViewOffset?: number;
|
|
121
|
+
@Input() colors?: readonly string[];
|
|
122
|
+
@Input() progressBackgroundColor?: string;
|
|
123
|
+
@Input() size?: 'default' | 'large';
|
|
124
|
+
@Input() enabled?: boolean;
|
|
125
|
+
@Input() style?: IStyleProp<IViewStyle>;
|
|
126
|
+
@Input() testID?: string;
|
|
127
|
+
@Input() nativeID?: string;
|
|
128
|
+
@Input() accessible?: boolean;
|
|
129
|
+
@Input() accessibilityLabel?: string;
|
|
130
|
+
@Input() accessibilityHint?: string;
|
|
131
|
+
@Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
|
|
132
|
+
@Input() accessibilityState?: IAccessibilityStateValue;
|
|
133
|
+
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
134
|
+
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
135
|
+
@Input() accessibilityLabelledBy?: string | string[];
|
|
136
|
+
@Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
137
|
+
@Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
138
|
+
@Input() screenReaderFocusable?: boolean;
|
|
139
|
+
@Input() accessibilityViewIsModal?: boolean;
|
|
140
|
+
@Input() accessibilityElementsHidden?: boolean;
|
|
141
|
+
@Input() accessibilityIgnoresInvertColors?: boolean;
|
|
142
|
+
@Input() accessibilityLanguage?: string;
|
|
143
|
+
@Input() accessibilityRespondsToUserInteraction?: boolean;
|
|
144
|
+
@Input() accessibilityShowsLargeContentViewer?: boolean;
|
|
145
|
+
@Input() accessibilityLargeContentTitle?: string;
|
|
146
|
+
@Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
|
|
147
|
+
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
148
|
+
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
149
|
+
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
150
|
+
@Input() role?: IAriaProps['role'];
|
|
151
|
+
@Input('aria-label') ariaLabel?: string;
|
|
152
|
+
@Input('aria-labelledby') ariaLabelledBy?: string;
|
|
153
|
+
@Input('aria-live') ariaLive?: IAriaProps['aria-live'];
|
|
154
|
+
@Input('aria-hidden') ariaHidden?: boolean;
|
|
155
|
+
@Input('aria-busy') ariaBusy?: boolean;
|
|
156
|
+
@Input('aria-checked') ariaChecked?: boolean | 'mixed';
|
|
157
|
+
@Input('aria-disabled') ariaDisabled?: boolean;
|
|
158
|
+
@Input('aria-expanded') ariaExpanded?: boolean;
|
|
159
|
+
@Input('aria-selected') ariaSelected?: boolean;
|
|
160
|
+
@Input('aria-modal') ariaModal?: boolean;
|
|
161
|
+
@Input('aria-valuemax') ariaValueMax?: number;
|
|
162
|
+
@Input('aria-valuemin') ariaValueMin?: number;
|
|
163
|
+
@Input('aria-valuenow') ariaValueNow?: number;
|
|
164
|
+
@Input('aria-valuetext') ariaValueText?: string;
|
|
165
|
+
|
|
166
|
+
// The inner symbiote-refresh-control primitive — NOT this component's own anchor host. Used for
|
|
167
|
+
// the imperative dispatchViewCommand calls below.
|
|
168
|
+
@ViewChild('host') private host?: RefreshControlHost;
|
|
169
|
+
|
|
170
|
+
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
171
|
+
// onto (see anchorHostStyle's doc comment) — distinct from `host` above, which targets the real
|
|
172
|
+
// inner `symbiote-refresh-control` primitive one level down.
|
|
173
|
+
private readonly elementRef = inject(ElementRef);
|
|
174
|
+
|
|
175
|
+
private lastNativeRefreshing = false;
|
|
176
|
+
private refreshNativeNode: unknown;
|
|
177
|
+
|
|
178
|
+
ngOnInit(): void {
|
|
179
|
+
dlog('RefreshControl -> PullToRefreshView');
|
|
180
|
+
dlog(`RefreshControl refreshing=${String(this.refreshing)}`);
|
|
181
|
+
if (this.enabled !== undefined)
|
|
182
|
+
dlog(`RefreshControl enabled=${String(this.enabled)} (Android-only)`);
|
|
183
|
+
if (this.refresh.observed) dlog('RefreshControl refresh listener wired');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
ngAfterViewInit(): void {
|
|
187
|
+
this.lastNativeRefreshing = this.refreshing;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
191
|
+
const refreshing = changes.refreshing;
|
|
192
|
+
if (refreshing === undefined) return;
|
|
193
|
+
if (refreshing.firstChange) {
|
|
194
|
+
this.lastNativeRefreshing = this.refreshing;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this.syncNativeRefreshing(this.refreshNativeNode ?? this.host?.nativeElement);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Native starts the spinner before JS runs. Mirror RN's RefreshControl controlled-component
|
|
201
|
+
// handshake: remember that native is refreshing, call the user's callback, then after Angular has
|
|
202
|
+
// had a chance to propagate `[refreshing]`, force native back to the JS value if it stayed false.
|
|
203
|
+
handleRefresh(nativeNode: unknown = this.host?.nativeElement): void {
|
|
204
|
+
this.refreshNativeNode = nativeNode;
|
|
205
|
+
this.lastNativeRefreshing = true;
|
|
206
|
+
this.refresh.emit();
|
|
207
|
+
queueMicrotask(() => this.syncNativeRefreshing(nativeNode));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private syncNativeRefreshing(nativeNode: unknown): void {
|
|
211
|
+
if (this.refreshing === this.lastNativeRefreshing) return;
|
|
212
|
+
if (!isSymbioteNode(nativeNode)) return;
|
|
213
|
+
dispatchViewCommand(nativeNode, 'setNativeRefreshing', [this.refreshing]);
|
|
214
|
+
this.lastNativeRefreshing = this.refreshing;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Forward an engine event to the matching @Output(), narrowing the template's untyped $event
|
|
218
|
+
// first. The accessibility* events arrive on the engine's structural event channel.
|
|
219
|
+
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
220
|
+
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// The full native + a11y prop bag applied onto the host in one shot via
|
|
224
|
+
// [symbioteHostProps], instead of enumerating each key as its own template binding. The
|
|
225
|
+
// anchor's class-derived style goes FIRST, this component's own explicit `style` @Input SECOND
|
|
226
|
+
// — flattenStyle's later-wins collapse keeps an explicit [style] winning over its ambient class.
|
|
227
|
+
get hostProps(): IHostProps {
|
|
228
|
+
return {
|
|
229
|
+
refreshing: this.refreshing,
|
|
230
|
+
tintColor: this.tintColor,
|
|
231
|
+
title: this.title,
|
|
232
|
+
titleColor: this.titleColor,
|
|
233
|
+
progressViewOffset: this.progressViewOffset,
|
|
234
|
+
colors: this.colors,
|
|
235
|
+
progressBackgroundColor: this.progressBackgroundColor,
|
|
236
|
+
size: this.size,
|
|
237
|
+
enabled: this.enabled,
|
|
238
|
+
style: [anchorHostStyle(this.elementRef), this.style],
|
|
239
|
+
testID: this.testID,
|
|
240
|
+
nativeID: this.nativeID,
|
|
241
|
+
accessible: this.accessible,
|
|
242
|
+
...this.folded,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Fold the web aria-*/role aliases into the canonical accessibility* props once per render, so the
|
|
247
|
+
// host node never sees an aria-* key (native ignores them) — the shared transform every adapter runs.
|
|
248
|
+
get folded(): Partial<IAngularRefreshControlProps> {
|
|
249
|
+
return resolveAccessibilityProps({
|
|
250
|
+
accessibilityLabel: this.accessibilityLabel,
|
|
251
|
+
accessibilityHint: this.accessibilityHint,
|
|
252
|
+
accessibilityRole: this.accessibilityRole,
|
|
253
|
+
accessibilityState: this.accessibilityState,
|
|
254
|
+
accessibilityValue: this.accessibilityValue,
|
|
255
|
+
accessibilityActions: this.accessibilityActions,
|
|
256
|
+
accessibilityLabelledBy: this.accessibilityLabelledBy,
|
|
257
|
+
importantForAccessibility: this.importantForAccessibility,
|
|
258
|
+
accessibilityLiveRegion: this.accessibilityLiveRegion,
|
|
259
|
+
screenReaderFocusable: this.screenReaderFocusable,
|
|
260
|
+
accessibilityViewIsModal: this.accessibilityViewIsModal,
|
|
261
|
+
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
262
|
+
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
263
|
+
accessibilityLanguage: this.accessibilityLanguage,
|
|
264
|
+
accessibilityRespondsToUserInteraction: this.accessibilityRespondsToUserInteraction,
|
|
265
|
+
accessibilityShowsLargeContentViewer: this.accessibilityShowsLargeContentViewer,
|
|
266
|
+
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
267
|
+
role: this.role,
|
|
268
|
+
'aria-label': this.ariaLabel,
|
|
269
|
+
'aria-labelledby': this.ariaLabelledBy,
|
|
270
|
+
'aria-live': this.ariaLive,
|
|
271
|
+
'aria-hidden': this.ariaHidden,
|
|
272
|
+
'aria-busy': this.ariaBusy,
|
|
273
|
+
'aria-checked': this.ariaChecked,
|
|
274
|
+
'aria-disabled': this.ariaDisabled,
|
|
275
|
+
'aria-expanded': this.ariaExpanded,
|
|
276
|
+
'aria-selected': this.ariaSelected,
|
|
277
|
+
'aria-modal': this.ariaModal,
|
|
278
|
+
'aria-valuemax': this.ariaValueMax,
|
|
279
|
+
'aria-valuemin': this.ariaValueMin,
|
|
280
|
+
'aria-valuenow': this.ariaValueNow,
|
|
281
|
+
'aria-valuetext': this.ariaValueText,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import '@angular/compiler';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
|
|
5
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
6
|
+
|
|
7
|
+
import { mount, unmount } from '../../render';
|
|
8
|
+
import { RefreshControl } from './index';
|
|
9
|
+
|
|
10
|
+
const ROOT_TAG = 914;
|
|
11
|
+
const fabric = installFabric();
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'symbiote-refresh-control-host',
|
|
15
|
+
standalone: true,
|
|
16
|
+
imports: [RefreshControl],
|
|
17
|
+
template: `
|
|
18
|
+
<RefreshControl [refreshing]="false" [testID]="'refresh'" class="spinner"></RefreshControl>
|
|
19
|
+
`,
|
|
20
|
+
})
|
|
21
|
+
class RefreshControlHostFixture {}
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
fabric.reset();
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
unmount(ROOT_TAG);
|
|
28
|
+
clearGlobalStyles();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('RefreshControl', () => {
|
|
32
|
+
it('resolves a class= on the RefreshControl use site onto the real committed view, not the anchor', async () => {
|
|
33
|
+
registerStyles({ spinner: { backgroundColor: 'green' } });
|
|
34
|
+
|
|
35
|
+
mount(ROOT_TAG, RefreshControlHostFixture);
|
|
36
|
+
await new Promise<void>(resolve => setTimeout(resolve, 0));
|
|
37
|
+
|
|
38
|
+
const node = fabric.find(n => n.props.testID === 'refresh');
|
|
39
|
+
expect(node?.props.backgroundColor).toBe('green');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// SafeAreaView, the Angular lifecycle half. A plain view whose native side insets its
|
|
2
|
+
// children to the safe area (notch, rounded corners, system bars). There is no JS-side
|
|
3
|
+
// translation — RN renders the native RCTSafeAreaView and lets the host do the inset math —
|
|
4
|
+
// so this folds aria/role through the shared resolveAccessibilityProps and maps style +
|
|
5
|
+
// a11y + onLayout straight onto the symbiote-safe-area-view host, children via <ng-content>.
|
|
6
|
+
// The Angular twin of the React/Vue SafeAreaView. No platform branch (one Fabric name both
|
|
7
|
+
// platforms), so this stays a flat single file.
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
CUSTOM_ELEMENTS_SCHEMA,
|
|
11
|
+
ChangeDetectionStrategy,
|
|
12
|
+
Component,
|
|
13
|
+
ElementRef,
|
|
14
|
+
EventEmitter,
|
|
15
|
+
inject,
|
|
16
|
+
Input,
|
|
17
|
+
Output,
|
|
18
|
+
} from '@angular/core';
|
|
19
|
+
import {
|
|
20
|
+
resolveAccessibilityProps,
|
|
21
|
+
type IAccessibilityProps,
|
|
22
|
+
type IAccessibilityStateValue,
|
|
23
|
+
type IAriaProps,
|
|
24
|
+
} from '@symbiote-native/components';
|
|
25
|
+
import {
|
|
26
|
+
isSymbioteEvent,
|
|
27
|
+
type IStyleProp,
|
|
28
|
+
type ISymbioteEvent,
|
|
29
|
+
type IViewStyle,
|
|
30
|
+
} from '@symbiote-native/engine';
|
|
31
|
+
import { anchorHostStyle, SafeAreaViewHost, SymbioteHostPropsDirective } from '../../primitives';
|
|
32
|
+
|
|
33
|
+
// Mirrors React's ISafeAreaViewProps minus children (Angular takes children via <ng-content>),
|
|
34
|
+
// declared per-adapter over the shared accessibility base since the framework-specific children
|
|
35
|
+
// slot keeps it from being fully shared across adapters.
|
|
36
|
+
export interface IAngularSafeAreaViewProps extends IAccessibilityProps, IAriaProps {
|
|
37
|
+
style?: IStyleProp<IViewStyle>;
|
|
38
|
+
onLayout?: (event: ISymbioteEvent) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// What the component itself takes as plain @Input()s: the full surface minus onLayout and the
|
|
42
|
+
// accessibility callbacks, which it exposes as real @Output() EventEmitters instead (see the
|
|
43
|
+
// class below), mirroring Pressable's IAngularPressableInputs split.
|
|
44
|
+
export type IAngularSafeAreaViewInputs = Omit<
|
|
45
|
+
IAngularSafeAreaViewProps,
|
|
46
|
+
| 'onLayout'
|
|
47
|
+
| 'onAccessibilityAction'
|
|
48
|
+
| 'onAccessibilityTap'
|
|
49
|
+
| 'onMagicTap'
|
|
50
|
+
| 'onAccessibilityEscape'
|
|
51
|
+
>;
|
|
52
|
+
|
|
53
|
+
@Component({
|
|
54
|
+
selector: 'SafeAreaView',
|
|
55
|
+
standalone: true,
|
|
56
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
57
|
+
imports: [SafeAreaViewHost, SymbioteHostPropsDirective],
|
|
58
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
59
|
+
template: `
|
|
60
|
+
<symbiote-safe-area-view
|
|
61
|
+
[symbioteHostProps]="hostProps"
|
|
62
|
+
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
63
|
+
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
64
|
+
(magicTap)="emit(magicTap, $event)"
|
|
65
|
+
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
66
|
+
(layout)="emit(layout, $event)"
|
|
67
|
+
>
|
|
68
|
+
<ng-content></ng-content>
|
|
69
|
+
</symbiote-safe-area-view>
|
|
70
|
+
`,
|
|
71
|
+
})
|
|
72
|
+
export class SafeAreaView implements IAngularSafeAreaViewInputs {
|
|
73
|
+
@Input() style?: IStyleProp<IViewStyle>;
|
|
74
|
+
// Real @Output()s, not `[onX]="…"` callbacks. Safe to name `layout` the same as the native
|
|
75
|
+
// `layout` event fired inside this component's own template — the engine's bubble() treats
|
|
76
|
+
// ANCHOR_HOST_COMPONENTS as transparent to listener lookup, so there is no double-fire.
|
|
77
|
+
@Output() readonly layout = new EventEmitter<ISymbioteEvent>();
|
|
78
|
+
@Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
|
|
79
|
+
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
80
|
+
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
81
|
+
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
82
|
+
@Input() testID?: string;
|
|
83
|
+
@Input() nativeID?: string;
|
|
84
|
+
@Input() accessible?: boolean;
|
|
85
|
+
@Input() accessibilityLabel?: string;
|
|
86
|
+
@Input() accessibilityHint?: string;
|
|
87
|
+
@Input() accessibilityRole?: IAccessibilityProps['accessibilityRole'];
|
|
88
|
+
@Input() accessibilityState?: IAccessibilityStateValue;
|
|
89
|
+
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
90
|
+
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
91
|
+
@Input() accessibilityLabelledBy?: string | string[];
|
|
92
|
+
@Input() importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
93
|
+
@Input() accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
94
|
+
@Input() screenReaderFocusable?: boolean;
|
|
95
|
+
@Input() accessibilityViewIsModal?: boolean;
|
|
96
|
+
@Input() accessibilityElementsHidden?: boolean;
|
|
97
|
+
@Input() accessibilityIgnoresInvertColors?: boolean;
|
|
98
|
+
@Input() accessibilityLanguage?: string;
|
|
99
|
+
@Input() accessibilityRespondsToUserInteraction?: boolean;
|
|
100
|
+
@Input() accessibilityShowsLargeContentViewer?: boolean;
|
|
101
|
+
@Input() accessibilityLargeContentTitle?: string;
|
|
102
|
+
@Input() role?: IAriaProps['role'];
|
|
103
|
+
@Input('aria-label') ariaLabel?: string;
|
|
104
|
+
@Input('aria-labelledby') ariaLabelledBy?: string;
|
|
105
|
+
@Input('aria-live') ariaLive?: IAriaProps['aria-live'];
|
|
106
|
+
@Input('aria-hidden') ariaHidden?: boolean;
|
|
107
|
+
@Input('aria-busy') ariaBusy?: boolean;
|
|
108
|
+
@Input('aria-checked') ariaChecked?: boolean | 'mixed';
|
|
109
|
+
@Input('aria-disabled') ariaDisabled?: boolean;
|
|
110
|
+
@Input('aria-expanded') ariaExpanded?: boolean;
|
|
111
|
+
@Input('aria-selected') ariaSelected?: boolean;
|
|
112
|
+
@Input('aria-modal') ariaModal?: boolean;
|
|
113
|
+
@Input('aria-valuemax') ariaValueMax?: number;
|
|
114
|
+
@Input('aria-valuemin') ariaValueMin?: number;
|
|
115
|
+
@Input('aria-valuenow') ariaValueNow?: number;
|
|
116
|
+
@Input('aria-valuetext') ariaValueText?: string;
|
|
117
|
+
|
|
118
|
+
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
119
|
+
// onto (see anchorHostStyle's doc comment). SafeAreaView has no inner ViewChild, so this is
|
|
120
|
+
// the only ElementRef in the class.
|
|
121
|
+
private readonly elementRef = inject(ElementRef);
|
|
122
|
+
|
|
123
|
+
// Forward an engine event to the matching @Output(), narrowing the template's untyped
|
|
124
|
+
// $event first. layout / accessibility* arrive on the engine's structural event channel
|
|
125
|
+
// (Angular blocks [onX] property bindings; events flow through (event) only).
|
|
126
|
+
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
127
|
+
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Folds style/testID/nativeID/accessible alongside the accessibility*/aria-*/role surface into
|
|
131
|
+
// one bag for [symbioteHostProps] — resolveAccessibilityProps takes the whole props object (not
|
|
132
|
+
// just the accessibility slice) and returns it with aria-*/role folded into accessibility*, so
|
|
133
|
+
// the host node never sees an aria-* key (native ignores them), same transform React's
|
|
134
|
+
// SafeAreaView runs over its whole `rawProps`. The anchor's class-derived style goes FIRST, this
|
|
135
|
+
// component's own explicit `style` @Input SECOND — flattenStyle's later-wins collapse keeps an
|
|
136
|
+
// explicit [style] winning over its ambient class.
|
|
137
|
+
get hostProps(): Record<string, unknown> {
|
|
138
|
+
return resolveAccessibilityProps({
|
|
139
|
+
style: [anchorHostStyle(this.elementRef), this.style],
|
|
140
|
+
testID: this.testID,
|
|
141
|
+
nativeID: this.nativeID,
|
|
142
|
+
accessible: this.accessible,
|
|
143
|
+
accessibilityLabel: this.accessibilityLabel,
|
|
144
|
+
accessibilityHint: this.accessibilityHint,
|
|
145
|
+
accessibilityRole: this.accessibilityRole,
|
|
146
|
+
accessibilityState: this.accessibilityState,
|
|
147
|
+
accessibilityValue: this.accessibilityValue,
|
|
148
|
+
accessibilityActions: this.accessibilityActions,
|
|
149
|
+
accessibilityLabelledBy: this.accessibilityLabelledBy,
|
|
150
|
+
importantForAccessibility: this.importantForAccessibility,
|
|
151
|
+
accessibilityLiveRegion: this.accessibilityLiveRegion,
|
|
152
|
+
screenReaderFocusable: this.screenReaderFocusable,
|
|
153
|
+
accessibilityViewIsModal: this.accessibilityViewIsModal,
|
|
154
|
+
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
155
|
+
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
156
|
+
accessibilityLanguage: this.accessibilityLanguage,
|
|
157
|
+
accessibilityRespondsToUserInteraction: this.accessibilityRespondsToUserInteraction,
|
|
158
|
+
accessibilityShowsLargeContentViewer: this.accessibilityShowsLargeContentViewer,
|
|
159
|
+
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
160
|
+
role: this.role,
|
|
161
|
+
'aria-label': this.ariaLabel,
|
|
162
|
+
'aria-labelledby': this.ariaLabelledBy,
|
|
163
|
+
'aria-live': this.ariaLive,
|
|
164
|
+
'aria-hidden': this.ariaHidden,
|
|
165
|
+
'aria-busy': this.ariaBusy,
|
|
166
|
+
'aria-checked': this.ariaChecked,
|
|
167
|
+
'aria-disabled': this.ariaDisabled,
|
|
168
|
+
'aria-expanded': this.ariaExpanded,
|
|
169
|
+
'aria-selected': this.ariaSelected,
|
|
170
|
+
'aria-modal': this.ariaModal,
|
|
171
|
+
'aria-valuemax': this.ariaValueMax,
|
|
172
|
+
'aria-valuemin': this.ariaValueMin,
|
|
173
|
+
'aria-valuenow': this.ariaValueNow,
|
|
174
|
+
'aria-valuetext': this.ariaValueText,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import '@angular/compiler';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { clearGlobalStyles, registerStyles } from '@symbiote-native/engine';
|
|
5
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
6
|
+
|
|
7
|
+
import { mount, unmount } from '../../render';
|
|
8
|
+
import { SafeAreaView } from './index';
|
|
9
|
+
|
|
10
|
+
const ROOT_TAG = 910;
|
|
11
|
+
const fabric = installFabric();
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'symbiote-safe-area-view-host',
|
|
15
|
+
standalone: true,
|
|
16
|
+
imports: [SafeAreaView],
|
|
17
|
+
template: `
|
|
18
|
+
<SafeAreaView [testID]="'safe-area'" class="screen">
|
|
19
|
+
<symbiote-text>Hello</symbiote-text>
|
|
20
|
+
</SafeAreaView>
|
|
21
|
+
`,
|
|
22
|
+
})
|
|
23
|
+
class SafeAreaViewHostFixture {}
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
fabric.reset();
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
unmount(ROOT_TAG);
|
|
30
|
+
clearGlobalStyles();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('SafeAreaView', () => {
|
|
34
|
+
it('resolves a class= on the SafeAreaView use site onto the real committed view, not the anchor', async () => {
|
|
35
|
+
registerStyles({ screen: { backgroundColor: 'navy' } });
|
|
36
|
+
|
|
37
|
+
mount(ROOT_TAG, SafeAreaViewHostFixture);
|
|
38
|
+
await new Promise<void>(resolve => setTimeout(resolve, 0));
|
|
39
|
+
|
|
40
|
+
const node = fabric.find(n => n.props.testID === 'safe-area');
|
|
41
|
+
expect(node?.props.backgroundColor).toBe('navy');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Regression test for an Android content-projection bug:
|
|
2
|
+
// ScrollView on Android picks a different native content view per axis (RCTView vs
|
|
3
|
+
// AndroidHorizontalScrollContentView), which used to require a separate <ng-content> per
|
|
4
|
+
// axis x refresh-control combination — and Angular only reliably projects into a component's
|
|
5
|
+
// LAST-declared <ng-content> when 2+ distinct declarations exist. Fixed by routing all four
|
|
6
|
+
// branches through a single shared <ng-template>/<ng-content> via a local template-outlet
|
|
7
|
+
// directive (see index.android.ts). Covers all four static combinations plus a runtime axis
|
|
8
|
+
// flip, which the projection.ts records/reconcile path must survive without dropping children.
|
|
9
|
+
import '@angular/compiler';
|
|
10
|
+
import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
12
|
+
import { installFabric } from '@symbiote-native/test-utils';
|
|
13
|
+
import { mount, unmount } from '../../render';
|
|
14
|
+
import { RefreshControl } from '../refresh-control';
|
|
15
|
+
import { ScrollView } from './index.android';
|
|
16
|
+
|
|
17
|
+
const ROOT_TAG = 919;
|
|
18
|
+
const fabric = installFabric();
|
|
19
|
+
const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
|
|
20
|
+
|
|
21
|
+
function expectCellsProjected(): void {
|
|
22
|
+
expect(fabric.find(node => node.props.testID === 'a')).toBeDefined();
|
|
23
|
+
expect(fabric.find(node => node.props.testID === 'b')).toBeDefined();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class HorizontalNoRefreshApp {}
|
|
27
|
+
Component({
|
|
28
|
+
selector: 'axis-projection-horizontal-no-refresh',
|
|
29
|
+
standalone: true,
|
|
30
|
+
imports: [ScrollView],
|
|
31
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
32
|
+
template: `
|
|
33
|
+
<ScrollView [horizontal]="true">
|
|
34
|
+
<symbiote-view testID="a"></symbiote-view>
|
|
35
|
+
<symbiote-view testID="b"></symbiote-view>
|
|
36
|
+
</ScrollView>
|
|
37
|
+
`,
|
|
38
|
+
})(HorizontalNoRefreshApp);
|
|
39
|
+
|
|
40
|
+
class HorizontalRefreshApp {
|
|
41
|
+
refresh = (): void => {};
|
|
42
|
+
}
|
|
43
|
+
Component({
|
|
44
|
+
selector: 'axis-projection-horizontal-refresh',
|
|
45
|
+
standalone: true,
|
|
46
|
+
imports: [ScrollView, RefreshControl],
|
|
47
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
48
|
+
template: `
|
|
49
|
+
<ScrollView [horizontal]="true">
|
|
50
|
+
<RefreshControl [refreshing]="true" (refresh)="refresh()" />
|
|
51
|
+
<symbiote-view testID="a"></symbiote-view>
|
|
52
|
+
<symbiote-view testID="b"></symbiote-view>
|
|
53
|
+
</ScrollView>
|
|
54
|
+
`,
|
|
55
|
+
})(HorizontalRefreshApp);
|
|
56
|
+
|
|
57
|
+
class VerticalNoRefreshApp {}
|
|
58
|
+
Component({
|
|
59
|
+
selector: 'axis-projection-vertical-no-refresh',
|
|
60
|
+
standalone: true,
|
|
61
|
+
imports: [ScrollView],
|
|
62
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
63
|
+
template: `
|
|
64
|
+
<ScrollView>
|
|
65
|
+
<symbiote-view testID="a"></symbiote-view>
|
|
66
|
+
<symbiote-view testID="b"></symbiote-view>
|
|
67
|
+
</ScrollView>
|
|
68
|
+
`,
|
|
69
|
+
})(VerticalNoRefreshApp);
|
|
70
|
+
|
|
71
|
+
class AxisSwitchApp {
|
|
72
|
+
static readonly horizontal = signal(false);
|
|
73
|
+
get isHorizontal(): boolean {
|
|
74
|
+
return AxisSwitchApp.horizontal();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
Component({
|
|
78
|
+
selector: 'axis-projection-axis-switch',
|
|
79
|
+
standalone: true,
|
|
80
|
+
imports: [ScrollView],
|
|
81
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
82
|
+
template: `
|
|
83
|
+
<ScrollView [horizontal]="isHorizontal">
|
|
84
|
+
<symbiote-view testID="a"></symbiote-view>
|
|
85
|
+
<symbiote-view testID="b"></symbiote-view>
|
|
86
|
+
</ScrollView>
|
|
87
|
+
`,
|
|
88
|
+
})(AxisSwitchApp);
|
|
89
|
+
|
|
90
|
+
beforeEach(() => fabric.reset());
|
|
91
|
+
afterEach(() => unmount(ROOT_TAG));
|
|
92
|
+
|
|
93
|
+
describe('Android ScrollView projects content across every axis x refresh combination', () => {
|
|
94
|
+
it('horizontal, no refresh control', async () => {
|
|
95
|
+
mount(ROOT_TAG, HorizontalNoRefreshApp);
|
|
96
|
+
await tick();
|
|
97
|
+
expectCellsProjected();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('horizontal, with a projected refresh control', async () => {
|
|
101
|
+
mount(ROOT_TAG, HorizontalRefreshApp);
|
|
102
|
+
await tick();
|
|
103
|
+
expectCellsProjected();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('vertical, no refresh control', async () => {
|
|
107
|
+
mount(ROOT_TAG, VerticalNoRefreshApp);
|
|
108
|
+
await tick();
|
|
109
|
+
expectCellsProjected();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('survives a runtime vertical -> horizontal axis switch', async () => {
|
|
113
|
+
AxisSwitchApp.horizontal.set(false);
|
|
114
|
+
mount(ROOT_TAG, AxisSwitchApp);
|
|
115
|
+
await tick();
|
|
116
|
+
expectCellsProjected();
|
|
117
|
+
|
|
118
|
+
AxisSwitchApp.horizontal.set(true);
|
|
119
|
+
await tick();
|
|
120
|
+
await tick();
|
|
121
|
+
expectCellsProjected();
|
|
122
|
+
});
|
|
123
|
+
});
|