@symbiote-native/angular 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/symbiote-angular-dev.cjs +20 -19
- package/build/angular/anchor-host-registry.js +2 -2
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/components/activity-indicator/index.android.d.ts +2 -1
- package/build/angular/components/activity-indicator/index.android.js +6 -3
- package/build/angular/components/activity-indicator/index.android.js.map +1 -1
- package/build/angular/components/activity-indicator/index.ios.d.ts +2 -1
- package/build/angular/components/activity-indicator/index.ios.js +6 -3
- package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
- package/build/angular/components/activity-indicator/shared.js +3 -3
- package/build/angular/components/button.d.ts +4 -2
- package/build/angular/components/button.js +28 -17
- package/build/angular/components/button.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +5 -3
- package/build/angular/components/flat-list/index.js +35 -38
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image/index.android.d.ts +8 -3
- package/build/angular/components/image/index.android.js +39 -9
- package/build/angular/components/image/index.android.js.map +1 -1
- package/build/angular/components/image/index.ios.d.ts +8 -3
- package/build/angular/components/image/index.ios.js +39 -9
- package/build/angular/components/image/index.ios.js.map +1 -1
- package/build/angular/components/image/shared.d.ts +4 -1
- package/build/angular/components/image/shared.js +33 -2
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/image-background.d.ts +2 -1
- package/build/angular/components/image-background.js +6 -4
- package/build/angular/components/image-background.js.map +1 -1
- package/build/angular/components/input-accessory-view/index.d.ts +9 -4
- package/build/angular/components/input-accessory-view/index.js +43 -9
- package/build/angular/components/input-accessory-view/index.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +2 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +6 -4
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +11 -8
- package/build/angular/components/modal/index.js +45 -28
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable/index.d.ts +11 -5
- package/build/angular/components/pressable/index.js +61 -12
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control/index.d.ts +8 -4
- package/build/angular/components/refresh-control/index.js +52 -16
- package/build/angular/components/refresh-control/index.js.map +1 -1
- package/build/angular/components/safe-area-view/index.d.ts +9 -4
- package/build/angular/components/safe-area-view/index.js +46 -9
- package/build/angular/components/safe-area-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/index.android.d.ts +2 -3
- package/build/angular/components/scroll-view/index.android.js +49 -52
- package/build/angular/components/scroll-view/index.android.js.map +1 -1
- package/build/angular/components/scroll-view/index.ios.d.ts +2 -3
- package/build/angular/components/scroll-view/index.ios.js +15 -17
- package/build/angular/components/scroll-view/index.ios.js.map +1 -1
- package/build/angular/components/scroll-view/projection.d.ts +3 -0
- package/build/angular/components/scroll-view/projection.js +68 -7
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.d.ts +14 -5
- package/build/angular/components/scroll-view/shared.js +198 -69
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.js +14 -19
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +2 -1
- package/build/angular/components/section-list/index.js +31 -39
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/switch/index.android.d.ts +7 -3
- package/build/angular/components/switch/index.android.js +38 -10
- package/build/angular/components/switch/index.android.js.map +1 -1
- package/build/angular/components/switch/index.ios.d.ts +7 -3
- package/build/angular/components/switch/index.ios.js +38 -10
- package/build/angular/components/switch/index.ios.js.map +1 -1
- package/build/angular/components/switch/shared.d.ts +12 -3
- package/build/angular/components/switch/shared.js +66 -5
- package/build/angular/components/switch/shared.js.map +1 -1
- package/build/angular/components/text-input.d.ts +10 -2
- package/build/angular/components/text-input.js +46 -6
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +17 -8
- package/build/angular/components/touchable/index.js +78 -19
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +6 -3
- package/build/angular/components/touchable-native-feedback/index.js +61 -25
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +18 -18
- package/build/angular/components/virtualized-list/index.d.ts +12 -4
- package/build/angular/components/virtualized-list/index.js +163 -68
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js +12 -12
- package/build/angular/components/virtualized-section-list/index.d.ts +2 -1
- package/build/angular/components/virtualized-section-list/index.js +6 -4
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +1 -1
- package/build/angular/components.js +5 -0
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.js +29 -35
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js +19 -22
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +3 -3
- package/build/angular/index.d.ts +4 -2
- package/build/angular/index.js +2 -2
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/animated-leaf-binder.d.ts +1 -7
- package/build/angular/modules/animated/animated-leaf-binder.js +22 -72
- package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +11 -6
- package/build/angular/modules/animated/create-animated-component.js +83 -50
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/app-registry/index.js.map +1 -1
- package/build/angular/modules/status-bar/index.js +3 -3
- package/build/angular/primitives/index.d.ts +1 -1
- package/build/angular/primitives/index.js +46 -46
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +39 -1
- package/build/angular/primitives/shared.js +110 -9
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/render/index.js +23 -35
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/services/color-scheme.service.js +3 -3
- package/build/angular/services/window-dimensions.service.js +3 -3
- package/package.json +13 -8
- package/src/anchor-host-registry.ts +2 -2
- package/src/components/activity-indicator/index.android.ts +2 -0
- package/src/components/activity-indicator/index.ios.ts +2 -0
- package/src/components/button.ts +28 -14
- package/src/components/flat-list/index.ts +34 -36
- package/src/components/image/index.android.ts +50 -6
- package/src/components/image/index.ios.ts +50 -6
- package/src/components/image/shared.ts +34 -2
- package/src/components/image-background.ts +2 -1
- package/src/components/input-accessory-view/index.ts +42 -4
- package/src/components/keyboard-avoiding-view/index.ts +7 -1
- package/src/components/modal/index.ts +51 -26
- package/src/components/pressable/index.ts +67 -8
- package/src/components/refresh-control/index.ts +54 -12
- package/src/components/safe-area-view/index.ts +50 -5
- package/src/components/scroll-view/index.android.ts +34 -41
- package/src/components/scroll-view/index.ios.ts +10 -21
- package/src/components/scroll-view/projection.ts +79 -7
- package/src/components/scroll-view/shared.ts +223 -68
- package/src/components/scroll-view/sticky-header.ts +11 -16
- package/src/components/section-list/index.ts +27 -36
- package/src/components/switch/index.android.ts +48 -7
- package/src/components/switch/index.ios.ts +48 -7
- package/src/components/switch/shared.ts +79 -3
- package/src/components/text-input.ts +52 -2
- package/src/components/touchable/index.ts +76 -16
- package/src/components/touchable-native-feedback/index.ts +62 -21
- package/src/components/virtualized-list/index.ts +137 -44
- package/src/components/virtualized-section-list/index.ts +2 -1
- package/src/components.ts +5 -0
- package/src/create-portal/index.ts +23 -29
- package/src/create-tunnel/index.ts +13 -16
- package/src/index.ts +30 -1
- package/src/modules/animated/animated-leaf-binder.ts +28 -77
- package/src/modules/animated/create-animated-component.ts +75 -36
- package/src/modules/app-registry/index.ts +6 -8
- package/src/primitives/index.ts +1 -0
- package/src/primitives/shared.ts +102 -3
- package/src/render/index.ts +23 -35
- package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +0 -14
- package/build/angular/modules/animated/animated-leaf-binder/index.js +0 -91
- package/build/angular/modules/animated/animated-leaf-binder/index.js.map +0 -1
- package/src/__tests__/responder-change-detection.test.ts +0 -102
- package/src/__tests__/responder-nested-cd.test.ts +0 -113
- package/src/angular-gaps.test.ts +0 -142
- package/src/angular-imports.test.ts +0 -100
- package/src/components/activity-indicator/activity-indicator-class-style.test.ts +0 -44
- package/src/components/activity-indicator/activity-indicator.test.ts +0 -131
- package/src/components/flat-list/column-wrapper-style-class.test.ts +0 -116
- package/src/components/flat-list/flat-list-array-style.test.ts +0 -131
- package/src/components/flat-list/flat-list-class-style.test.ts +0 -74
- package/src/components/flat-list/flat-list-scroll-containment.test.ts +0 -107
- package/src/components/flat-list/flat-list.test.ts +0 -99
- package/src/components/image/image-class-style.test.ts +0 -46
- package/src/components/image-background-class-style.test.ts +0 -48
- package/src/components/image-background-style-class.test.ts +0 -79
- package/src/components/input-accessory-view/input-accessory-view.test.ts +0 -43
- package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +0 -54
- package/src/components/modal/modal.test.ts +0 -43
- package/src/components/pressable/pressable.test.ts +0 -85
- package/src/components/refresh-control/refresh-control.test.ts +0 -41
- package/src/components/safe-area-view/safe-area-view.test.ts +0 -43
- package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +0 -123
- package/src/components/scroll-view/content-container-style-class.test.ts +0 -75
- package/src/components/scroll-view/scroll-view-class-style.test.ts +0 -134
- package/src/components/scroll-view/scroll-view-projection.test.ts +0 -194
- package/src/components/switch/android-switch-class.test.ts +0 -42
- package/src/components/switch/switch.test.ts +0 -92
- package/src/components/text-input-class-style.test.ts +0 -54
- package/src/components/touchable/touchable.test.ts +0 -102
- package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +0 -49
- package/src/components/virtualized-list/virtualized-list-class-style.test.ts +0 -76
- package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +0 -60
- package/src/create-portal/create-portal.test.ts +0 -90
- package/src/create-tunnel/create-tunnel.test.ts +0 -120
- package/src/descriptor-to-angular/descriptor-outlet.test.ts +0 -94
- package/src/host-instance/host-instance.test.ts +0 -49
- package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +0 -116
- package/src/modules/animated/animated-leaf-binder/index.ts +0 -101
- package/src/modules/animated/animated-leaf-binder.test.ts +0 -116
- package/src/modules/animated/animated-scroll-view.test.ts +0 -102
- package/src/modules/app-registry/app-registry.test.ts +0 -106
- package/src/modules/status-bar/status-bar.test.ts +0 -50
- package/src/render/render.test.ts +0 -147
- package/src/renderer/renderer.test.ts +0 -203
- package/src/services/services.test.ts +0 -163
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/angular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "SymbioteNative's Angular adapter — a Renderer2/RendererFactory2 with DOM-less bootstrap driving real native iOS/Android views through the same engine as the other adapters.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,10 @@
|
|
|
42
42
|
"metro-config.cjs",
|
|
43
43
|
"babel-linker.cjs",
|
|
44
44
|
"babel-register-composed.cjs",
|
|
45
|
-
"tsconfig.angular.base.json"
|
|
45
|
+
"tsconfig.angular.base.json",
|
|
46
|
+
"!src/**/*.test.*",
|
|
47
|
+
"!src/**/*.spec.*",
|
|
48
|
+
"!src/**/*.detox.*"
|
|
46
49
|
],
|
|
47
50
|
"bin": {
|
|
48
51
|
"symbiote-angular-dev": "./bin/symbiote-angular-dev.cjs"
|
|
@@ -51,24 +54,26 @@
|
|
|
51
54
|
"access": "public"
|
|
52
55
|
},
|
|
53
56
|
"dependencies": {
|
|
54
|
-
"@angular/compiler-cli": "
|
|
57
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
55
58
|
"@react-native/typescript-config": "0.86.0",
|
|
56
|
-
"@symbiote-native/components": "0.
|
|
57
|
-
"@symbiote-native/css-parser": "0.
|
|
59
|
+
"@symbiote-native/components": "0.4.0",
|
|
60
|
+
"@symbiote-native/css-parser": "0.3.0"
|
|
58
61
|
},
|
|
59
62
|
"peerDependencies": {
|
|
60
63
|
"@angular/core": ">=20",
|
|
64
|
+
"@angular/forms": ">=20",
|
|
61
65
|
"@symbiote-native/engine": ">=0.1.7",
|
|
62
66
|
"react-native": ">=0.86"
|
|
63
67
|
},
|
|
64
68
|
"devDependencies": {
|
|
65
|
-
"@angular/compiler": "
|
|
66
|
-
"@angular/core": "
|
|
69
|
+
"@angular/compiler": "~22.0.8",
|
|
70
|
+
"@angular/core": "~22.0.8",
|
|
71
|
+
"@angular/forms": "~22.0.8",
|
|
67
72
|
"@babel/core": "^7.25.2",
|
|
68
73
|
"@types/babel__core": "^7.20.5",
|
|
69
74
|
"@types/node": "^26.0.0",
|
|
70
75
|
"typescript": "~6.0.0",
|
|
71
|
-
"@symbiote-native/engine": "0.
|
|
76
|
+
"@symbiote-native/engine": "0.2.0",
|
|
72
77
|
"@symbiote-native/test-utils": "0.1.6"
|
|
73
78
|
},
|
|
74
79
|
"scripts": {
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
// Housing the registry here rather than inline in `renderer/index.ts` keeps it out of the require
|
|
12
12
|
// cycle (cheap insurance against a load-order strand). The bug that actually surfaced this,
|
|
13
13
|
// though, was STALE BUILD ARTIFACTS, not the cycle: `ngc -p` never deletes orphaned outputs, so
|
|
14
|
-
// after the renderer moved `renderer.ts`
|
|
14
|
+
// after the renderer moved `renderer.ts` -> `renderer/index.ts` the orphaned `build/angular/
|
|
15
15
|
// renderer.js` lingered and SHADOWED `build/angular/renderer/index.js` (a file beats a folder in
|
|
16
16
|
// Node/Metro resolution), giving the bundle a second, stale registry Set — fixed by cleaning
|
|
17
17
|
// `build/` before every ngc build (see each Angular package's `clean` script). Full record:
|
|
18
|
-
// angular-adapter §11c
|
|
18
|
+
// angular-adapter §11c.
|
|
19
19
|
|
|
20
20
|
// Lowercased at construction — isAnchorHostComponent and registerComposedComponent both normalize
|
|
21
21
|
// to lowercase (Angular lowercases a dynamically-mounted component's selector at runtime, see
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
2
|
import { DescriptorOutlet } from '../../descriptor-to-angular';
|
|
3
3
|
import { ActivityIndicatorBase } from './shared';
|
|
4
|
+
import { SymbioteStyleInputDirective } from '../../primitives';
|
|
4
5
|
export type { IActivityIndicatorProps } from './shared';
|
|
5
6
|
|
|
6
7
|
@Component({
|
|
7
8
|
selector: 'ActivityIndicator',
|
|
8
9
|
standalone: true,
|
|
10
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
9
11
|
imports: [DescriptorOutlet],
|
|
10
12
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11
13
|
template: `<symbiote-descriptor-outlet [node]="descriptor" />`,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
2
|
import { DescriptorOutlet } from '../../descriptor-to-angular';
|
|
3
3
|
import { ActivityIndicatorBase } from './shared';
|
|
4
|
+
import { SymbioteStyleInputDirective } from '../../primitives';
|
|
4
5
|
export type { IActivityIndicatorProps } from './shared';
|
|
5
6
|
|
|
6
7
|
const IOS_DEFAULT_COLOR = '#999999';
|
|
@@ -8,6 +9,7 @@ const IOS_DEFAULT_COLOR = '#999999';
|
|
|
8
9
|
@Component({
|
|
9
10
|
selector: 'ActivityIndicator',
|
|
10
11
|
standalone: true,
|
|
12
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
11
13
|
imports: [DescriptorOutlet],
|
|
12
14
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
13
15
|
template: `<symbiote-descriptor-outlet [node]="descriptor" />`,
|
package/src/components/button.ts
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
// Button for Angular. The minimal cross-platform button in RN's iOS shape (Button.js): a
|
|
2
2
|
// TouchableOpacity wrapping a Text. The base text style, the role constant, and the color fold are
|
|
3
|
-
// shared in @symbiote-native/components/view;
|
|
4
|
-
// symbiote-text child and re-maps the few Button-owned props.
|
|
5
|
-
//
|
|
3
|
+
// shared in @symbiote-native/components/view; Angular only composes its TouchableOpacity + a
|
|
4
|
+
// symbiote-text child and re-maps the few Button-owned props. No JS-side platform branch, so this
|
|
5
|
+
// stays a flat single file.
|
|
6
6
|
//
|
|
7
|
-
// RN's Button fixes accessibilityRole="button", marks the root accessible, and propagates
|
|
8
|
-
// into the accessibility state; those three win over any caller value. `title` becomes
|
|
9
|
-
// child; disabled forwards straight; touchSoundDisabled maps to the pressable's
|
|
10
|
-
// Every
|
|
11
|
-
//
|
|
12
|
-
// verbatim. onPress and the four accessibility callbacks are the
|
|
7
|
+
// RN's Button fixes accessibilityRole="button", marks the root accessible, and propagates
|
|
8
|
+
// `disabled` into the accessibility state; those three win over any caller value. `title` becomes
|
|
9
|
+
// the Text child; disabled forwards straight; touchSoundDisabled maps to the pressable's
|
|
10
|
+
// android_disableSound. Every other field of the shared IButtonProps stays RE-EXPORTED from
|
|
11
|
+
// @symbiote-native/components verbatim. onPress and the four accessibility callbacks are the
|
|
13
12
|
// exceptions: each is a real @Output() here (`press`, `accessibilityAction`, `accessibilityTap`,
|
|
14
|
-
// `magicTap`, `accessibilityEscape`), mirroring the Vue adapter's Button, which forks the
|
|
15
|
-
// field the same way
|
|
16
|
-
// existing one extended to accessibility.
|
|
13
|
+
// `magicTap`, `accessibilityEscape`), mirroring the Vue adapter's Button, which forks the same
|
|
14
|
+
// onPress field the same way — not a new precedent, the existing one extended to accessibility.
|
|
17
15
|
|
|
18
16
|
import {
|
|
19
17
|
CUSTOM_ELEMENTS_SCHEMA,
|
|
@@ -24,6 +22,8 @@ import {
|
|
|
24
22
|
inject,
|
|
25
23
|
Input,
|
|
26
24
|
Output,
|
|
25
|
+
signal,
|
|
26
|
+
type DoCheck,
|
|
27
27
|
} from '@angular/core';
|
|
28
28
|
import {
|
|
29
29
|
BUTTON_ACCESSIBILITY_ROLE,
|
|
@@ -108,7 +108,7 @@ export type IButtonProps = Omit<
|
|
|
108
108
|
</TouchableOpacity>
|
|
109
109
|
`,
|
|
110
110
|
})
|
|
111
|
-
export class Button implements IButtonProps {
|
|
111
|
+
export class Button implements IButtonProps, DoCheck {
|
|
112
112
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
113
113
|
// onto (see anchorHostStyle's doc comment in primitives/shared.ts) — NOT the inner
|
|
114
114
|
// TouchableOpacity/Pressable one level down. RN's real Button takes no `style` prop, so this
|
|
@@ -187,7 +187,21 @@ export class Button implements IButtonProps {
|
|
|
187
187
|
return resolveButtonTextStyle(this.color, this.disabled);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// Polled, not read live: a class toggled after mount reaches the anchor through
|
|
191
|
+
// addClass/removeClass, which dirties nothing, so a plain getter froze at its creation value.
|
|
192
|
+
// Button cannot use SymbioteStyleInputDirective instead - RN's Button has no `style` prop, so
|
|
193
|
+
// there is no `style` @Input for it to hang off.
|
|
194
|
+
//
|
|
195
|
+
// ngDoCheck runs during the PARENT's refresh even when this view is skipped; the signal write is
|
|
196
|
+
// what marks this view. The anchor returns the same props.style reference until a commit changes
|
|
197
|
+
// it, so Object.is makes an unchanged poll free.
|
|
198
|
+
private readonly anchorStyleValue = signal<IStyleProp<IViewStyle> | undefined>(undefined);
|
|
199
|
+
|
|
200
|
+
ngDoCheck(): void {
|
|
201
|
+
this.anchorStyleValue.set(anchorStyleProp<IViewStyle>(this.elementRef));
|
|
202
|
+
}
|
|
203
|
+
|
|
190
204
|
get anchorStyle(): IStyleProp<IViewStyle> | undefined {
|
|
191
|
-
return
|
|
205
|
+
return this.anchorStyleValue();
|
|
192
206
|
}
|
|
193
207
|
}
|
|
@@ -1,37 +1,35 @@
|
|
|
1
1
|
// FlatList, the Angular convenience surface over VirtualizedList. It takes a plain `data` array
|
|
2
2
|
// and derives getItem/getItemCount; numColumns packs items into rows (each row a flex-row of N
|
|
3
|
-
// columns), so the virtualized stream is rows, not items (RN's FlatList).
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// verbatim from @symbiote-native/components,
|
|
7
|
-
// Angular twin of adapters/vue/src/components/flat-list/index.ts.
|
|
3
|
+
// columns), so the virtualized stream is rows, not items (RN's FlatList). Windowing / viewability /
|
|
4
|
+
// batching / imperative scrolling are inherited from VirtualizedList; the data shaping
|
|
5
|
+
// (chunkIntoRows / rowKeyExtractor / expandRowViewability / row-separator unwrap) is shared
|
|
6
|
+
// verbatim from @symbiote-native/components, same as React/Vue's FlatLists.
|
|
8
7
|
//
|
|
9
|
-
// TEMPLATE FORWARDING — both paths RE-STAMP; neither uses a bare `<ng-content>` passthrough.
|
|
8
|
+
// TEMPLATE FORWARDING — both column paths RE-STAMP; neither uses a bare `<ng-content>` passthrough.
|
|
10
9
|
// Angular's @ContentChild does NOT resolve a directive across a SECOND `<ng-content>` re-projection
|
|
11
|
-
// hop (
|
|
12
|
-
// `<VirtualizedList ...><ng-content></ng-content></VirtualizedList>`
|
|
13
|
-
// itemDir/headerDir/etc. undefined,
|
|
14
|
-
// flat-list.test.ts). Fixed for both column modes
|
|
10
|
+
// hop (only what's projected directly onto the querying component's own tag) — a bare
|
|
11
|
+
// `<VirtualizedList ...><ng-content></ng-content></VirtualizedList>` left VirtualizedList's own
|
|
12
|
+
// itemDir/headerDir/etc. undefined, rendering every cell empty (confirmed device bug, see
|
|
13
|
+
// flat-list.test.ts). Fixed for both column modes:
|
|
15
14
|
// * Single column (numColumns <= 1): FlatList captures the app's `<ng-template vListItem>` (and
|
|
16
15
|
// vListHeader/vListFooter/vListEmpty/vListSeparator) with its OWN @ContentChild — a single,
|
|
17
16
|
// direct projection hop, which always resolves — then re-authors equivalent `<ng-template>`s on
|
|
18
|
-
// `<VirtualizedList>`,
|
|
17
|
+
// `<VirtualizedList>`, forwarding the captured templateRef + context through
|
|
19
18
|
// VListOutletDirective. Item/index/separators pass through 1:1 (no row wrapping).
|
|
20
|
-
// * Multi column (numColumns > 1):
|
|
21
|
-
// while the virtualized stream is rows (IRow<ItemT>), so a plain passthrough couldn't work
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// renderItem with renderRow.
|
|
19
|
+
// * Multi column (numColumns > 1): same re-stamp, but the app's vListItem is typed for ItemT
|
|
20
|
+
// while the virtualized stream is rows (IRow<ItemT>), so a plain passthrough couldn't work
|
|
21
|
+
// regardless. The row vListItem lays out the N columns side by side (each cell stamps the
|
|
22
|
+
// app's item template via VListOutletDirective with context {item, index: row.startIndex +
|
|
23
|
+
// column, separators}), the row vListSeparator unwraps the flanking rows to their last/first
|
|
24
|
+
// item, and onViewableItemsChanged is wrapped with expandRowViewability so the caller still
|
|
25
|
+
// sees per-item visibility — mirrors how Vue's FlatList intercepts renderItem with renderRow.
|
|
28
26
|
//
|
|
29
27
|
// The imperative handle (scrollToIndex / scrollToOffset / scrollToEnd / scrollToItem /
|
|
30
28
|
// recordInteraction / flashScrollIndicators / getScroll* / getNativeScrollRef) is RN's FlatList
|
|
31
|
-
// surface
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
29
|
+
// surface, delegated straight to the inner VirtualizedList (@ViewChild). The element-returning
|
|
30
|
+
// props (renderItem / ItemSeparatorComponent / List{Header,Footer,Empty}Component) are templates
|
|
31
|
+
// in Angular, so they're absent from IFlatListProps per the per-adapter children/render split;
|
|
32
|
+
// everything else mirrors IVirtualizedListProps.
|
|
35
33
|
|
|
36
34
|
import {
|
|
37
35
|
CUSTOM_ELEMENTS_SCHEMA,
|
|
@@ -44,6 +42,7 @@ import {
|
|
|
44
42
|
Output,
|
|
45
43
|
ViewChild,
|
|
46
44
|
inject,
|
|
45
|
+
type DoCheck,
|
|
47
46
|
type OnChanges,
|
|
48
47
|
type SimpleChanges,
|
|
49
48
|
} from '@angular/core';
|
|
@@ -81,7 +80,7 @@ import {
|
|
|
81
80
|
type IVListSeparatorContext,
|
|
82
81
|
} from '../virtualized-list';
|
|
83
82
|
import { VListOutletDirective } from '../virtualized-list/directives';
|
|
84
|
-
import { stableAnchorStyle, ViewHost } from '../../primitives';
|
|
83
|
+
import { stableAnchorStyle, SymbioteStyleInputDirective, ViewHost } from '../../primitives';
|
|
85
84
|
|
|
86
85
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
87
86
|
return typeof value === 'object' && value !== null;
|
|
@@ -165,6 +164,7 @@ export type IFlatListInputs<ItemT> = Omit<
|
|
|
165
164
|
@Component({
|
|
166
165
|
selector: 'FlatList',
|
|
167
166
|
standalone: true,
|
|
167
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
168
168
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
169
169
|
imports: [
|
|
170
170
|
VirtualizedList,
|
|
@@ -338,7 +338,7 @@ export type IFlatListInputs<ItemT> = Omit<
|
|
|
338
338
|
`,
|
|
339
339
|
})
|
|
340
340
|
export class FlatList<ItemT = unknown>
|
|
341
|
-
implements IFlatListInputs<ItemT>, IVirtualizedListHandle, OnChanges
|
|
341
|
+
implements IFlatListInputs<ItemT>, IVirtualizedListHandle, DoCheck, OnChanges
|
|
342
342
|
{
|
|
343
343
|
// The list's edge/viewability/failure events as real Angular events: `(endReached)="…"`, not
|
|
344
344
|
// `[onEndReached]="…"` — re-emitted straight from the inner VirtualizedList's own @Output()s (see
|
|
@@ -427,18 +427,16 @@ export class FlatList<ItemT = unknown>
|
|
|
427
427
|
return this.columns > SINGLE_COLUMN;
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
-
ngOnChanges
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// inline from Renderer2.addClass, before this component's own lifecycle hooks). stableAnchorStyle
|
|
437
|
-
// (not a bare flattenStyle) keeps `resolvedStyle`'s REFERENCE stable across ticks where nothing
|
|
438
|
-
// actually changed — `[style]="resolvedStyle"` binds onto VirtualizedList's own `@Input() style`,
|
|
439
|
-
// which feeds its `ngDoCheck` dedup gate; a fresh object every tick would defeat that gate and
|
|
440
|
-
// free-run change detection forever (see stableAnchorStyle's doc comment).
|
|
430
|
+
// ngDoCheck, NOT ngOnChanges: a bare `class=` never becomes an @Input, so ngOnChanges does not
|
|
431
|
+
// RUN for it and the merge froze at its creation value (a stranded tile on the ReactiveStyle
|
|
432
|
+
// canary). stableAnchorStyle, not a bare flattenStyle, keeps `resolvedStyle`'s reference stable
|
|
433
|
+
// across ticks that changed nothing - a fresh object every tick defeats VirtualizedList's own
|
|
434
|
+
// dedup gate and free-runs CD (see stableAnchorStyle's doc comment).
|
|
435
|
+
ngDoCheck(): void {
|
|
441
436
|
this.resolvedStyle = stableAnchorStyle(this.elementRef, this.style, this.resolvedStyle);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
442
440
|
if (changes['columnWrapperStyle'] !== undefined) {
|
|
443
441
|
// A class-name string resolves through the shared registry before flattenStyle, which
|
|
444
442
|
// only understands style objects/arrays.
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
CUSTOM_ELEMENTS_SCHEMA,
|
|
3
|
+
Component,
|
|
4
|
+
ElementRef,
|
|
5
|
+
inject,
|
|
6
|
+
signal,
|
|
7
|
+
type DoCheck,
|
|
8
|
+
type OnChanges,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import {
|
|
11
|
+
anchorHostStyle,
|
|
12
|
+
ImageHost,
|
|
13
|
+
SymbioteHostPropsDirective,
|
|
14
|
+
SymbioteStyleInputDirective,
|
|
15
|
+
} from '../../primitives';
|
|
3
16
|
import { IMAGE_INPUTS, IMAGE_OUTPUTS, ImageBase, resolveImageProps } from './shared';
|
|
4
17
|
export { setImageSourceResolver } from './shared';
|
|
5
18
|
export type {
|
|
@@ -14,13 +27,14 @@ export type {
|
|
|
14
27
|
@Component({
|
|
15
28
|
selector: 'Image',
|
|
16
29
|
standalone: true,
|
|
30
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
17
31
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
18
32
|
imports: [ImageHost, SymbioteHostPropsDirective],
|
|
19
33
|
inputs: IMAGE_INPUTS,
|
|
20
34
|
outputs: IMAGE_OUTPUTS,
|
|
21
35
|
template: `
|
|
22
36
|
<symbiote-image
|
|
23
|
-
[symbioteHostProps]="imageProps"
|
|
37
|
+
[symbioteHostProps]="imageProps()"
|
|
24
38
|
(accessibilityAction)="handleAccessibilityAction($event)"
|
|
25
39
|
(accessibilityTap)="handleAccessibilityTap($event)"
|
|
26
40
|
(magicTap)="handleMagicTap($event)"
|
|
@@ -34,15 +48,45 @@ export type {
|
|
|
34
48
|
/>
|
|
35
49
|
`,
|
|
36
50
|
})
|
|
37
|
-
export class Image extends ImageBase {
|
|
51
|
+
export class Image extends ImageBase implements OnChanges, DoCheck {
|
|
38
52
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
39
53
|
// onto (see anchorHostStyle's doc comment) — NOT the inner `symbiote-image` one level down.
|
|
40
54
|
private readonly elementRef = inject(ElementRef);
|
|
41
55
|
|
|
42
|
-
|
|
56
|
+
// The anchor's class-derived style is written by the renderer's addClass/removeClass and never
|
|
57
|
+
// reaches Angular's input pipeline (a bare `class="x"` never appears in SimpleChanges — see
|
|
58
|
+
// anchorHostStyle's doc comment), so ImageBase's inputsRevision cannot see it. Hence its own
|
|
59
|
+
// signal: a plain field read inside a computed() would be untracked and the bag would go stale
|
|
60
|
+
// the moment a `[class.x]`/`[ngClass]` toggled.
|
|
61
|
+
//
|
|
62
|
+
// ngDoCheck is the right cadence: render3's callHooks flushes a node's pre-order hooks at the
|
|
63
|
+
// next `ɵɵadvance` past it (or in refreshView's post-template flush), i.e. AFTER the declaring
|
|
64
|
+
// template's ɵɵclassProp wrote the class onto this element and BEFORE this component's own view
|
|
65
|
+
// refreshes — the exact window a class poll needs. callHooks also runs under
|
|
66
|
+
// setActiveConsumer(null), so the write registers no dependency on the caller's view (no NG0600).
|
|
67
|
+
// commitClassStyle allocates a fresh style array only when a token actually moved, so signal.set's
|
|
68
|
+
// own Object.is check makes an unchanged poll a no-op that dirties nothing.
|
|
69
|
+
//
|
|
70
|
+
// Known gap, shared with every other component polling this way (Pressable, SafeAreaView,
|
|
71
|
+
// TouchableNativeFeedback): `[ngClass]` applies its tokens from NgClass's OWN ngDoCheck, and a
|
|
72
|
+
// node's hooks flush in registration order with the component's first, so an [ngClass] flip lands
|
|
73
|
+
// one pass after this poll reads it. `class=` and `[class.x]` are written by the declaring
|
|
74
|
+
// template's styling instructions and so are always current here.
|
|
75
|
+
private readonly anchorStyle = signal<unknown>(undefined);
|
|
76
|
+
|
|
77
|
+
// ImageBase is undecorated, so the input bump is declared here rather than inherited.
|
|
78
|
+
ngOnChanges(): void {
|
|
79
|
+
this.bumpInputsRevision();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ngDoCheck(): void {
|
|
83
|
+
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected override buildImageProps(): Record<string, unknown> {
|
|
43
87
|
return resolveImageProps({
|
|
44
88
|
...this.imageInputProps,
|
|
45
|
-
style: [
|
|
89
|
+
style: [this.anchorStyle(), this.style],
|
|
46
90
|
});
|
|
47
91
|
}
|
|
48
92
|
}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
CUSTOM_ELEMENTS_SCHEMA,
|
|
3
|
+
Component,
|
|
4
|
+
ElementRef,
|
|
5
|
+
inject,
|
|
6
|
+
signal,
|
|
7
|
+
type DoCheck,
|
|
8
|
+
type OnChanges,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import {
|
|
11
|
+
anchorHostStyle,
|
|
12
|
+
ImageHost,
|
|
13
|
+
SymbioteHostPropsDirective,
|
|
14
|
+
SymbioteStyleInputDirective,
|
|
15
|
+
} from '../../primitives';
|
|
3
16
|
import { IMAGE_INPUTS, IMAGE_OUTPUTS, ImageBase, resolveImageProps } from './shared';
|
|
4
17
|
export { setImageSourceResolver } from './shared';
|
|
5
18
|
export type {
|
|
@@ -14,13 +27,14 @@ export type {
|
|
|
14
27
|
@Component({
|
|
15
28
|
selector: 'Image',
|
|
16
29
|
standalone: true,
|
|
30
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
17
31
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
18
32
|
imports: [ImageHost, SymbioteHostPropsDirective],
|
|
19
33
|
inputs: IMAGE_INPUTS,
|
|
20
34
|
outputs: IMAGE_OUTPUTS,
|
|
21
35
|
template: `
|
|
22
36
|
<symbiote-image
|
|
23
|
-
[symbioteHostProps]="imageProps"
|
|
37
|
+
[symbioteHostProps]="imageProps()"
|
|
24
38
|
(accessibilityAction)="handleAccessibilityAction($event)"
|
|
25
39
|
(accessibilityTap)="handleAccessibilityTap($event)"
|
|
26
40
|
(magicTap)="handleMagicTap($event)"
|
|
@@ -34,15 +48,45 @@ export type {
|
|
|
34
48
|
/>
|
|
35
49
|
`,
|
|
36
50
|
})
|
|
37
|
-
export class Image extends ImageBase {
|
|
51
|
+
export class Image extends ImageBase implements OnChanges, DoCheck {
|
|
38
52
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
39
53
|
// onto (see anchorHostStyle's doc comment) — NOT the inner `symbiote-image` one level down.
|
|
40
54
|
private readonly elementRef = inject(ElementRef);
|
|
41
55
|
|
|
42
|
-
|
|
56
|
+
// The anchor's class-derived style is written by the renderer's addClass/removeClass and never
|
|
57
|
+
// reaches Angular's input pipeline (a bare `class="x"` never appears in SimpleChanges — see
|
|
58
|
+
// anchorHostStyle's doc comment), so ImageBase's inputsRevision cannot see it. Hence its own
|
|
59
|
+
// signal: a plain field read inside a computed() would be untracked and the bag would go stale
|
|
60
|
+
// the moment a `[class.x]`/`[ngClass]` toggled.
|
|
61
|
+
//
|
|
62
|
+
// ngDoCheck is the right cadence: render3's callHooks flushes a node's pre-order hooks at the
|
|
63
|
+
// next `ɵɵadvance` past it (or in refreshView's post-template flush), i.e. AFTER the declaring
|
|
64
|
+
// template's ɵɵclassProp wrote the class onto this element and BEFORE this component's own view
|
|
65
|
+
// refreshes — the exact window a class poll needs. callHooks also runs under
|
|
66
|
+
// setActiveConsumer(null), so the write registers no dependency on the caller's view (no NG0600).
|
|
67
|
+
// commitClassStyle allocates a fresh style array only when a token actually moved, so signal.set's
|
|
68
|
+
// own Object.is check makes an unchanged poll a no-op that dirties nothing.
|
|
69
|
+
//
|
|
70
|
+
// Known gap, shared with every other component polling this way (Pressable, SafeAreaView,
|
|
71
|
+
// TouchableNativeFeedback): `[ngClass]` applies its tokens from NgClass's OWN ngDoCheck, and a
|
|
72
|
+
// node's hooks flush in registration order with the component's first, so an [ngClass] flip lands
|
|
73
|
+
// one pass after this poll reads it. `class=` and `[class.x]` are written by the declaring
|
|
74
|
+
// template's styling instructions and so are always current here.
|
|
75
|
+
private readonly anchorStyle = signal<unknown>(undefined);
|
|
76
|
+
|
|
77
|
+
// ImageBase is undecorated, so the input bump is declared here rather than inherited.
|
|
78
|
+
ngOnChanges(): void {
|
|
79
|
+
this.bumpInputsRevision();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ngDoCheck(): void {
|
|
83
|
+
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected override buildImageProps(): Record<string, unknown> {
|
|
43
87
|
return resolveImageProps({
|
|
44
88
|
...this.imageInputProps,
|
|
45
|
-
style: [
|
|
89
|
+
style: [this.anchorStyle(), this.style],
|
|
46
90
|
});
|
|
47
91
|
}
|
|
48
92
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventEmitter } from '@angular/core';
|
|
1
|
+
import { EventEmitter, computed, signal } from '@angular/core';
|
|
2
2
|
import {
|
|
3
3
|
imageStatics,
|
|
4
4
|
renderImage,
|
|
@@ -473,7 +473,39 @@ export abstract class ImageBase {
|
|
|
473
473
|
};
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
-
|
|
476
|
+
// Bridges the non-reactive @Input fields into the reactive graph so imageProps below can be a
|
|
477
|
+
// memoized computed(). Signal inputs would make this unnecessary, but `input()` is visible only
|
|
478
|
+
// to the AOT compiler and this package's unit suite runs on JIT - see the
|
|
479
|
+
// `angular-adapter-change-detection` skill, §6. `signal()`/`computed()` are plain runtime APIs
|
|
480
|
+
// and need no compiler support, which is what makes this bridge possible at all.
|
|
481
|
+
//
|
|
482
|
+
// ImageBase is deliberately UNdecorated, so the ngOnChanges that bumps this lives on each
|
|
483
|
+
// platform @Component instead of here.
|
|
484
|
+
//
|
|
485
|
+
// Every dependency of the bag below is an @Input, which Angular always routes through
|
|
486
|
+
// ngOnChanges. State assigned OUTSIDE that path needs its OWN signal - never widen this one to
|
|
487
|
+
// "bump on everything", which silently un-memoizes the bag. The platform anchor style is exactly
|
|
488
|
+
// such a dependency and carries its own signal.
|
|
489
|
+
protected readonly inputsRevision = signal(0);
|
|
490
|
+
|
|
491
|
+
protected bumpInputsRevision(): void {
|
|
492
|
+
this.inputsRevision.update(revision => revision + 1);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// MEASURED 2026-08-16: as a getter this rebuilt the bag on every refresh of this view, so
|
|
496
|
+
// `[symbioteHostProps]`'s reference check failed every time and every key was re-pushed through
|
|
497
|
+
// Renderer2 -> routeProp - pure waste when nothing changed. computed() returns the SAME object
|
|
498
|
+
// until a tracked dependency actually changes, and the input setter then skips the whole spread.
|
|
499
|
+
//
|
|
500
|
+
// A computed FIELD cannot be overridden by a subclass accessor the way a getter can, so the
|
|
501
|
+
// platform components override buildImageProps() below and this stays the single memoization
|
|
502
|
+
// point for every platform.
|
|
503
|
+
readonly imageProps = computed<Record<string, unknown>>(() => {
|
|
504
|
+
this.inputsRevision();
|
|
505
|
+
return this.buildImageProps();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
protected buildImageProps(): Record<string, unknown> {
|
|
477
509
|
return resolveImageProps(this.imageInputProps);
|
|
478
510
|
}
|
|
479
511
|
}
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
type IViewStyle,
|
|
38
38
|
} from '@symbiote-native/engine';
|
|
39
39
|
import { Image } from './image';
|
|
40
|
-
import { anchorHostStyle, ViewHost } from '../primitives';
|
|
40
|
+
import { anchorHostStyle, SymbioteStyleInputDirective, ViewHost } from '../primitives';
|
|
41
41
|
|
|
42
42
|
// Mirrors React's IImageBackgroundProps minus children (Angular takes children via <ng-content>):
|
|
43
43
|
// every forwarding Image prop flows onto the inner image; `style` is the WRAPPER View style and
|
|
@@ -68,6 +68,7 @@ export type IAngularImageBackgroundInputs = Omit<
|
|
|
68
68
|
@Component({
|
|
69
69
|
selector: 'ImageBackground',
|
|
70
70
|
standalone: true,
|
|
71
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
71
72
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
72
73
|
imports: [Image, ViewHost],
|
|
73
74
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -11,11 +11,15 @@ import {
|
|
|
11
11
|
CUSTOM_ELEMENTS_SCHEMA,
|
|
12
12
|
ChangeDetectionStrategy,
|
|
13
13
|
Component,
|
|
14
|
+
computed,
|
|
14
15
|
ElementRef,
|
|
15
16
|
EventEmitter,
|
|
16
17
|
inject,
|
|
17
18
|
Input,
|
|
18
19
|
Output,
|
|
20
|
+
signal,
|
|
21
|
+
type DoCheck,
|
|
22
|
+
type OnChanges,
|
|
19
23
|
} from '@angular/core';
|
|
20
24
|
import {
|
|
21
25
|
renderInputAccessoryView,
|
|
@@ -34,6 +38,7 @@ import {
|
|
|
34
38
|
anchorHostStyle,
|
|
35
39
|
InputAccessoryViewHost,
|
|
36
40
|
SymbioteHostPropsDirective,
|
|
41
|
+
SymbioteStyleInputDirective,
|
|
37
42
|
} from '../../primitives';
|
|
38
43
|
|
|
39
44
|
// Mirrors React's IInputAccessoryViewProps minus children (Angular takes children via
|
|
@@ -56,12 +61,13 @@ export type IAngularInputAccessoryViewInputs = Omit<
|
|
|
56
61
|
@Component({
|
|
57
62
|
selector: 'InputAccessoryView',
|
|
58
63
|
standalone: true,
|
|
64
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
59
65
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
60
66
|
imports: [InputAccessoryViewHost, SymbioteHostPropsDirective],
|
|
61
67
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
62
68
|
template: `
|
|
63
69
|
<symbiote-input-accessory-view
|
|
64
|
-
[symbioteHostProps]="hostProps"
|
|
70
|
+
[symbioteHostProps]="hostProps()"
|
|
65
71
|
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
66
72
|
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
67
73
|
(magicTap)="emit(magicTap, $event)"
|
|
@@ -71,7 +77,7 @@ export type IAngularInputAccessoryViewInputs = Omit<
|
|
|
71
77
|
</symbiote-input-accessory-view>
|
|
72
78
|
`,
|
|
73
79
|
})
|
|
74
|
-
export class InputAccessoryView implements IAngularInputAccessoryViewInputs {
|
|
80
|
+
export class InputAccessoryView implements IAngularInputAccessoryViewInputs, OnChanges, DoCheck {
|
|
75
81
|
@Input() nativeID?: string;
|
|
76
82
|
@Input() backgroundColor?: string;
|
|
77
83
|
@Input() style?: IStyleProp<IViewStyle>;
|
|
@@ -135,12 +141,44 @@ export class InputAccessoryView implements IAngularInputAccessoryViewInputs {
|
|
|
135
141
|
});
|
|
136
142
|
}
|
|
137
143
|
|
|
144
|
+
// Bridges the non-reactive fields `hostProps` reads into the reactive graph, so it can memoize.
|
|
145
|
+
// Plain fields read inside a computed() are UNTRACKED - something must signal "a dependency
|
|
146
|
+
// changed" or the bag goes stale. Signal inputs would do this natively, but `input()` is visible
|
|
147
|
+
// only to the AOT compiler and this package's unit suite runs on JIT (see the
|
|
148
|
+
// `angular-adapter-change-detection` skill, §6); `signal()`/`computed()` are plain runtime APIs.
|
|
149
|
+
private readonly hostPropsRevision = signal(0);
|
|
150
|
+
// What the anchor's class-derived style was when the bag was last built (identity, not value).
|
|
151
|
+
private lastAnchorStyle: unknown;
|
|
152
|
+
|
|
153
|
+
ngOnChanges(): void {
|
|
154
|
+
// The single moment Angular has finished writing every changed @Input for this pass.
|
|
155
|
+
this.hostPropsRevision.update(revision => revision + 1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// The anchor's class-derived style is NOT an @Input: `class="..."`/`[ngClass]` at the use site
|
|
159
|
+
// resolves through the renderer's addClass onto this component's anchor host (see
|
|
160
|
+
// anchorHostStyle's doc comment), so it never shows up in SimpleChanges and ngOnChanges alone
|
|
161
|
+
// would leave a later class toggle stranded. ngDoCheck runs at exactly the cadence the old
|
|
162
|
+
// getter was re-read, and bumps only on a real identity change.
|
|
163
|
+
ngDoCheck(): void {
|
|
164
|
+
const anchorStyle = anchorHostStyle(this.elementRef);
|
|
165
|
+
if (anchorStyle === this.lastAnchorStyle) return;
|
|
166
|
+
this.lastAnchorStyle = anchorStyle;
|
|
167
|
+
this.hostPropsRevision.update(revision => revision + 1);
|
|
168
|
+
}
|
|
169
|
+
|
|
138
170
|
// The anchor's class-derived style goes FIRST, the Descriptor's resolved style SECOND —
|
|
139
171
|
// flattenStyle's later-wins collapse keeps an explicit [style] winning over its ambient class.
|
|
140
|
-
|
|
172
|
+
//
|
|
173
|
+
// A computed(), not a getter: Angular re-reads a template getter on every refresh of this view
|
|
174
|
+
// and `[symbioteHostProps]` compares by REFERENCE, so a freshly rebuilt (but identical) bag
|
|
175
|
+
// re-pushes every key through renderer.setProperty -> the engine's prop routing - and this one
|
|
176
|
+
// re-runs renderInputAccessoryView to build it.
|
|
177
|
+
readonly hostProps = computed<Record<string, unknown>>(() => {
|
|
178
|
+
this.hostPropsRevision();
|
|
141
179
|
const descriptorProps = this.descriptor.props;
|
|
142
180
|
return { ...descriptorProps, style: [anchorHostStyle(this.elementRef), descriptorProps.style] };
|
|
143
|
-
}
|
|
181
|
+
});
|
|
144
182
|
|
|
145
183
|
// Typed as the a11y intersection WITH the string index (the bag renderInputAccessoryView spreads
|
|
146
184
|
// into the host props), so resolveAccessibilityProps's result stays assignable to passthrough.
|
|
@@ -48,7 +48,12 @@ import {
|
|
|
48
48
|
type ISymbioteEvent,
|
|
49
49
|
type IViewStyle,
|
|
50
50
|
} from '@symbiote-native/engine';
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
anchorHostStyle,
|
|
53
|
+
SymbioteHostPropsDirective,
|
|
54
|
+
SymbioteStyleInputDirective,
|
|
55
|
+
ViewHost,
|
|
56
|
+
} from '../../primitives';
|
|
52
57
|
|
|
53
58
|
export type { IKeyboardAvoidingBehavior } from '@symbiote-native/components';
|
|
54
59
|
|
|
@@ -79,6 +84,7 @@ export type IAngularKeyboardAvoidingViewInputs = Omit<
|
|
|
79
84
|
@Component({
|
|
80
85
|
selector: 'KeyboardAvoidingView',
|
|
81
86
|
standalone: true,
|
|
87
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
82
88
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
83
89
|
imports: [ViewHost, SymbioteHostPropsDirective],
|
|
84
90
|
changeDetection: ChangeDetectionStrategy.OnPush,
|