@symbiote-native/angular 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/babel-lower-host-primitives.cjs +636 -0
- package/babel-register-composed.cjs +21 -3
- package/build/angular/anchor-host-registry.js +3 -1
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/bootstrap.js +1 -1
- package/build/angular/bootstrap.js.map +1 -1
- package/build/angular/components/activity-indicator/index.android.js +7 -2
- package/build/angular/components/activity-indicator/index.android.js.map +1 -1
- package/build/angular/components/activity-indicator/index.ios.js +3 -1
- package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
- package/build/angular/components/activity-indicator/shared.d.ts +1 -1
- package/build/angular/components/activity-indicator/shared.js +1 -1
- package/build/angular/components/activity-indicator/shared.js.map +1 -1
- package/build/angular/components/button.d.ts +3 -0
- package/build/angular/components/button.js +22 -5
- package/build/angular/components/button.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +1 -1
- package/build/angular/components/flat-list/index.js +47 -14
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image/index.android.js +4 -10
- package/build/angular/components/image/index.android.js.map +1 -1
- package/build/angular/components/image/index.ios.js +4 -10
- package/build/angular/components/image/index.ios.js.map +1 -1
- package/build/angular/components/image/shared.d.ts +4 -4
- package/build/angular/components/image/shared.js +51 -33
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/image-background.d.ts +3 -0
- package/build/angular/components/image-background.js +32 -9
- package/build/angular/components/image-background.js.map +1 -1
- package/build/angular/components/input-accessory-view/index.d.ts +2 -1
- package/build/angular/components/input-accessory-view/index.js +18 -16
- package/build/angular/components/input-accessory-view/index.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +3 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +56 -19
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +1 -0
- package/build/angular/components/modal/index.js +21 -10
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable/index.d.ts +10 -5
- package/build/angular/components/pressable/index.js +61 -16
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control/index.d.ts +2 -1
- package/build/angular/components/refresh-control/index.js +13 -9
- package/build/angular/components/refresh-control/index.js.map +1 -1
- package/build/angular/components/safe-area-view/index.d.ts +3 -1
- package/build/angular/components/safe-area-view/index.js +22 -20
- package/build/angular/components/safe-area-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/index.android.js +44 -14
- package/build/angular/components/scroll-view/index.android.js.map +1 -1
- package/build/angular/components/scroll-view/index.ios.js +13 -5
- package/build/angular/components/scroll-view/index.ios.js.map +1 -1
- package/build/angular/components/scroll-view/projection.d.ts +32 -0
- package/build/angular/components/scroll-view/projection.js +212 -47
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.d.ts +2 -0
- package/build/angular/components/scroll-view/shared.js +48 -11
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.js +7 -3
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +9 -1
- package/build/angular/components/section-list/index.js +72 -18
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/switch/index.android.js +23 -9
- package/build/angular/components/switch/index.android.js.map +1 -1
- package/build/angular/components/switch/index.ios.js +23 -9
- package/build/angular/components/switch/index.ios.js.map +1 -1
- package/build/angular/components/switch/shared.d.ts +1 -1
- package/build/angular/components/switch/shared.js +5 -2
- package/build/angular/components/switch/shared.js.map +1 -1
- package/build/angular/components/text-input.d.ts +8 -2
- package/build/angular/components/text-input.js +79 -34
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +36 -7
- package/build/angular/components/touchable/index.js +280 -72
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +2 -1
- package/build/angular/components/touchable-native-feedback/index.js +19 -17
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +4 -0
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +8 -2
- package/build/angular/components/virtualized-list/index.js +207 -61
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +21 -1
- package/build/angular/components/virtualized-section-list/index.js +100 -22
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +2 -2
- package/build/angular/components.js +1 -1
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.d.ts +11 -2
- package/build/angular/create-portal/index.js +42 -12
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +3 -1
- package/build/angular/descriptor-to-angular/index.js.map +1 -1
- package/build/angular/diagnostics.d.ts +33 -0
- package/build/angular/diagnostics.js +82 -0
- package/build/angular/diagnostics.js.map +1 -0
- package/build/angular/gate-demand.d.ts +37 -0
- package/build/angular/gate-demand.js +84 -0
- package/build/angular/gate-demand.js.map +1 -0
- package/build/angular/host-instance/index.js +1 -1
- package/build/angular/host-instance/index.js.map +1 -1
- package/build/angular/index.d.ts +6 -2
- package/build/angular/index.js +16 -1
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +1 -0
- package/build/angular/modules/animated/create-animated-component.js +41 -18
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/app-registry/index.js.map +1 -1
- package/build/angular/modules/status-bar/index.js +1 -1
- package/build/angular/modules/status-bar/index.js.map +1 -1
- package/build/angular/primitives/index.d.ts +31 -2
- package/build/angular/primitives/index.js +81 -3
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +9 -1
- package/build/angular/primitives/shared.js +49 -3
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/register.d.ts +1 -0
- package/build/angular/register.js +35 -0
- package/build/angular/register.js.map +1 -0
- package/build/angular/render/index.js +51 -6
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.d.ts +1 -0
- package/build/angular/renderer/index.js +157 -21
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/services/color-scheme.service.js.map +1 -1
- package/build/angular/services/window-dimensions.service.js +1 -1
- package/build/angular/services/window-dimensions.service.js.map +1 -1
- package/build/angular/state-style.d.ts +9 -0
- package/build/angular/state-style.js +39 -0
- package/build/angular/state-style.js.map +1 -0
- package/metro-transformer.cjs +69 -0
- package/package.json +27 -8
- package/src/anchor-host-registry.ts +3 -1
- package/src/bootstrap.ts +4 -1
- package/src/components/activity-indicator/index.android.ts +7 -2
- package/src/components/activity-indicator/index.ios.ts +3 -1
- package/src/components/activity-indicator/shared.ts +32 -13
- package/src/components/button.ts +36 -6
- package/src/components/flat-list/index.ts +91 -29
- package/src/components/image/index.android.ts +9 -6
- package/src/components/image/index.ios.ts +9 -6
- package/src/components/image/shared.ts +110 -45
- package/src/components/image-background.ts +47 -10
- package/src/components/input-accessory-view/index.ts +41 -18
- package/src/components/keyboard-avoiding-view/index.ts +76 -22
- package/src/components/modal/index.ts +39 -12
- package/src/components/pressable/index.ts +121 -31
- package/src/components/refresh-control/index.ts +36 -13
- package/src/components/safe-area-view/index.ts +44 -19
- package/src/components/scroll-view/index.android.ts +31 -9
- package/src/components/scroll-view/index.ios.ts +17 -4
- package/src/components/scroll-view/projection.ts +290 -63
- package/src/components/scroll-view/shared.ts +124 -30
- package/src/components/scroll-view/sticky-header.ts +25 -8
- package/src/components/section-list/index.ts +79 -19
- package/src/components/switch/index.android.ts +18 -7
- package/src/components/switch/index.ios.ts +18 -7
- package/src/components/switch/shared.ts +15 -4
- package/src/components/text-input.ts +119 -31
- package/src/components/touchable/index.ts +339 -72
- package/src/components/touchable-native-feedback/index.ts +50 -18
- package/src/components/virtualized-list/directives.ts +19 -5
- package/src/components/virtualized-list/index.ts +292 -72
- package/src/components/virtualized-section-list/directives.ts +6 -3
- package/src/components/virtualized-section-list/index.ts +164 -34
- package/src/components.ts +6 -1
- package/src/create-portal/index.ts +47 -11
- package/src/create-tunnel/index.ts +3 -1
- package/src/descriptor-to-angular/index.ts +29 -8
- package/src/diagnostics.ts +138 -0
- package/src/gate-demand.ts +109 -0
- package/src/host-instance/index.ts +9 -3
- package/src/index.ts +33 -2
- package/src/modules/animated/create-animated-component.ts +67 -19
- package/src/modules/app-registry/index.ts +3 -1
- package/src/modules/status-bar/index.ts +13 -4
- package/src/primitives/index.ts +73 -2
- package/src/primitives/shared.ts +70 -9
- package/src/register.ts +43 -0
- package/src/render/index.ts +59 -7
- package/src/renderer/index.ts +209 -37
- package/src/services/color-scheme.service.ts +3 -1
- package/src/services/window-dimensions.service.ts +11 -4
- package/src/state-style.ts +39 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Per-window counters for the Angular adapter, the twin of the engine's readCommitProfile().
|
|
2
|
+
// The engine's number answers "what did the commit walk cost"; these answer the question one layer
|
|
3
|
+
// up, which is the one Angular keeps losing: HOW MANY TIMES did anything run at all.
|
|
4
|
+
//
|
|
5
|
+
// Angular is the only adapter whose update model can free-run. A change-detection pass that writes
|
|
6
|
+
// a fresh reference into a downstream @Input marks a view dirty, the zoneless scheduler books
|
|
7
|
+
// another pass, and it never settles - the hazard `stableAnchorStyle` and VirtualizedList's
|
|
8
|
+
// `lastRecompute` gate each guard one instance of. A guard that stops holding is INVISIBLE from the
|
|
9
|
+
// outside: the screen paints correctly, only the frame rate collapses. `cdPasses` at rest is what
|
|
10
|
+
// makes it visible, and every other counter here narrows the same window to a suspect.
|
|
11
|
+
//
|
|
12
|
+
// Deliberately NOT gated behind isDebug(): an integer increment is noise next to the work it
|
|
13
|
+
// counts, and the numbers are only meaningful from a RELEASE build (dev-mode Angular runs its own
|
|
14
|
+
// checks and drowns the signal). The per-name detail map IS gated, since it allocates.
|
|
15
|
+
|
|
16
|
+
export interface IAngularProfile {
|
|
17
|
+
// Change-detection passes: RendererFactory2.end() runs once per ApplicationRef.tick().
|
|
18
|
+
cdPasses: number;
|
|
19
|
+
// Primitive host views (symbiote-view / symbiote-text / ...) Angular actually walked. Divided by
|
|
20
|
+
// cdPasses this is the SIZE OF THE TREE one tick touches - the number that separates "the list is
|
|
21
|
+
// expensive" from "every mounted screen is re-checked because a list scrolled". The primitives are
|
|
22
|
+
// CheckAlways, and a Global tick refreshes every CheckAlways view in the application.
|
|
23
|
+
viewsChecked: number;
|
|
24
|
+
// Prop/style/class/text writes that reached the engine. At rest this must be 0: a nonzero idle
|
|
25
|
+
// value means change detection is rewriting props nothing asked to change.
|
|
26
|
+
rendererWrites: number;
|
|
27
|
+
nodesCreated: number;
|
|
28
|
+
nodesInserted: number;
|
|
29
|
+
nodesRemoved: number;
|
|
30
|
+
// The `class` poll every COMPOSED component runs per check (SymbioteStyleInputDirective: Pressable,
|
|
31
|
+
// ScrollView, Image, the lists...; the raw primitives are counted by `viewsChecked` instead).
|
|
32
|
+
// `styleMarks` is how often the poll actually found a change and dirtied the view - the ratio is
|
|
33
|
+
// how much of the polling is wasted, and a nonzero idle `styleMarks` is a free-run engine.
|
|
34
|
+
styleChecks: number;
|
|
35
|
+
styleMarks: number;
|
|
36
|
+
// VirtualizedList: checks vs the ones that got past the `lastRecompute` dedup gate. Recomputes
|
|
37
|
+
// climbing with checks means the gate is not holding, which is the free-run bug itself.
|
|
38
|
+
listChecks: number;
|
|
39
|
+
listRecomputes: number;
|
|
40
|
+
listMarks: number;
|
|
41
|
+
// Scroll events the list saw. With sticky headers RN pins scrollEventThrottle to 1, so this is
|
|
42
|
+
// the frame rate of the gesture and the denominator for everything measured during a drag.
|
|
43
|
+
scrollTicks: number;
|
|
44
|
+
// Cell views. `outletUpdates` is the cheap path (context patched in place); `outletCreates` past
|
|
45
|
+
// the first window means cells are being rebuilt instead of reused, which is the cost CDK's
|
|
46
|
+
// view-recycling strategy exists to avoid.
|
|
47
|
+
outletCreates: number;
|
|
48
|
+
outletUpdates: number;
|
|
49
|
+
outletDestroys: number;
|
|
50
|
+
// Sticky projection: schedules are cheap (a Set insert), flushes walk every projected record.
|
|
51
|
+
projectionSchedules: number;
|
|
52
|
+
projectionFlushes: number;
|
|
53
|
+
// A sticky wrapper rebuilt loses its measured layout and costs a native round trip; reuse is the
|
|
54
|
+
// fixed path. Creates climbing during a plain scroll is the regression this pair pins.
|
|
55
|
+
stickyWrapperCreates: number;
|
|
56
|
+
stickyWrapperReuses: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type IAngularCounter = keyof IAngularProfile;
|
|
60
|
+
|
|
61
|
+
function createCounters(): IAngularProfile {
|
|
62
|
+
return {
|
|
63
|
+
cdPasses: 0,
|
|
64
|
+
viewsChecked: 0,
|
|
65
|
+
rendererWrites: 0,
|
|
66
|
+
nodesCreated: 0,
|
|
67
|
+
nodesInserted: 0,
|
|
68
|
+
nodesRemoved: 0,
|
|
69
|
+
styleChecks: 0,
|
|
70
|
+
styleMarks: 0,
|
|
71
|
+
listChecks: 0,
|
|
72
|
+
listRecomputes: 0,
|
|
73
|
+
listMarks: 0,
|
|
74
|
+
scrollTicks: 0,
|
|
75
|
+
outletCreates: 0,
|
|
76
|
+
outletUpdates: 0,
|
|
77
|
+
outletDestroys: 0,
|
|
78
|
+
projectionSchedules: 0,
|
|
79
|
+
projectionFlushes: 0,
|
|
80
|
+
stickyWrapperCreates: 0,
|
|
81
|
+
stickyWrapperReuses: 0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let counters = createCounters();
|
|
86
|
+
|
|
87
|
+
export function countAngular(counter: IAngularCounter): void {
|
|
88
|
+
counters[counter] += 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Reading zeroes the counters, so a sampler on an interval gets disjoint windows rather than a
|
|
92
|
+
// growing total - same contract as readCommitProfile().
|
|
93
|
+
export function readAngularProfile(): IAngularProfile {
|
|
94
|
+
const snapshot = counters;
|
|
95
|
+
counters = createCounters();
|
|
96
|
+
return snapshot;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Which prop names the renderer actually rewrites, and which host tags get created. Off by default
|
|
100
|
+
// because it allocates per write; the probe screen turns it on for one sample. A write nothing
|
|
101
|
+
// asked for is the whole diagnosis, and the prop NAME is what points at the binding responsible.
|
|
102
|
+
let isDetailEnabled = false;
|
|
103
|
+
const writesByProp = new Map<string, number>();
|
|
104
|
+
const createsByTag = new Map<string, number>();
|
|
105
|
+
|
|
106
|
+
export function setAngularProfileDetail(enabled: boolean): void {
|
|
107
|
+
isDetailEnabled = enabled;
|
|
108
|
+
if (!enabled) {
|
|
109
|
+
writesByProp.clear();
|
|
110
|
+
createsByTag.clear();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function noteAngularWrite(propName: string): void {
|
|
115
|
+
if (!isDetailEnabled) return;
|
|
116
|
+
writesByProp.set(propName, (writesByProp.get(propName) ?? 0) + 1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function noteAngularCreate(tagName: string): void {
|
|
120
|
+
if (!isDetailEnabled) return;
|
|
121
|
+
createsByTag.set(tagName, (createsByTag.get(tagName) ?? 0) + 1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface IAngularProfileDetail {
|
|
125
|
+
writesByProp: readonly (readonly [string, number])[];
|
|
126
|
+
createsByTag: readonly (readonly [string, number])[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Sorted hottest-first and read-and-cleared, so a sample names its own top offenders.
|
|
130
|
+
export function readAngularProfileDetail(): IAngularProfileDetail {
|
|
131
|
+
const detail = {
|
|
132
|
+
writesByProp: [...writesByProp.entries()].sort((a, b) => b[1] - a[1]),
|
|
133
|
+
createsByTag: [...createsByTag.entries()].sort((a, b) => b[1] - a[1]),
|
|
134
|
+
};
|
|
135
|
+
writesByProp.clear();
|
|
136
|
+
createsByTag.clear();
|
|
137
|
+
return detail;
|
|
138
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Whether anything downstream actually wants a gated accessibility event, passed from a wrapper
|
|
2
|
+
// component to the inner component that owns the gate.
|
|
3
|
+
//
|
|
4
|
+
// THE PROBLEM. Six Fabric events fire only if a BOOLEAN prop reaches the payload
|
|
5
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`), and the flag follows the VALUE: a handler
|
|
6
|
+
// present means `true`, absent means the key is deleted. Every component that owns such a gate
|
|
7
|
+
// therefore writes it only while its own `@Output()` is `.observed`.
|
|
8
|
+
//
|
|
9
|
+
// Angular has no conditional template binding, so a wrapper rendering an inner component must write
|
|
10
|
+
// `(accessibilityAction)="accessibilityAction.emit($event)"` unconditionally — which subscribes,
|
|
11
|
+
// which makes the inner's `.observed` permanently true, which lights the flag on EVERY instance
|
|
12
|
+
// whether or not any app listens. Fixing the inner component does nothing: the wrapper is the
|
|
13
|
+
// subscriber. The chain is up to four deep here (SectionList -> VirtualizedSectionList ->
|
|
14
|
+
// VirtualizedList -> ScrollView), and every middle link has the same problem for the same reason.
|
|
15
|
+
//
|
|
16
|
+
// WHY THIS IS DI AND NOT AN `@Input`. An input is public API. An app could write
|
|
17
|
+
// `[gatedEvents]="…"` on a `Pressable` and silently switch its accessibility events OFF — a change
|
|
18
|
+
// nothing in this repo could detect and nothing on screen would show. A `viewProviders` entry is
|
|
19
|
+
// not part of the component's binding surface, so an app cannot reach it by accident.
|
|
20
|
+
//
|
|
21
|
+
// WHY `viewProviders` AND NOT `providers` — measured, both arms, 2026-09-02:
|
|
22
|
+
//
|
|
23
|
+
// viewProviders the wrapper's OWN template sees it, projected content does NOT
|
|
24
|
+
// providers both see it
|
|
25
|
+
//
|
|
26
|
+
// The second half is what matters: an app writing `<TouchableOpacity><Pressable/></TouchableOpacity>`
|
|
27
|
+
// projects its own Pressable INTO the wrapper, and that Pressable must behave exactly as it would
|
|
28
|
+
// standing alone. Under `providers` it would inherit the wrapper's demand and the debt would just
|
|
29
|
+
// move one level down.
|
|
30
|
+
//
|
|
31
|
+
// Verified on both rungs — JIT through `mount()`, and again by executing the artifact from a real
|
|
32
|
+
// `ngc --compilationMode partial` + `babel-linker.cjs` chain, because `viewProviders` compiles into
|
|
33
|
+
// the component definition (`ɵɵProvidersFeature`) and this repo has a recorded case of JIT and AOT
|
|
34
|
+
// disagreeing on a compiled binding (`.claude/rules/test-harness-false-greens.md` §21/§21a). Both
|
|
35
|
+
// arms reported the same four cells.
|
|
36
|
+
import {
|
|
37
|
+
EventEmitter,
|
|
38
|
+
InjectionToken,
|
|
39
|
+
forwardRef,
|
|
40
|
+
inject,
|
|
41
|
+
} from '@angular/core';
|
|
42
|
+
import type { Provider, Type } from '@angular/core';
|
|
43
|
+
import type { ISymbioteEvent } from '@symbiote-native/engine';
|
|
44
|
+
|
|
45
|
+
// The four `on*`-gated accessibility events every wrapper forwards. Not the full gate list from the
|
|
46
|
+
// engine: `layout` and `textLayout` are never forwarded wrapper-to-wrapper, and
|
|
47
|
+
// KeyboardAvoidingView READS its own `onLayout` internally, so gating that one on a subscriber
|
|
48
|
+
// would break inset-following (`.claude/rules/keyboard-avoiding-view-rn-contract.md`).
|
|
49
|
+
export const GATED_ACCESSIBILITY_EVENTS = [
|
|
50
|
+
'accessibilityAction',
|
|
51
|
+
'accessibilityTap',
|
|
52
|
+
'magicTap',
|
|
53
|
+
'accessibilityEscape',
|
|
54
|
+
] as const;
|
|
55
|
+
|
|
56
|
+
export type IGatedAccessibilityEvent =
|
|
57
|
+
(typeof GATED_ACCESSIBILITY_EVENTS)[number];
|
|
58
|
+
|
|
59
|
+
export interface IGateDemand {
|
|
60
|
+
wantsGate(name: IGatedAccessibilityEvent): boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const GATE_DEMAND = new InjectionToken<IGateDemand>(
|
|
64
|
+
'symbiote.gate-demand',
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Declare a wrapper as the demand for everything in its own template.
|
|
69
|
+
*
|
|
70
|
+
* Takes a THUNK rather than the class: the entry sits in the decorator of the very class it names,
|
|
71
|
+
* so the reference has to be late.
|
|
72
|
+
*/
|
|
73
|
+
export function provideGateDemand(get: () => Type<IGateDemand>): Provider {
|
|
74
|
+
return { provide: GATE_DEMAND, useExisting: forwardRef(get) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* For a component that OWNS a gate — the innermost one, which writes the flag.
|
|
79
|
+
*/
|
|
80
|
+
export function injectGateDemand(): IGateDemand | null {
|
|
81
|
+
return inject(GATE_DEMAND, { optional: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* For a component that PROVIDES demand and is itself wrapped.
|
|
86
|
+
*
|
|
87
|
+
* `skipSelf` is not optional here: a component resolves its own `viewProviders`, so without it the
|
|
88
|
+
* lookup reaches this class's own `useExisting` entry and Angular throws NG0200 at construction.
|
|
89
|
+
* Loud rather than silent, but the shape is easy to get wrong, which is why the two lookups are
|
|
90
|
+
* separate functions instead of one with a flag.
|
|
91
|
+
*/
|
|
92
|
+
export function injectGateDemandAbove(): IGateDemand | null {
|
|
93
|
+
return inject(GATE_DEMAND, { optional: true, skipSelf: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The one expression both roles evaluate.
|
|
98
|
+
*
|
|
99
|
+
* A demand from above OVERRIDES the local emitter, and that override is the whole mechanism: a
|
|
100
|
+
* middle link's own emitter is `.observed` precisely because the level above bound it, so answering
|
|
101
|
+
* from it would report "wanted" for a chain nobody subscribed to.
|
|
102
|
+
*/
|
|
103
|
+
export function gateWanted(
|
|
104
|
+
above: IGateDemand | null,
|
|
105
|
+
name: IGatedAccessibilityEvent,
|
|
106
|
+
own: EventEmitter<ISymbioteEvent>,
|
|
107
|
+
): boolean {
|
|
108
|
+
return above !== null ? above.wantsGate(name) : own.observed;
|
|
109
|
+
}
|
|
@@ -13,14 +13,19 @@
|
|
|
13
13
|
// An uncommitted or unknown input surfaces as null.
|
|
14
14
|
|
|
15
15
|
import { ElementRef } from '@angular/core';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
getNativeTag,
|
|
18
|
+
isSymbioteNode,
|
|
19
|
+
type ISymbioteNode,
|
|
20
|
+
} from '@symbiote-native/engine';
|
|
17
21
|
|
|
18
22
|
export type { IHostInstance } from '@symbiote-native/engine';
|
|
19
23
|
|
|
20
24
|
function resolveHostNode(candidate: unknown): ISymbioteNode | null {
|
|
21
25
|
if (candidate === null || candidate === undefined) return null;
|
|
22
26
|
if (isSymbioteNode(candidate)) return candidate;
|
|
23
|
-
if (candidate instanceof ElementRef)
|
|
27
|
+
if (candidate instanceof ElementRef)
|
|
28
|
+
return resolveHostNode(candidate.nativeElement);
|
|
24
29
|
const maybeHost = candidate as { nativeElement?: unknown };
|
|
25
30
|
if (typeof maybeHost.nativeElement !== 'undefined') {
|
|
26
31
|
return resolveHostNode(maybeHost.nativeElement);
|
|
@@ -29,7 +34,8 @@ function resolveHostNode(candidate: unknown): ISymbioteNode | null {
|
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
export function findNodeHandle(componentOrHandle: unknown): number | null {
|
|
32
|
-
if (componentOrHandle === null || componentOrHandle === undefined)
|
|
37
|
+
if (componentOrHandle === null || componentOrHandle === undefined)
|
|
38
|
+
return null;
|
|
33
39
|
if (typeof componentOrHandle === 'number') return componentOrHandle;
|
|
34
40
|
const node = resolveHostNode(componentOrHandle);
|
|
35
41
|
return node ? (getNativeTag(node) ?? null) : null;
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// SEAM SCAFFOLD: mount/unmount + the renderer seam + host intrinsic selectors. Full RN-like
|
|
6
6
|
// composed components still flow through the shared @symbiote-native/components bridge.
|
|
7
7
|
|
|
8
|
+
import './register';
|
|
9
|
+
|
|
8
10
|
export {
|
|
9
11
|
ActivityIndicator,
|
|
10
12
|
anchorHostStyle,
|
|
@@ -61,6 +63,16 @@ export {
|
|
|
61
63
|
AnimatedText,
|
|
62
64
|
AnimatedView,
|
|
63
65
|
} from './modules/animated';
|
|
66
|
+
// Exported even though Angular's version maps only the four primitives (View/Text/Image/
|
|
67
|
+
// ScrollView) onto the pre-authored wrappers above and THROWS on anything else — there is no JIT
|
|
68
|
+
// under AOT/Metro, so it cannot synthesize a wrapper at runtime the way the other four adapters
|
|
69
|
+
// do. It ships anyway because portable code (`createAnimatedComponent(View)`) then compiles and
|
|
70
|
+
// runs identically on all five adapters, and the one case Angular cannot serve fails with a
|
|
71
|
+
// message naming the fix (author a standalone @Component over AnimatedComponentBase) instead of
|
|
72
|
+
// failing as a missing export, which names nothing. Found by the barrel audit in
|
|
73
|
+
// `.claude/rules/adapter-parity-audit.md`: it was implemented and tested here all along and simply
|
|
74
|
+
// never reached this file, so `@symbiote-native/angular` consumers could not reach it at all.
|
|
75
|
+
export { createAnimatedComponent } from './modules/animated';
|
|
64
76
|
export type {
|
|
65
77
|
IActivityIndicatorProps,
|
|
66
78
|
IAngularImageBackgroundProps,
|
|
@@ -135,7 +147,12 @@ export { DescriptorOutlet } from './descriptor-to-angular';
|
|
|
135
147
|
// idiom) parameterized by an `@Input()`, rather than a factory returning fresh components per
|
|
136
148
|
// call.
|
|
137
149
|
export { PortalDirective, PortalOutletDirective } from './create-portal';
|
|
138
|
-
export {
|
|
150
|
+
export {
|
|
151
|
+
createTunnel,
|
|
152
|
+
TunnelInDirective,
|
|
153
|
+
TunnelOut,
|
|
154
|
+
type ITunnelStore,
|
|
155
|
+
} from './create-tunnel';
|
|
139
156
|
export { SymbioteRenderer, SymbioteRendererFactory } from './renderer';
|
|
140
157
|
// registerComposedComponent lives in the dependency-free leaf ./anchor-host-registry, NOT in the
|
|
141
158
|
// require-cyclic ./renderer — see the leaf header. The babel-register-composed plugin injects the
|
|
@@ -258,7 +275,11 @@ export {
|
|
|
258
275
|
setNativeViewConfigSource,
|
|
259
276
|
StyleSheet,
|
|
260
277
|
} from '@symbiote-native/engine';
|
|
261
|
-
export type {
|
|
278
|
+
export type {
|
|
279
|
+
IRootTag,
|
|
280
|
+
ISymbioteEvent,
|
|
281
|
+
ISymbioteNode,
|
|
282
|
+
} from '@symbiote-native/engine';
|
|
262
283
|
// The agnostic style / platform / view-config types behind the values above. Angular takes its
|
|
263
284
|
// component props as @Input()s and exports no per-component prop type, but an app still needs
|
|
264
285
|
// these to type a style object or a Platform.select spec.
|
|
@@ -288,3 +309,13 @@ export type {
|
|
|
288
309
|
IPressableAndroidRippleConfig,
|
|
289
310
|
IImageStatics,
|
|
290
311
|
} from '@symbiote-native/components';
|
|
312
|
+
|
|
313
|
+
// Diagnostics: per-window Angular-side counters (change-detection passes, renderer writes, list
|
|
314
|
+
// recomputes, cell views). The engine's readCommitProfile() prices the commit; these say how many
|
|
315
|
+
// times anything ran at all - the number that exposes a change-detection free-run.
|
|
316
|
+
export {
|
|
317
|
+
readAngularProfile,
|
|
318
|
+
readAngularProfileDetail,
|
|
319
|
+
setAngularProfileDetail,
|
|
320
|
+
} from './diagnostics';
|
|
321
|
+
export type { IAngularProfile, IAngularProfileDetail } from './diagnostics';
|
|
@@ -53,8 +53,10 @@ import {
|
|
|
53
53
|
IMAGE_INPUTS,
|
|
54
54
|
IMAGE_OUTPUTS,
|
|
55
55
|
ImageBase,
|
|
56
|
+
isImageEventCallback,
|
|
56
57
|
resolveImageProps,
|
|
57
58
|
} from '../../components/image/shared';
|
|
59
|
+
import type { IGatedAccessibilityEvent } from '../../gate-demand';
|
|
58
60
|
import { SectionList } from '../../components/section-list';
|
|
59
61
|
import { AnimatedLeafBinder } from './animated-leaf-binder';
|
|
60
62
|
|
|
@@ -69,7 +71,11 @@ const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
|
|
|
69
71
|
// plus static host props to forward (the React/Vue `rest`). Each concrete @Component re-lists
|
|
70
72
|
// these (Angular's inputs-on-base convention, mirroring Switch/Image).
|
|
71
73
|
export const ANIMATED_INPUTS = ['style', 'animatedProps', PASSTHROUGH_PROP];
|
|
72
|
-
export const ANIMATED_IMAGE_INPUTS = [
|
|
74
|
+
export const ANIMATED_IMAGE_INPUTS = [
|
|
75
|
+
...IMAGE_INPUTS,
|
|
76
|
+
'animatedProps',
|
|
77
|
+
PASSTHROUGH_PROP,
|
|
78
|
+
];
|
|
73
79
|
|
|
74
80
|
// The shared lifecycle for every animated wrapper. Concrete subclasses add ONLY a @Component
|
|
75
81
|
// decorator (selector + the host-primitive template) — no behavior. Plain fields (no @Input
|
|
@@ -92,7 +98,8 @@ export abstract class AnimatedComponentBase
|
|
|
92
98
|
|
|
93
99
|
// The directive sitting on the inner host primitive; its `node` is the committed SymbioteNode
|
|
94
100
|
// the leaf binds to. Inherited by the decorated subclass (Angular collects base-class queries).
|
|
95
|
-
@ViewChild(SymbioteHostPropsDirective)
|
|
101
|
+
@ViewChild(SymbioteHostPropsDirective)
|
|
102
|
+
private hostDirective?: SymbioteHostPropsDirective;
|
|
96
103
|
|
|
97
104
|
// The leaf-lifecycle orchestration (build/attach/swap/bind/detach an AnimatedProps leaf) is a
|
|
98
105
|
// Pure Fabrication shared with AnimatedImage, which cannot extend this class (it must extend
|
|
@@ -110,10 +117,14 @@ export abstract class AnimatedComponentBase
|
|
|
110
117
|
// array, which the commit layer flattens) so the ShadowTree carries the current transform.
|
|
111
118
|
get reducedProps(): Record<string, unknown> {
|
|
112
119
|
const reduced = reduceProps(this.mergedProps());
|
|
113
|
-
const passthroughStyle = readPassthroughStyle(
|
|
120
|
+
const passthroughStyle = readPassthroughStyle(
|
|
121
|
+
this.passthroughAnimatedPropExplicitValues,
|
|
122
|
+
);
|
|
114
123
|
if (passthroughStyle !== undefined) {
|
|
115
124
|
reduced['style'] =
|
|
116
|
-
reduced['style'] === undefined
|
|
125
|
+
reduced['style'] === undefined
|
|
126
|
+
? passthroughStyle
|
|
127
|
+
: [reduced['style'], passthroughStyle];
|
|
117
128
|
}
|
|
118
129
|
reduced['style'] = [this.anchorStyle(), reduced['style']];
|
|
119
130
|
return reduced;
|
|
@@ -162,7 +173,8 @@ export abstract class AnimatedComponentBase
|
|
|
162
173
|
const hasPassthroughAnimatedValues =
|
|
163
174
|
this.passthroughAnimatedPropExplicitValues !== null &&
|
|
164
175
|
this.passthroughAnimatedPropExplicitValues !== undefined;
|
|
165
|
-
const wantsNative =
|
|
176
|
+
const wantsNative =
|
|
177
|
+
hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
|
|
166
178
|
this.leafBinder.reconcile(props, wantsNative);
|
|
167
179
|
}
|
|
168
180
|
|
|
@@ -171,7 +183,9 @@ export abstract class AnimatedComponentBase
|
|
|
171
183
|
// never disagree.
|
|
172
184
|
private mergedProps(): Record<string, unknown> {
|
|
173
185
|
const base = this.animatedProps ?? {};
|
|
174
|
-
return this.style === undefined
|
|
186
|
+
return this.style === undefined
|
|
187
|
+
? { ...base }
|
|
188
|
+
: { ...base, style: this.style };
|
|
175
189
|
}
|
|
176
190
|
|
|
177
191
|
// The committed host node held by IDENTITY. resolveHostNode unwraps an imperative scroll
|
|
@@ -186,7 +200,9 @@ export abstract class AnimatedComponentBase
|
|
|
186
200
|
@Component({
|
|
187
201
|
selector: 'AnimatedView, symbiote-animated-view',
|
|
188
202
|
standalone: true,
|
|
189
|
-
hostDirectives: [
|
|
203
|
+
hostDirectives: [
|
|
204
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
205
|
+
],
|
|
190
206
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
191
207
|
imports: [SymbioteHostPropsDirective, ViewHost],
|
|
192
208
|
inputs: ANIMATED_INPUTS,
|
|
@@ -202,7 +218,9 @@ export class AnimatedView extends AnimatedComponentBase {}
|
|
|
202
218
|
@Component({
|
|
203
219
|
selector: 'AnimatedText, symbiote-animated-text',
|
|
204
220
|
standalone: true,
|
|
205
|
-
hostDirectives: [
|
|
221
|
+
hostDirectives: [
|
|
222
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
223
|
+
],
|
|
206
224
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
207
225
|
imports: [SymbioteHostPropsDirective, TextHost],
|
|
208
226
|
inputs: ANIMATED_INPUTS,
|
|
@@ -218,7 +236,9 @@ export class AnimatedText extends AnimatedComponentBase {}
|
|
|
218
236
|
@Component({
|
|
219
237
|
selector: 'AnimatedImage, symbiote-animated-image',
|
|
220
238
|
standalone: true,
|
|
221
|
-
hostDirectives: [
|
|
239
|
+
hostDirectives: [
|
|
240
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
241
|
+
],
|
|
222
242
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
223
243
|
imports: [SymbioteHostPropsDirective, ImageHost],
|
|
224
244
|
inputs: ANIMATED_IMAGE_INPUTS,
|
|
@@ -226,10 +246,6 @@ export class AnimatedText extends AnimatedComponentBase {}
|
|
|
226
246
|
template: `
|
|
227
247
|
<symbiote-image
|
|
228
248
|
[symbioteHostProps]="animatedImageProps"
|
|
229
|
-
(accessibilityAction)="handleAccessibilityAction($event)"
|
|
230
|
-
(accessibilityTap)="handleAccessibilityTap($event)"
|
|
231
|
-
(magicTap)="handleMagicTap($event)"
|
|
232
|
-
(accessibilityEscape)="handleAccessibilityEscape($event)"
|
|
233
249
|
(loadStart)="handleLoadStart($event)"
|
|
234
250
|
(load)="handleLoad($event)"
|
|
235
251
|
(loadEnd)="handleLoadEnd($event)"
|
|
@@ -242,7 +258,10 @@ export class AnimatedText extends AnimatedComponentBase {}
|
|
|
242
258
|
`,
|
|
243
259
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
244
260
|
})
|
|
245
|
-
export class AnimatedImage
|
|
261
|
+
export class AnimatedImage
|
|
262
|
+
extends ImageBase
|
|
263
|
+
implements AfterViewInit, OnChanges, OnDestroy
|
|
264
|
+
{
|
|
246
265
|
animatedProps: Record<string, unknown> | undefined;
|
|
247
266
|
passthroughAnimatedPropExplicitValues: unknown;
|
|
248
267
|
|
|
@@ -250,7 +269,8 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
|
|
|
250
269
|
// comment on AnimatedComponentBase.elementRef.
|
|
251
270
|
private readonly elementRef = inject(ElementRef);
|
|
252
271
|
|
|
253
|
-
@ViewChild(SymbioteHostPropsDirective)
|
|
272
|
+
@ViewChild(SymbioteHostPropsDirective)
|
|
273
|
+
private hostDirective?: SymbioteHostPropsDirective;
|
|
254
274
|
|
|
255
275
|
// Same Pure Fabrication AnimatedComponentBase holds — see its matching comment. AnimatedImage
|
|
256
276
|
// can't extend AnimatedComponentBase (it must extend ImageBase, see the file header), so it
|
|
@@ -261,15 +281,40 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
|
|
|
261
281
|
);
|
|
262
282
|
private viewReady = false;
|
|
263
283
|
|
|
284
|
+
private gateInto(
|
|
285
|
+
props: Record<string, unknown>,
|
|
286
|
+
name: IGatedAccessibilityEvent,
|
|
287
|
+
key: string,
|
|
288
|
+
): void {
|
|
289
|
+
const carried = props[key];
|
|
290
|
+
props[key] = this.gatedAccessibilityHandler(
|
|
291
|
+
name,
|
|
292
|
+
isImageEventCallback(carried) ? carried : undefined,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
264
296
|
get animatedImageProps(): Record<string, unknown> {
|
|
265
297
|
const reduced = reduceProps(this.mergedProps());
|
|
266
|
-
const passthroughStyle = readPassthroughStyle(
|
|
298
|
+
const passthroughStyle = readPassthroughStyle(
|
|
299
|
+
this.passthroughAnimatedPropExplicitValues,
|
|
300
|
+
);
|
|
267
301
|
if (passthroughStyle !== undefined) {
|
|
268
302
|
reduced['style'] =
|
|
269
|
-
reduced['style'] === undefined
|
|
303
|
+
reduced['style'] === undefined
|
|
304
|
+
? passthroughStyle
|
|
305
|
+
: [reduced['style'], passthroughStyle];
|
|
270
306
|
}
|
|
271
307
|
const resolved = resolveImageProps(reduced);
|
|
272
308
|
resolved['style'] = [anchorHostStyle(this.elementRef), resolved['style']];
|
|
309
|
+
// The four gated accessibility events, which used to be template bindings on the host tag and
|
|
310
|
+
// therefore lit their Fabric flags on every AnimatedImage in the app
|
|
311
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`). Routed through the base's gate instead, so
|
|
312
|
+
// both channels still reach the app — the `onX` callback carried in the animated prop bag, and
|
|
313
|
+
// the `@Output()` an app may bind on <AnimatedImage> (IMAGE_OUTPUTS declares all four).
|
|
314
|
+
this.gateInto(resolved, 'accessibilityAction', 'onAccessibilityAction');
|
|
315
|
+
this.gateInto(resolved, 'accessibilityTap', 'onAccessibilityTap');
|
|
316
|
+
this.gateInto(resolved, 'magicTap', 'onMagicTap');
|
|
317
|
+
this.gateInto(resolved, 'accessibilityEscape', 'onAccessibilityEscape');
|
|
273
318
|
return resolved;
|
|
274
319
|
}
|
|
275
320
|
|
|
@@ -311,7 +356,8 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
|
|
|
311
356
|
const hasPassthroughAnimatedValues =
|
|
312
357
|
this.passthroughAnimatedPropExplicitValues !== null &&
|
|
313
358
|
this.passthroughAnimatedPropExplicitValues !== undefined;
|
|
314
|
-
const wantsNative =
|
|
359
|
+
const wantsNative =
|
|
360
|
+
hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
|
|
315
361
|
this.leafBinder.reconcile(props, wantsNative);
|
|
316
362
|
}
|
|
317
363
|
|
|
@@ -332,7 +378,9 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
|
|
|
332
378
|
@Component({
|
|
333
379
|
selector: 'AnimatedScrollView, symbiote-animated-scroll-view',
|
|
334
380
|
standalone: true,
|
|
335
|
-
hostDirectives: [
|
|
381
|
+
hostDirectives: [
|
|
382
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
383
|
+
],
|
|
336
384
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
337
385
|
imports: [SymbioteHostPropsDirective, ScrollViewHost],
|
|
338
386
|
inputs: ANIMATED_INPUTS,
|
|
@@ -23,7 +23,9 @@ export type IComponentProvider = () => Type<unknown>;
|
|
|
23
23
|
// a pre-authored standalone @Component whose template renders `<ng-content>` — the Angular idiom
|
|
24
24
|
// for "render my children" (mount() projects the root's host node into it, the twin of React's
|
|
25
25
|
// createElement(Wrapper, null, rootElement)).
|
|
26
|
-
export type IWrapperComponentProvider = (
|
|
26
|
+
export type IWrapperComponentProvider = (
|
|
27
|
+
appParameters: IAppParameters,
|
|
28
|
+
) => Type<unknown>;
|
|
27
29
|
|
|
28
30
|
function runnableFor(
|
|
29
31
|
componentProvider: IComponentProvider,
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
// ngOnChanges on mount + every prop change. Imperative statics are attached to the component
|
|
6
6
|
// object the same way RN does.
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Component,
|
|
10
|
+
Input,
|
|
11
|
+
type OnChanges,
|
|
12
|
+
type SimpleChanges,
|
|
13
|
+
} from '@angular/core';
|
|
9
14
|
import {
|
|
10
15
|
applyStatusBarProps,
|
|
11
16
|
statusBarImperative,
|
|
@@ -46,12 +51,16 @@ class StatusBarComponent implements OnChanges {
|
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
const StatusBarWithStatics = Object.assign(
|
|
54
|
+
const StatusBarWithStatics = Object.assign(
|
|
55
|
+
StatusBarComponent,
|
|
56
|
+
statusBarImperative,
|
|
57
|
+
);
|
|
50
58
|
|
|
51
59
|
Object.defineProperty(StatusBarWithStatics, 'currentHeight', {
|
|
52
60
|
get: statusBarCurrentHeight,
|
|
53
61
|
enumerable: true,
|
|
54
62
|
});
|
|
55
63
|
|
|
56
|
-
export const StatusBar: typeof StatusBarWithStatics & {
|
|
57
|
-
|
|
64
|
+
export const StatusBar: typeof StatusBarWithStatics & {
|
|
65
|
+
readonly currentHeight?: number;
|
|
66
|
+
} = StatusBarWithStatics;
|