@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
|
@@ -13,11 +13,14 @@ import {
|
|
|
13
13
|
ChangeDetectionStrategy,
|
|
14
14
|
ChangeDetectorRef,
|
|
15
15
|
Component,
|
|
16
|
+
computed,
|
|
16
17
|
ElementRef,
|
|
17
18
|
EventEmitter,
|
|
18
19
|
inject,
|
|
19
20
|
Input,
|
|
20
21
|
Output,
|
|
22
|
+
signal,
|
|
23
|
+
type DoCheck,
|
|
21
24
|
type OnChanges,
|
|
22
25
|
type OnInit,
|
|
23
26
|
type SimpleChanges,
|
|
@@ -33,7 +36,6 @@ import {
|
|
|
33
36
|
type IAriaProps,
|
|
34
37
|
type IModalAnimationType,
|
|
35
38
|
type IModalOrientation,
|
|
36
|
-
type IModalOrientationChangeEvent,
|
|
37
39
|
type IModalPresentationStyle,
|
|
38
40
|
type IModalState,
|
|
39
41
|
} from '@symbiote-native/components';
|
|
@@ -44,7 +46,13 @@ import {
|
|
|
44
46
|
type ISymbioteEvent,
|
|
45
47
|
type IViewStyle,
|
|
46
48
|
} from '@symbiote-native/engine';
|
|
47
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
anchorHostStyle,
|
|
51
|
+
ModalHost,
|
|
52
|
+
SymbioteHostPropsDirective,
|
|
53
|
+
SymbioteStyleInputDirective,
|
|
54
|
+
ViewHost,
|
|
55
|
+
} from '../../primitives';
|
|
48
56
|
|
|
49
57
|
export type {
|
|
50
58
|
IModalAnimationType,
|
|
@@ -53,17 +61,6 @@ export type {
|
|
|
53
61
|
IModalOrientationChangeEvent,
|
|
54
62
|
} from '@symbiote-native/components';
|
|
55
63
|
|
|
56
|
-
const ORIENTATIONS: ReadonlyArray<IModalOrientation> = ['portrait', 'landscape'];
|
|
57
|
-
|
|
58
|
-
function readOrientation(event: ISymbioteEvent): IModalOrientationChangeEvent | undefined {
|
|
59
|
-
const native = event.nativeEvent;
|
|
60
|
-
if (typeof native !== 'object' || native === null || !('orientation' in native)) return undefined;
|
|
61
|
-
const orientation = native.orientation;
|
|
62
|
-
return ORIENTATIONS.some(value => value === orientation)
|
|
63
|
-
? { orientation: orientation === 'landscape' ? 'landscape' : 'portrait' }
|
|
64
|
-
: undefined;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
64
|
// Mirrors React's IModalProps minus children (Angular takes children via <ng-content>).
|
|
68
65
|
export interface IAngularModalProps extends IAccessibilityProps, IAriaProps {
|
|
69
66
|
visible?: boolean;
|
|
@@ -79,7 +76,9 @@ export interface IAngularModalProps extends IAccessibilityProps, IAriaProps {
|
|
|
79
76
|
onShow?: () => void;
|
|
80
77
|
onDismiss?: () => void;
|
|
81
78
|
onRequestClose?: () => void;
|
|
82
|
-
|
|
79
|
+
// The engine hands every listener the ISymbioteEvent wrapper, so the orientation is read at
|
|
80
|
+
// event.nativeEvent.orientation (IModalOrientationChangeEvent describes that payload).
|
|
81
|
+
onOrientationChange?: (event: ISymbioteEvent) => void;
|
|
83
82
|
style?: IStyleProp<IViewStyle>;
|
|
84
83
|
}
|
|
85
84
|
|
|
@@ -102,17 +101,18 @@ export type IAngularModalInputs = Omit<
|
|
|
102
101
|
@Component({
|
|
103
102
|
selector: 'Modal',
|
|
104
103
|
standalone: true,
|
|
104
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
105
105
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
106
106
|
imports: [ModalHost, ViewHost, SymbioteHostPropsDirective],
|
|
107
107
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
108
108
|
template: `
|
|
109
109
|
@if (shouldRender) {
|
|
110
110
|
<symbiote-modal
|
|
111
|
-
[symbioteHostProps]="hostProps"
|
|
111
|
+
[symbioteHostProps]="hostProps()"
|
|
112
112
|
(show)="show.emit()"
|
|
113
113
|
(dismiss)="dismiss.emit()"
|
|
114
114
|
(requestClose)="requestClose.emit()"
|
|
115
|
-
(orientationChange)="
|
|
115
|
+
(orientationChange)="emit(orientationChange, $event)"
|
|
116
116
|
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
117
117
|
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
118
118
|
(magicTap)="emit(magicTap, $event)"
|
|
@@ -125,11 +125,11 @@ export type IAngularModalInputs = Omit<
|
|
|
125
125
|
}
|
|
126
126
|
`,
|
|
127
127
|
})
|
|
128
|
-
export class Modal implements IAngularModalInputs, OnInit, OnChanges {
|
|
128
|
+
export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
129
129
|
@Output() readonly show = new EventEmitter<void>();
|
|
130
130
|
@Output() readonly dismiss = new EventEmitter<void>();
|
|
131
131
|
@Output() readonly requestClose = new EventEmitter<void>();
|
|
132
|
-
@Output() readonly orientationChange = new EventEmitter<
|
|
132
|
+
@Output() readonly orientationChange = new EventEmitter<ISymbioteEvent>();
|
|
133
133
|
@Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
|
|
134
134
|
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
135
135
|
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
@@ -195,7 +195,20 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges {
|
|
|
195
195
|
this.state = createInitialModalState(this.visible === true);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
// Bridges the non-reactive @Input fields `hostProps` reads into the reactive graph, so it can
|
|
199
|
+
// memoize. Plain fields read inside a computed() are UNTRACKED - something must signal "a
|
|
200
|
+
// dependency changed" or the bag goes stale. Signal inputs would do this natively, but `input()`
|
|
201
|
+
// is visible only to the AOT compiler and this package's unit suite runs on JIT (see the
|
|
202
|
+
// `angular-adapter-change-detection` skill, §6); `signal()`/`computed()` are plain runtime APIs.
|
|
203
|
+
// The keep-alive `state` is deliberately NOT a dependency: it gates `shouldRender`, never the bag.
|
|
204
|
+
private readonly hostPropsRevision = signal(0);
|
|
205
|
+
// What the anchor's class-derived style was when the bag was last built (identity, not value).
|
|
206
|
+
private lastAnchorStyle: unknown;
|
|
207
|
+
|
|
198
208
|
ngOnChanges(changes: SimpleChanges): void {
|
|
209
|
+
// Before the keep-alive early-return below: this is the single moment Angular has finished
|
|
210
|
+
// writing every changed @Input, and `hostProps` depends on far more than `visible`.
|
|
211
|
+
this.hostPropsRevision.update(revision => revision + 1);
|
|
199
212
|
const visibleChange = changes.visible;
|
|
200
213
|
// First change is reflected by the ngOnInit seed; only later toggles drive the keep-alive.
|
|
201
214
|
if (visibleChange === undefined || visibleChange.firstChange) return;
|
|
@@ -233,13 +246,31 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges {
|
|
|
233
246
|
});
|
|
234
247
|
}
|
|
235
248
|
|
|
249
|
+
// The anchor's class-derived style is NOT an @Input: `class="..."`/`[ngClass]` at the use site
|
|
250
|
+
// resolves through the renderer's addClass onto this component's anchor host (see
|
|
251
|
+
// anchorHostStyle's doc comment), so it never shows up in SimpleChanges and ngOnChanges alone
|
|
252
|
+
// would leave a later class toggle stranded. ngDoCheck runs at exactly the cadence the old
|
|
253
|
+
// getter was re-read, and bumps only on a real identity change.
|
|
254
|
+
ngDoCheck(): void {
|
|
255
|
+
const anchorStyle = anchorHostStyle(this.elementRef);
|
|
256
|
+
if (anchorStyle === this.lastAnchorStyle) return;
|
|
257
|
+
this.lastAnchorStyle = anchorStyle;
|
|
258
|
+
this.hostPropsRevision.update(revision => revision + 1);
|
|
259
|
+
}
|
|
260
|
+
|
|
236
261
|
// renderModal's own MODAL_HOST_STYLE (position:'absolute') is the outer symbiote-modal node's
|
|
237
262
|
// style; the anchor's class-derived style goes FIRST, that resolved style SECOND —
|
|
238
263
|
// flattenStyle's later-wins collapse keeps the modal's own style winning over its ambient class.
|
|
239
|
-
|
|
264
|
+
//
|
|
265
|
+
// A computed(), not a getter: Angular re-reads a template getter on every refresh of this view
|
|
266
|
+
// and `[symbioteHostProps]` compares by REFERENCE, so a freshly rebuilt (but identical) bag
|
|
267
|
+
// re-pushes every key through renderer.setProperty -> the engine's prop routing - and this one
|
|
268
|
+
// re-runs renderModal to build it.
|
|
269
|
+
readonly hostProps = computed<Record<string, unknown>>(() => {
|
|
270
|
+
this.hostPropsRevision();
|
|
240
271
|
const descriptorProps = this.descriptor.props;
|
|
241
272
|
return { ...descriptorProps, style: [anchorHostStyle(this.elementRef), descriptorProps.style] };
|
|
242
|
-
}
|
|
273
|
+
});
|
|
243
274
|
|
|
244
275
|
get containerStyle(): unknown {
|
|
245
276
|
const container = this.descriptor.children[0];
|
|
@@ -250,12 +281,6 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges {
|
|
|
250
281
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
251
282
|
}
|
|
252
283
|
|
|
253
|
-
emitOrientation(event: unknown): void {
|
|
254
|
-
if (!isSymbioteEvent(event)) return;
|
|
255
|
-
const change = readOrientation(event);
|
|
256
|
-
if (change !== undefined) this.orientationChange.emit(change);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
284
|
// Typed as the a11y intersection WITH the string index (the bag renderModal spreads into the
|
|
260
285
|
// host props), so resolveAccessibilityProps's result stays assignable to passthrough.
|
|
261
286
|
private accessibilityInputs(): IAccessibilityProps & IAriaProps & Record<string, unknown> {
|
|
@@ -5,10 +5,14 @@ import {
|
|
|
5
5
|
Component,
|
|
6
6
|
ElementRef,
|
|
7
7
|
EventEmitter,
|
|
8
|
+
computed,
|
|
8
9
|
inject,
|
|
9
10
|
Input,
|
|
10
11
|
Output,
|
|
12
|
+
signal,
|
|
11
13
|
ViewChild,
|
|
14
|
+
type DoCheck,
|
|
15
|
+
type OnChanges,
|
|
12
16
|
} from '@angular/core';
|
|
13
17
|
import {
|
|
14
18
|
createPressHandlers,
|
|
@@ -40,7 +44,12 @@ import {
|
|
|
40
44
|
type IStyleProp,
|
|
41
45
|
type IViewStyle,
|
|
42
46
|
} from '@symbiote-native/engine';
|
|
43
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
anchorHostStyle,
|
|
49
|
+
SymbioteHostPropsDirective,
|
|
50
|
+
SymbioteStyleInputDirective,
|
|
51
|
+
ViewHost,
|
|
52
|
+
} from '../../primitives';
|
|
44
53
|
|
|
45
54
|
export type { IPressState, IPressableAndroidRippleConfig } from '@symbiote-native/components';
|
|
46
55
|
|
|
@@ -109,12 +118,13 @@ function asSymbioteEvent(event: unknown): ISymbioteEvent | undefined {
|
|
|
109
118
|
@Component({
|
|
110
119
|
selector: 'Pressable, symbiote-pressable',
|
|
111
120
|
standalone: true,
|
|
121
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
112
122
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
113
123
|
imports: [ViewHost, SymbioteHostPropsDirective],
|
|
114
124
|
template: `
|
|
115
125
|
<symbiote-view
|
|
116
126
|
#host
|
|
117
|
-
[symbioteHostProps]="hostProps"
|
|
127
|
+
[symbioteHostProps]="hostProps()"
|
|
118
128
|
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
119
129
|
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
120
130
|
(magicTap)="emit(magicTap, $event)"
|
|
@@ -131,7 +141,7 @@ function asSymbioteEvent(event: unknown): ISymbioteEvent | undefined {
|
|
|
131
141
|
`,
|
|
132
142
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
133
143
|
})
|
|
134
|
-
export class Pressable implements IAngularPressableInputs {
|
|
144
|
+
export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
135
145
|
// The press/hover lifecycle as real Angular events: `(press)="onTap($event)"`, not
|
|
136
146
|
// `[onPress]="onTap"`. createPressHandlers still wants plain IPressHandler callbacks, so
|
|
137
147
|
// emitterHandler() below adapts each EventEmitter into one - only while something is actually
|
|
@@ -207,11 +217,20 @@ export class Pressable implements IAngularPressableInputs {
|
|
|
207
217
|
|
|
208
218
|
@ViewChild('host', { read: ElementRef }) private hostElement?: ElementRef<unknown>;
|
|
209
219
|
|
|
210
|
-
|
|
220
|
+
// INTERNAL mutable state, driven by the press machine rather than by an @Input, so ngOnChanges
|
|
221
|
+
// can never cover it - it carries its OWN signal, which is what lets the memoized bag below stay
|
|
222
|
+
// correct across a press. Exposed as a plain boolean getter so a use site's `#ref.pressed` keeps
|
|
223
|
+
// reading a boolean and the public surface is unchanged.
|
|
224
|
+
private readonly pressedState = signal(false);
|
|
225
|
+
|
|
226
|
+
get pressed(): boolean {
|
|
227
|
+
return this.pressedState();
|
|
228
|
+
}
|
|
229
|
+
|
|
211
230
|
private readonly runtime: IPressRuntime = createPressRuntime();
|
|
212
231
|
private readonly host: IPressHost = {
|
|
213
232
|
setPressed: pressed => {
|
|
214
|
-
this.pressed
|
|
233
|
+
this.pressedState.set(pressed);
|
|
215
234
|
this.changeDetector.detectChanges();
|
|
216
235
|
},
|
|
217
236
|
getMeasureFn: () => {
|
|
@@ -231,6 +250,39 @@ export class Pressable implements IAngularPressableInputs {
|
|
|
231
250
|
// inner `symbiote-view` one level down.
|
|
232
251
|
private readonly elementRef = inject(ElementRef);
|
|
233
252
|
|
|
253
|
+
// Bridges the non-reactive @Input fields above into the reactive graph, so the computed() bag
|
|
254
|
+
// below can memoize a value derived from them. Signal inputs would make this unnecessary, but
|
|
255
|
+
// `input()` is visible only to the AOT compiler and this package's unit suite runs on JIT - see
|
|
256
|
+
// the `angular-adapter-change-detection` skill, §6. Mirrors ScrollView's identical bridge
|
|
257
|
+
// (components/scroll-view/shared.ts), including its constraint: ONLY safe for a bag whose every
|
|
258
|
+
// dependency is an @Input or carries its own signal, never for plain internal mutable state.
|
|
259
|
+
private readonly inputsRevision = signal(0);
|
|
260
|
+
|
|
261
|
+
// The anchor's class-derived style is the one hostProps dependency that is NOT an @Input:
|
|
262
|
+
// `class="..."`/`[class.x]`/`[ngClass]` at the use site resolves through the renderer's
|
|
263
|
+
// addClass/removeClass onto the anchor node (anchorHostStyle's doc comment), so it never appears
|
|
264
|
+
// in SimpleChanges and ngOnChanges cannot cover it. The engine's commitClassStyle allocates a
|
|
265
|
+
// fresh [classStyle, explicitStyle] array on exactly the passes where a class token actually
|
|
266
|
+
// moved, so signal.set's own Object.is check makes an unchanged poll a no-op that dirties nothing.
|
|
267
|
+
private readonly anchorStyle = signal<unknown>(undefined);
|
|
268
|
+
|
|
269
|
+
// ngDoCheck is the only hook that observes the anchor AFTER the parent wrote it. Angular flushes
|
|
270
|
+
// a node's pre-order hooks at the next `advance` PAST it (hooks.ts callHooks runs "until that
|
|
271
|
+
// node index exclusive") or in refreshView's post-template flush - both strictly after the
|
|
272
|
+
// parent's classProp on this element, and strictly before this component's own view refreshes.
|
|
273
|
+
// Angular also nulls the active consumer around lifecycle hooks (render3/hooks.ts), so this
|
|
274
|
+
// write registers no dependency on the caller's view and cannot throw NG0600.
|
|
275
|
+
ngDoCheck(): void {
|
|
276
|
+
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
ngOnChanges(): void {
|
|
280
|
+
// Tells the computed bag below that an @Input moved - plain class fields are not reactive on
|
|
281
|
+
// their own. Must stay in ngOnChanges: that is the single moment Angular has finished writing
|
|
282
|
+
// every changed input for this pass.
|
|
283
|
+
this.inputsRevision.update(revision => revision + 1);
|
|
284
|
+
}
|
|
285
|
+
|
|
234
286
|
get resolvedStyle(): IStyleProp<IViewStyle> | undefined {
|
|
235
287
|
const state: IPressState = { pressed: this.pressed };
|
|
236
288
|
return isStyleFn(this.style) ? this.style(state) : this.style;
|
|
@@ -279,7 +331,14 @@ export class Pressable implements IAngularPressableInputs {
|
|
|
279
331
|
}
|
|
280
332
|
|
|
281
333
|
// Flat bag for `[symbioteHostProps]` (Renderer2.setProperty per key) - see primitives/shared.ts.
|
|
282
|
-
|
|
334
|
+
//
|
|
335
|
+
// MEASURED 2026-08-16: as a getter this rebuilt the bag on every refresh of this view, and a
|
|
336
|
+
// press dirties this very view (SymbioteHostPropsDirective wraps each flat-bag onX with
|
|
337
|
+
// markForCheck, and setPressed calls detectChanges outright), so `[symbioteHostProps]`'s
|
|
338
|
+
// reference check failed every time and re-pushed every key through Renderer2.setProperty ->
|
|
339
|
+
// routeProp for an identical bag. Memoized, an unchanged bag costs one reference compare.
|
|
340
|
+
readonly hostProps = computed<Record<string, unknown>>(() => {
|
|
341
|
+
this.inputsRevision();
|
|
283
342
|
return {
|
|
284
343
|
testID: this.testID,
|
|
285
344
|
nativeID: this.nativeID,
|
|
@@ -289,14 +348,14 @@ export class Pressable implements IAngularPressableInputs {
|
|
|
289
348
|
nextFocusLeft: this.nextFocusLeft,
|
|
290
349
|
nextFocusRight: this.nextFocusRight,
|
|
291
350
|
nextFocusUp: this.nextFocusUp,
|
|
292
|
-
style: [
|
|
351
|
+
style: [this.anchorStyle(), this.resolvedStyle],
|
|
293
352
|
accessible: this.accessible,
|
|
294
353
|
...this.foldedAccessibility,
|
|
295
354
|
android_disableSound: this.android_disableSound,
|
|
296
355
|
nativeBackgroundAndroid: this.resolvedRippleProps?.nativeBackgroundAndroid,
|
|
297
356
|
nativeForegroundAndroid: this.resolvedRippleProps?.nativeForegroundAndroid,
|
|
298
357
|
};
|
|
299
|
-
}
|
|
358
|
+
});
|
|
300
359
|
|
|
301
360
|
handlePressIn(event: unknown): void {
|
|
302
361
|
const symbioteEvent = asSymbioteEvent(event);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// RefreshControl, the Angular lifecycle half. On iOS this is the PullToRefreshView Fabric node that
|
|
2
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
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
3
|
+
// AndroidSwipeRefreshLayout and WRAPS the scroll view, receiving it through <ng-content>. There is
|
|
4
|
+
// no JS-side platform renaming and no shared render fn — every prop forwards straight to the
|
|
5
|
+
// native node, which reads what it understands and ignores the rest, so Android-only and iOS-only
|
|
6
|
+
// prop families ride down harmlessly on both. This folds aria/role through the shared
|
|
7
|
+
// resolveAccessibilityProps and maps the native props + a11y + onRefresh onto the
|
|
8
|
+
// symbiote-refresh-control host, children via <ng-content>. One composed component covers both
|
|
9
|
+
// platforms, so this stays a flat single file.
|
|
10
10
|
//
|
|
11
11
|
// `refreshing` is a controlled prop: the parent owns it and pushes it down each commit; native
|
|
12
12
|
// reports the gesture via the direct `topRefresh` event, which the engine routes to the host's
|
|
@@ -16,13 +16,16 @@ import {
|
|
|
16
16
|
CUSTOM_ELEMENTS_SCHEMA,
|
|
17
17
|
ChangeDetectionStrategy,
|
|
18
18
|
Component,
|
|
19
|
+
computed,
|
|
19
20
|
ElementRef,
|
|
20
21
|
EventEmitter,
|
|
21
22
|
inject,
|
|
22
23
|
Input,
|
|
23
24
|
Output,
|
|
25
|
+
signal,
|
|
24
26
|
ViewChild,
|
|
25
27
|
type AfterViewInit,
|
|
28
|
+
type DoCheck,
|
|
26
29
|
type OnChanges,
|
|
27
30
|
type OnInit,
|
|
28
31
|
type SimpleChanges,
|
|
@@ -42,7 +45,12 @@ import {
|
|
|
42
45
|
type ISymbioteEvent,
|
|
43
46
|
type IViewStyle,
|
|
44
47
|
} from '@symbiote-native/engine';
|
|
45
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
anchorHostStyle,
|
|
50
|
+
RefreshControlHost,
|
|
51
|
+
SymbioteHostPropsDirective,
|
|
52
|
+
SymbioteStyleInputDirective,
|
|
53
|
+
} from '../../primitives';
|
|
46
54
|
|
|
47
55
|
// Record<string, unknown> already tolerates `style` holding the [anchorHostStyle, this.style]
|
|
48
56
|
// array anchorHostStyle's merge produces (see hostProps below) — no widening needed.
|
|
@@ -89,13 +97,14 @@ export type IAngularRefreshControlInputs = Omit<
|
|
|
89
97
|
@Component({
|
|
90
98
|
selector: 'RefreshControl',
|
|
91
99
|
standalone: true,
|
|
100
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
92
101
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
93
102
|
imports: [RefreshControlHost, SymbioteHostPropsDirective],
|
|
94
103
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
95
104
|
template: `
|
|
96
105
|
<symbiote-refresh-control
|
|
97
106
|
#host
|
|
98
|
-
[symbioteHostProps]="hostProps"
|
|
107
|
+
[symbioteHostProps]="hostProps()"
|
|
99
108
|
(refresh)="handleRefresh()"
|
|
100
109
|
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
101
110
|
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
@@ -107,7 +116,7 @@ export type IAngularRefreshControlInputs = Omit<
|
|
|
107
116
|
`,
|
|
108
117
|
})
|
|
109
118
|
export class RefreshControl
|
|
110
|
-
implements IAngularRefreshControlInputs, OnInit, OnChanges, AfterViewInit
|
|
119
|
+
implements IAngularRefreshControlInputs, OnInit, OnChanges, DoCheck, AfterViewInit
|
|
111
120
|
{
|
|
112
121
|
// Controlled prop the parent owns; required to match the React reference surface.
|
|
113
122
|
@Input({ required: true }) refreshing!: boolean;
|
|
@@ -175,6 +184,18 @@ export class RefreshControl
|
|
|
175
184
|
private lastNativeRefreshing = false;
|
|
176
185
|
private refreshNativeNode: unknown;
|
|
177
186
|
|
|
187
|
+
// Bridges the non-reactive @Input fields `hostProps` reads into the reactive graph, so it can
|
|
188
|
+
// memoize. Plain fields read inside a computed() are UNTRACKED - something must signal "a
|
|
189
|
+
// dependency changed" or the bag goes stale. Signal inputs would do this natively, but `input()`
|
|
190
|
+
// is visible only to the AOT compiler and this package's unit suite runs on JIT (see the
|
|
191
|
+
// `angular-adapter-change-detection` skill, §6); `signal()`/`computed()` are plain runtime APIs.
|
|
192
|
+
// Safe here because `refreshing` is a CONTROLLED @Input - the parent owns it, nothing in this
|
|
193
|
+
// class assigns it. The internal `lastNativeRefreshing` / `refreshNativeNode` fields drive the
|
|
194
|
+
// native handshake only; neither is read by the bag, so neither may bump this.
|
|
195
|
+
private readonly hostPropsRevision = signal(0);
|
|
196
|
+
// What the anchor's class-derived style was when the bag was last built (identity, not value).
|
|
197
|
+
private lastAnchorStyle: unknown;
|
|
198
|
+
|
|
178
199
|
ngOnInit(): void {
|
|
179
200
|
dlog('RefreshControl -> PullToRefreshView');
|
|
180
201
|
dlog(`RefreshControl refreshing=${String(this.refreshing)}`);
|
|
@@ -188,6 +209,9 @@ export class RefreshControl
|
|
|
188
209
|
}
|
|
189
210
|
|
|
190
211
|
ngOnChanges(changes: SimpleChanges): void {
|
|
212
|
+
// Before the `refreshing`-only early-return below: this is the single moment Angular has
|
|
213
|
+
// finished writing every changed @Input, and `hostProps` depends on the whole input surface.
|
|
214
|
+
this.hostPropsRevision.update(revision => revision + 1);
|
|
191
215
|
const refreshing = changes.refreshing;
|
|
192
216
|
if (refreshing === undefined) return;
|
|
193
217
|
if (refreshing.firstChange) {
|
|
@@ -220,11 +244,29 @@ export class RefreshControl
|
|
|
220
244
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
221
245
|
}
|
|
222
246
|
|
|
247
|
+
// The anchor's class-derived style is NOT an @Input: `class="..."`/`[ngClass]` at the use site
|
|
248
|
+
// resolves through the renderer's addClass onto this component's anchor host (see
|
|
249
|
+
// anchorHostStyle's doc comment), so it never shows up in SimpleChanges and ngOnChanges alone
|
|
250
|
+
// would leave a later class toggle stranded. ngDoCheck runs at exactly the cadence the old
|
|
251
|
+
// getter was re-read, and bumps only on a real identity change.
|
|
252
|
+
ngDoCheck(): void {
|
|
253
|
+
const anchorStyle = anchorHostStyle(this.elementRef);
|
|
254
|
+
if (anchorStyle === this.lastAnchorStyle) return;
|
|
255
|
+
this.lastAnchorStyle = anchorStyle;
|
|
256
|
+
this.hostPropsRevision.update(revision => revision + 1);
|
|
257
|
+
}
|
|
258
|
+
|
|
223
259
|
// The full native + a11y prop bag applied onto the host in one shot via
|
|
224
260
|
// [symbioteHostProps], instead of enumerating each key as its own template binding. The
|
|
225
261
|
// anchor's class-derived style goes FIRST, this component's own explicit `style` @Input SECOND
|
|
226
262
|
// — flattenStyle's later-wins collapse keeps an explicit [style] winning over its ambient class.
|
|
227
|
-
|
|
263
|
+
//
|
|
264
|
+
// A computed(), not a getter: Angular re-reads a template getter on every refresh of this view
|
|
265
|
+
// and `[symbioteHostProps]` compares by REFERENCE, so a freshly rebuilt (but identical) bag
|
|
266
|
+
// re-pushes every key through renderer.setProperty -> the engine's prop routing. A pull gesture
|
|
267
|
+
// dirties this view through its own `(refresh)` binding, which is exactly when that waste hit.
|
|
268
|
+
readonly hostProps = computed<IHostProps>(() => {
|
|
269
|
+
this.hostPropsRevision();
|
|
228
270
|
return {
|
|
229
271
|
refreshing: this.refreshing,
|
|
230
272
|
tintColor: this.tintColor,
|
|
@@ -241,7 +283,7 @@ export class RefreshControl
|
|
|
241
283
|
accessible: this.accessible,
|
|
242
284
|
...this.folded,
|
|
243
285
|
};
|
|
244
|
-
}
|
|
286
|
+
});
|
|
245
287
|
|
|
246
288
|
// Fold the web aria-*/role aliases into the canonical accessibility* props once per render, so the
|
|
247
289
|
// host node never sees an aria-* key (native ignores them) — the shared transform every adapter runs.
|
|
@@ -10,11 +10,15 @@ import {
|
|
|
10
10
|
CUSTOM_ELEMENTS_SCHEMA,
|
|
11
11
|
ChangeDetectionStrategy,
|
|
12
12
|
Component,
|
|
13
|
+
computed,
|
|
13
14
|
ElementRef,
|
|
14
15
|
EventEmitter,
|
|
15
16
|
inject,
|
|
16
17
|
Input,
|
|
17
18
|
Output,
|
|
19
|
+
signal,
|
|
20
|
+
type DoCheck,
|
|
21
|
+
type OnChanges,
|
|
18
22
|
} from '@angular/core';
|
|
19
23
|
import {
|
|
20
24
|
resolveAccessibilityProps,
|
|
@@ -28,7 +32,12 @@ import {
|
|
|
28
32
|
type ISymbioteEvent,
|
|
29
33
|
type IViewStyle,
|
|
30
34
|
} from '@symbiote-native/engine';
|
|
31
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
anchorHostStyle,
|
|
37
|
+
SafeAreaViewHost,
|
|
38
|
+
SymbioteHostPropsDirective,
|
|
39
|
+
SymbioteStyleInputDirective,
|
|
40
|
+
} from '../../primitives';
|
|
32
41
|
|
|
33
42
|
// Mirrors React's ISafeAreaViewProps minus children (Angular takes children via <ng-content>),
|
|
34
43
|
// declared per-adapter over the shared accessibility base since the framework-specific children
|
|
@@ -53,12 +62,13 @@ export type IAngularSafeAreaViewInputs = Omit<
|
|
|
53
62
|
@Component({
|
|
54
63
|
selector: 'SafeAreaView',
|
|
55
64
|
standalone: true,
|
|
65
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
56
66
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
57
67
|
imports: [SafeAreaViewHost, SymbioteHostPropsDirective],
|
|
58
68
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
59
69
|
template: `
|
|
60
70
|
<symbiote-safe-area-view
|
|
61
|
-
[symbioteHostProps]="hostProps"
|
|
71
|
+
[symbioteHostProps]="hostProps()"
|
|
62
72
|
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
63
73
|
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
64
74
|
(magicTap)="emit(magicTap, $event)"
|
|
@@ -69,7 +79,7 @@ export type IAngularSafeAreaViewInputs = Omit<
|
|
|
69
79
|
</symbiote-safe-area-view>
|
|
70
80
|
`,
|
|
71
81
|
})
|
|
72
|
-
export class SafeAreaView implements IAngularSafeAreaViewInputs {
|
|
82
|
+
export class SafeAreaView implements IAngularSafeAreaViewInputs, OnChanges, DoCheck {
|
|
73
83
|
@Input() style?: IStyleProp<IViewStyle>;
|
|
74
84
|
// Real @Output()s, not `[onX]="…"` callbacks. Safe to name `layout` the same as the native
|
|
75
85
|
// `layout` event fired inside this component's own template — the engine's bubble() treats
|
|
@@ -127,6 +137,35 @@ export class SafeAreaView implements IAngularSafeAreaViewInputs {
|
|
|
127
137
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
128
138
|
}
|
|
129
139
|
|
|
140
|
+
// Bridges the non-reactive fields `hostProps` reads into the reactive graph, so it can memoize.
|
|
141
|
+
// Plain fields read inside a computed() are UNTRACKED - something must signal "a dependency
|
|
142
|
+
// changed" or the bag goes stale. Signal inputs would do this natively, but `input()` is visible
|
|
143
|
+
// only to the AOT compiler and this package's unit suite runs on JIT (see the
|
|
144
|
+
// `angular-adapter-change-detection` skill, §6); `signal()`/`computed()` are plain runtime APIs.
|
|
145
|
+
// TWO bump sites, both required - see ngOnChanges and ngDoCheck below.
|
|
146
|
+
private readonly hostPropsRevision = signal(0);
|
|
147
|
+
// What the anchor's class-derived style was when the bag was last built (identity, not value).
|
|
148
|
+
private lastAnchorStyle: unknown;
|
|
149
|
+
|
|
150
|
+
ngOnChanges(): void {
|
|
151
|
+
// The single moment Angular has finished writing every changed @Input for this pass.
|
|
152
|
+
this.hostPropsRevision.update(revision => revision + 1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The anchor's class-derived style is NOT an @Input: `class="..."`/`[ngClass]` at the use site
|
|
156
|
+
// resolves through the renderer's addClass onto this component's anchor host (see
|
|
157
|
+
// anchorHostStyle's doc comment), so it never shows up in SimpleChanges - ngOnChanges alone
|
|
158
|
+
// would strand a class toggled after mount (regression-covered by safe-area-view.test.ts's
|
|
159
|
+
// class-toggle case, which fails the moment this hook stops bumping). ngDoCheck runs at exactly
|
|
160
|
+
// the cadence the old getter was re-read, and bumps only on a real identity change, so an
|
|
161
|
+
// unchanged anchor still costs nothing.
|
|
162
|
+
ngDoCheck(): void {
|
|
163
|
+
const anchorStyle = anchorHostStyle(this.elementRef);
|
|
164
|
+
if (anchorStyle === this.lastAnchorStyle) return;
|
|
165
|
+
this.lastAnchorStyle = anchorStyle;
|
|
166
|
+
this.hostPropsRevision.update(revision => revision + 1);
|
|
167
|
+
}
|
|
168
|
+
|
|
130
169
|
// Folds style/testID/nativeID/accessible alongside the accessibility*/aria-*/role surface into
|
|
131
170
|
// one bag for [symbioteHostProps] — resolveAccessibilityProps takes the whole props object (not
|
|
132
171
|
// just the accessibility slice) and returns it with aria-*/role folded into accessibility*, so
|
|
@@ -134,7 +173,13 @@ export class SafeAreaView implements IAngularSafeAreaViewInputs {
|
|
|
134
173
|
// SafeAreaView runs over its whole `rawProps`. The anchor's class-derived style goes FIRST, this
|
|
135
174
|
// component's own explicit `style` @Input SECOND — flattenStyle's later-wins collapse keeps an
|
|
136
175
|
// explicit [style] winning over its ambient class.
|
|
137
|
-
|
|
176
|
+
//
|
|
177
|
+
// A computed(), not a getter: Angular re-reads a template getter on every refresh of this view
|
|
178
|
+
// and `[symbioteHostProps]` compares by REFERENCE, so a freshly rebuilt (but identical) bag
|
|
179
|
+
// re-pushes every key through renderer.setProperty -> the engine's prop routing. Memoized, an
|
|
180
|
+
// a11y/layout event that only dirties this view costs nothing.
|
|
181
|
+
readonly hostProps = computed<Record<string, unknown>>(() => {
|
|
182
|
+
this.hostPropsRevision();
|
|
138
183
|
return resolveAccessibilityProps({
|
|
139
184
|
style: [anchorHostStyle(this.elementRef), this.style],
|
|
140
185
|
testID: this.testID,
|
|
@@ -173,5 +218,5 @@ export class SafeAreaView implements IAngularSafeAreaViewInputs {
|
|
|
173
218
|
'aria-valuenow': this.ariaValueNow,
|
|
174
219
|
'aria-valuetext': this.ariaValueText,
|
|
175
220
|
});
|
|
176
|
-
}
|
|
221
|
+
});
|
|
177
222
|
}
|