@symbiote-native/angular 0.6.2 → 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/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 +9 -7
- 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/modules/animated/animated-leaf-binder/index.ts +0 -101
|
@@ -1,101 +1,52 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// (
|
|
9
|
-
//
|
|
1
|
+
// Angular's binding of the shared AnimatedProps leaf lifecycle (core/engine's
|
|
2
|
+
// createAnimatedLeafLifecycle). The POLICY - when to rebuild, when to skip, attach-new-before-
|
|
3
|
+
// detach-old, native event rebinding - lives in the engine and is identical for every adapter;
|
|
4
|
+
// this class supplies only the two things that are genuinely Angular's: how the host node is
|
|
5
|
+
// resolved (a @ViewChild directive, not a ref), and the whenCommitted deferral its batched
|
|
6
|
+
// change detection forces.
|
|
7
|
+
//
|
|
8
|
+
// Pure Fabrication (GRASP): kept as its own class rather than folded into AnimatedComponentBase so
|
|
9
|
+
// AnimatedImage - which must extend ImageBase instead - gets the same wiring by composition rather
|
|
10
|
+
// than copy-paste.
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
-
attachNativeEventHandler,
|
|
13
|
-
dlog,
|
|
12
|
+
createAnimatedLeafLifecycle,
|
|
14
13
|
whenCommitted,
|
|
14
|
+
type IAnimatedLeafLifecycle,
|
|
15
15
|
type ISymbioteNode,
|
|
16
16
|
} from '@symbiote-native/engine';
|
|
17
17
|
|
|
18
18
|
export class AnimatedLeafBinder {
|
|
19
|
-
|
|
20
|
-
private attached: AnimatedProps | null = null;
|
|
21
|
-
// The whenCommitted waiter for the current leaf's node binding, cancelled before the next.
|
|
22
|
-
private cancelBind: (() => void) | undefined;
|
|
23
|
-
// Detachers for any Animated.event prop bound natively to the committed node (JS path: none).
|
|
24
|
-
private eventDetachers: Array<() => void> = [];
|
|
19
|
+
private readonly lifecycle: IAnimatedLeafLifecycle;
|
|
25
20
|
|
|
26
21
|
// `resolveNode` stays a caller-supplied resolver (not a constructor-captured node) so this
|
|
27
|
-
// class never needs to know HOW the host node is found
|
|
22
|
+
// class never needs to know HOW the host node is found - AnimatedComponentBase and
|
|
28
23
|
// AnimatedImage both resolve it the same way (resolveHostNode + isSymbioteNode over their
|
|
29
24
|
// own @ViewChild), but that's Angular DI state this binder has no business touching.
|
|
30
25
|
// `label` is purely diagnostic (dlog), mirroring the constructor.name the pre-extraction
|
|
31
26
|
// dlog call used.
|
|
32
27
|
constructor(
|
|
33
28
|
private readonly resolveNode: () => ISymbioteNode | null,
|
|
34
|
-
|
|
35
|
-
) {
|
|
36
|
-
|
|
37
|
-
// Build a fresh AnimatedProps leaf from the current props (the React/Vue per-render
|
|
38
|
-
// useMemo(rest)), wire it into the graph, and swap the previous one out. Attach the NEW leaf
|
|
39
|
-
// BEFORE detaching the OLD one: a shared Value self-detaches (dropping its native node) the
|
|
40
|
-
// instant its child count hits zero, so detaching first would kill a running native animation
|
|
41
|
-
// on any unrelated re-render (mirrors RN's AnimatedComponent._attachProps). Then bind the
|
|
42
|
-
// committed node, go native if wanted, and rebind native events.
|
|
43
|
-
reconcile(props: Record<string, unknown>, wantsNative: boolean): void {
|
|
44
|
-
dlog(`${this.label} reconcile`);
|
|
45
|
-
const newLeaf = new AnimatedProps(props);
|
|
46
|
-
newLeaf.__attach();
|
|
47
|
-
if (this.attached !== null && this.attached !== newLeaf) this.attached.__detach();
|
|
48
|
-
this.attached = newLeaf;
|
|
49
|
-
|
|
50
|
-
this.bindNode(newLeaf, props, wantsNative);
|
|
29
|
+
label: string,
|
|
30
|
+
) {
|
|
31
|
+
this.lifecycle = createAnimatedLeafLifecycle(label);
|
|
51
32
|
}
|
|
52
33
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
wantsNative: boolean,
|
|
61
|
-
): void {
|
|
62
|
-
this.cancelBind?.();
|
|
63
|
-
this.cancelBind = undefined;
|
|
34
|
+
// Only the NATIVE half is deferred through whenCommitted (which runs it NOW when the node is
|
|
35
|
+
// already committed - the steady state, and the only path a scroll frame takes). The leaf itself
|
|
36
|
+
// always joins the value graph synchronously: deferring THAT too put the build behind a
|
|
37
|
+
// canceller the next reconcile drops, so a component reconciling faster than it commits never
|
|
38
|
+
// attached anything and a sticky header's rebuilt interpolation never reached the graph.
|
|
39
|
+
// Regression-covered in leaf-attach-before-commit.test.ts.
|
|
40
|
+
reconcile(props: Record<string, unknown>, wantsNative: boolean): void {
|
|
64
41
|
const node = this.resolveNode();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (wantsNative) leaf.__makeNative();
|
|
69
|
-
this.attachEvents(node, props);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Native-attach any Animated.event prop (e.g. onScroll={Animated.event(…,{useNativeDriver:true})})
|
|
74
|
-
// to the committed node. attachNativeEventHandler no-ops (returns undefined) unless the prop is a
|
|
75
|
-
// native event handler with a committed tag, so the JS path stays the fallback. Rebound each
|
|
76
|
-
// reconcile so a new inline event re-attaches; detached first to avoid leaking the prior binding.
|
|
77
|
-
private attachEvents(node: ISymbioteNode, props: Record<string, unknown>): void {
|
|
78
|
-
this.detachEvents();
|
|
79
|
-
for (const key of Object.keys(props)) {
|
|
80
|
-
const attachment = attachNativeEventHandler(node, key, props[key]);
|
|
81
|
-
if (attachment !== undefined) this.eventDetachers.push(attachment.detach);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private detachEvents(): void {
|
|
86
|
-
for (const detach of this.eventDetachers) detach();
|
|
87
|
-
this.eventDetachers = [];
|
|
42
|
+
this.lifecycle.reconcile(props, node, wantsNative, bind =>
|
|
43
|
+
node === null ? undefined : whenCommitted(node, bind),
|
|
44
|
+
);
|
|
88
45
|
}
|
|
89
46
|
|
|
90
47
|
// Tear down everything: cancel a still-pending bind, drop native event listeners, detach the
|
|
91
48
|
// current leaf from the value graph. Safe to call even if reconcile() was never called.
|
|
92
49
|
destroy(): void {
|
|
93
|
-
this.
|
|
94
|
-
this.cancelBind = undefined;
|
|
95
|
-
this.detachEvents();
|
|
96
|
-
if (this.attached !== null) {
|
|
97
|
-
this.attached.__detach();
|
|
98
|
-
this.attached = null;
|
|
99
|
-
}
|
|
50
|
+
this.lifecycle.teardown();
|
|
100
51
|
}
|
|
101
52
|
}
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
// createAnimatedComponent for @symbiote-native/angular
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// values for the first paint, bind the leaf to the committed host node, swap leaves on
|
|
11
|
-
// re-render, tear down on destroy) lives once in AnimatedComponentBase; only the per-host
|
|
12
|
-
// template differs.
|
|
1
|
+
// createAnimatedComponent for @symbiote-native/angular. React/Vue have a runtime HOC
|
|
2
|
+
// (`createAnimatedComponent(Component)` builds a fresh wrapped component on the fly); Angular
|
|
3
|
+
// can't — the AOT-under-Metro build compiles every @Component ahead of time and ships no JIT
|
|
4
|
+
// compiler to Hermes, so a runtime `Component({...})(class)` would throw. The AOT-safe
|
|
5
|
+
// equivalent is an EXPLICIT set of standalone components — AnimatedView / AnimatedText /
|
|
6
|
+
// AnimatedImage / AnimatedScrollView — each a thin @Component over the matching host primitive,
|
|
7
|
+
// sharing one abstract lifecycle base. The mechanism (build an AnimatedProps leaf, reduce
|
|
8
|
+
// animated props to current values for the first paint, bind the leaf to the committed host
|
|
9
|
+
// node, swap leaves on re-render, tear down on destroy) lives once in AnimatedComponentBase.
|
|
13
10
|
//
|
|
14
11
|
// The framework-agnostic half (reduceProps / readPassthroughStyle / resolveHostNode /
|
|
15
12
|
// AnimatedProps / attachNativeEventHandler / isNativeAnimatedAvailable) comes from
|
|
16
|
-
// @symbiote-native/engine, shared
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// setNativeProps(node), exactly as on the other adapters.
|
|
13
|
+
// @symbiote-native/engine, shared with React and Vue. The per-frame path NEVER goes through
|
|
14
|
+
// Angular change detection: value.setValue / animation -> flushValue -> AnimatedProps.update()
|
|
15
|
+
// -> setNativeProps(node), same as the other adapters.
|
|
20
16
|
|
|
21
17
|
import {
|
|
22
18
|
ChangeDetectionStrategy,
|
|
@@ -25,8 +21,10 @@ import {
|
|
|
25
21
|
Directive,
|
|
26
22
|
ElementRef,
|
|
27
23
|
inject,
|
|
24
|
+
signal,
|
|
28
25
|
ViewChild,
|
|
29
26
|
type AfterViewInit,
|
|
27
|
+
type DoCheck,
|
|
30
28
|
type OnChanges,
|
|
31
29
|
type OnDestroy,
|
|
32
30
|
type Type,
|
|
@@ -43,11 +41,12 @@ import { selectScrollIntrinsics } from '@symbiote-native/components';
|
|
|
43
41
|
import { Image, ScrollView, Text, View } from '../../components';
|
|
44
42
|
import {
|
|
45
43
|
anchorHostStyle,
|
|
46
|
-
ViewHost,
|
|
47
|
-
TextHost,
|
|
48
44
|
ImageHost,
|
|
49
45
|
ScrollViewHost,
|
|
50
46
|
SymbioteHostPropsDirective,
|
|
47
|
+
SymbioteStyleInputDirective,
|
|
48
|
+
TextHost,
|
|
49
|
+
ViewHost,
|
|
51
50
|
} from '../../primitives';
|
|
52
51
|
import { FlatList } from '../../components/flat-list';
|
|
53
52
|
import {
|
|
@@ -67,9 +66,8 @@ const PASSTHROUGH_PROP = 'passthroughAnimatedPropExplicitValues';
|
|
|
67
66
|
// The inputs every animated wrapper accepts. `style` is the (possibly animated) style — the
|
|
68
67
|
// common path (TouchableOpacity passes `[style, { opacity }]`). `animatedProps` is the generic
|
|
69
68
|
// escape hatch for any OTHER prop that may hold an AnimatedNode or an Animated.event handler,
|
|
70
|
-
// plus static host props to forward (the React/Vue `rest`).
|
|
71
|
-
//
|
|
72
|
-
// Switch/Image), so they are inherited consistently.
|
|
69
|
+
// plus static host props to forward (the React/Vue `rest`). Each concrete @Component re-lists
|
|
70
|
+
// these (Angular's inputs-on-base convention, mirroring Switch/Image).
|
|
73
71
|
export const ANIMATED_INPUTS = ['style', 'animatedProps', PASSTHROUGH_PROP];
|
|
74
72
|
export const ANIMATED_IMAGE_INPUTS = [...IMAGE_INPUTS, 'animatedProps', PASSTHROUGH_PROP];
|
|
75
73
|
|
|
@@ -79,7 +77,9 @@ export const ANIMATED_IMAGE_INPUTS = [...IMAGE_INPUTS, 'animatedProps', PASSTHRO
|
|
|
79
77
|
// @Directive() (no selector) is the Angular-sanctioned decorator for an abstract base that
|
|
80
78
|
// declares lifecycle hooks — without it ngtsc rejects the inherited hooks (NG2007).
|
|
81
79
|
@Directive()
|
|
82
|
-
export abstract class AnimatedComponentBase
|
|
80
|
+
export abstract class AnimatedComponentBase
|
|
81
|
+
implements AfterViewInit, DoCheck, OnChanges, OnDestroy
|
|
82
|
+
{
|
|
83
83
|
style: unknown;
|
|
84
84
|
animatedProps: Record<string, unknown> | undefined;
|
|
85
85
|
passthroughAnimatedPropExplicitValues: unknown;
|
|
@@ -115,10 +115,29 @@ export abstract class AnimatedComponentBase implements AfterViewInit, OnChanges,
|
|
|
115
115
|
reduced['style'] =
|
|
116
116
|
reduced['style'] === undefined ? passthroughStyle : [reduced['style'], passthroughStyle];
|
|
117
117
|
}
|
|
118
|
-
reduced['style'] = [
|
|
118
|
+
reduced['style'] = [this.anchorStyle(), reduced['style']];
|
|
119
119
|
return reduced;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// The anchor's class-derived style, polled rather than read live inside `reducedProps`.
|
|
123
|
+
//
|
|
124
|
+
// Two different writers land style on this component's anchor and NEITHER goes through
|
|
125
|
+
// ngOnChanges: `class=` / `[class.x]` / `[ngClass]` at the use site (via the renderer's
|
|
126
|
+
// addClass/removeClass -> commitClassStyle), and a `[style]` binding from a composing parent
|
|
127
|
+
// that Angular writes as a host property because `style` is not declared as an @Input here.
|
|
128
|
+
// Reading them live in the getter worked at CREATION only - nothing dirties THIS view when the
|
|
129
|
+
// anchor changes, so the getter was never re-read and the update was lost. Device-visible as a
|
|
130
|
+
// TouchableOpacity whose `class` toggled after mount never repainted.
|
|
131
|
+
//
|
|
132
|
+
// ngDoCheck runs during the PARENT's refresh even when this view is skipped, and the signal
|
|
133
|
+
// write is what then marks this view for refresh. `signal.set`'s own Object.is makes an
|
|
134
|
+
// unchanged poll a no-op, so a steady screen costs one property read per pass and nothing else.
|
|
135
|
+
private readonly anchorStyle = signal<unknown>(undefined);
|
|
136
|
+
|
|
137
|
+
ngDoCheck(): void {
|
|
138
|
+
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
139
|
+
}
|
|
140
|
+
|
|
122
141
|
ngAfterViewInit(): void {
|
|
123
142
|
this.viewReady = true;
|
|
124
143
|
this.reconcile();
|
|
@@ -167,6 +186,7 @@ export abstract class AnimatedComponentBase implements AfterViewInit, OnChanges,
|
|
|
167
186
|
@Component({
|
|
168
187
|
selector: 'AnimatedView, symbiote-animated-view',
|
|
169
188
|
standalone: true,
|
|
189
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
170
190
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
171
191
|
imports: [SymbioteHostPropsDirective, ViewHost],
|
|
172
192
|
inputs: ANIMATED_INPUTS,
|
|
@@ -182,6 +202,7 @@ export class AnimatedView extends AnimatedComponentBase {}
|
|
|
182
202
|
@Component({
|
|
183
203
|
selector: 'AnimatedText, symbiote-animated-text',
|
|
184
204
|
standalone: true,
|
|
205
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
185
206
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
186
207
|
imports: [SymbioteHostPropsDirective, TextHost],
|
|
187
208
|
inputs: ANIMATED_INPUTS,
|
|
@@ -197,6 +218,7 @@ export class AnimatedText extends AnimatedComponentBase {}
|
|
|
197
218
|
@Component({
|
|
198
219
|
selector: 'AnimatedImage, symbiote-animated-image',
|
|
199
220
|
standalone: true,
|
|
221
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
200
222
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
201
223
|
imports: [SymbioteHostPropsDirective, ImageHost],
|
|
202
224
|
inputs: ANIMATED_IMAGE_INPUTS,
|
|
@@ -251,6 +273,25 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
|
|
|
251
273
|
return resolved;
|
|
252
274
|
}
|
|
253
275
|
|
|
276
|
+
// The anchor's class-derived style, polled rather than read live inside `reducedProps`.
|
|
277
|
+
//
|
|
278
|
+
// Two different writers land style on this component's anchor and NEITHER goes through
|
|
279
|
+
// ngOnChanges: `class=` / `[class.x]` / `[ngClass]` at the use site (via the renderer's
|
|
280
|
+
// addClass/removeClass -> commitClassStyle), and a `[style]` binding from a composing parent
|
|
281
|
+
// that Angular writes as a host property because `style` is not declared as an @Input here.
|
|
282
|
+
// Reading them live in the getter worked at CREATION only - nothing dirties THIS view when the
|
|
283
|
+
// anchor changes, so the getter was never re-read and the update was lost. Device-visible as a
|
|
284
|
+
// TouchableOpacity whose `class` toggled after mount never repainted.
|
|
285
|
+
//
|
|
286
|
+
// ngDoCheck runs during the PARENT's refresh even when this view is skipped, and the signal
|
|
287
|
+
// write is what then marks this view for refresh. `signal.set`'s own Object.is makes an
|
|
288
|
+
// unchanged poll a no-op, so a steady screen costs one property read per pass and nothing else.
|
|
289
|
+
private readonly anchorStyle = signal<unknown>(undefined);
|
|
290
|
+
|
|
291
|
+
ngDoCheck(): void {
|
|
292
|
+
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
293
|
+
}
|
|
294
|
+
|
|
254
295
|
ngAfterViewInit(): void {
|
|
255
296
|
this.viewReady = true;
|
|
256
297
|
this.reconcile();
|
|
@@ -291,6 +332,7 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
|
|
|
291
332
|
@Component({
|
|
292
333
|
selector: 'AnimatedScrollView, symbiote-animated-scroll-view',
|
|
293
334
|
standalone: true,
|
|
335
|
+
hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }],
|
|
294
336
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
295
337
|
imports: [SymbioteHostPropsDirective, ScrollViewHost],
|
|
296
338
|
inputs: ANIMATED_INPUTS,
|
|
@@ -305,20 +347,17 @@ export class AnimatedImage extends ImageBase implements AfterViewInit, OnChanges
|
|
|
305
347
|
})
|
|
306
348
|
export class AnimatedScrollView extends AnimatedComponentBase {
|
|
307
349
|
// RN defaults nested scrolling ON (ScrollView.js `nestedScrollEnabled ?? true`) — Android
|
|
308
|
-
// needs it explicit or a ScrollView
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
350
|
+
// needs it explicit or a nested ScrollView never receives touch (swallowed by the outer one;
|
|
351
|
+
// iOS doesn't gate on this). The real ScrollView component already defaults this
|
|
352
|
+
// (scroll-view/shared.ts), but AnimatedScrollView talks to the raw primitive directly and
|
|
353
|
+
// must apply it too.
|
|
312
354
|
//
|
|
313
|
-
// scrollViewBaseStyle (overflow: 'scroll' + the per-axis flexDirection) is the
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
// (symbiote-scroll-view/symbiote-scroll-content, not the horizontal variants), so
|
|
320
|
-
// selectScrollIntrinsics is always called with isHorizontal=false here — matching what the
|
|
321
|
-
// template can actually render.
|
|
355
|
+
// scrollViewBaseStyle (overflow: 'scroll' + the per-axis flexDirection) is the other thing the
|
|
356
|
+
// real ScrollView applies that this bespoke template used to skip — without it, iOS Fabric
|
|
357
|
+
// never clips content to the scroll view's frame (invisible on Android, whose native ViewGroup
|
|
358
|
+
// clips regardless of style). This wrapper has no `horizontal` input and its template is
|
|
359
|
+
// hardcoded to the vertical intrinsics, so selectScrollIntrinsics is always called with
|
|
360
|
+
// isHorizontal=false here, matching what the template can actually render.
|
|
322
361
|
override get reducedProps(): Record<string, unknown> {
|
|
323
362
|
const reduced = super.reducedProps;
|
|
324
363
|
const { scrollViewBaseStyle } = selectScrollIntrinsics(false, undefined);
|
|
@@ -15,16 +15,14 @@ import {
|
|
|
15
15
|
} from '@symbiote-native/engine';
|
|
16
16
|
import { mount } from '../../render';
|
|
17
17
|
|
|
18
|
-
// RN's IComponentProvider
|
|
19
|
-
// graph stays cheap until the app actually runs).
|
|
18
|
+
// RN's IComponentProvider. Lazy so the module graph stays cheap until the app actually runs.
|
|
20
19
|
export type IComponentProvider = () => Type<unknown>;
|
|
21
20
|
|
|
22
|
-
// RN's IWrapperComponentProvider
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// host node into it, the twin of React's createElement(Wrapper, null, rootElement)).
|
|
21
|
+
// RN's IWrapperComponentProvider (e.g. a context provider). Angular has no runtime template
|
|
22
|
+
// synthesis (no JIT under AOT/Metro — see create-animated-component.ts), so the wrapper must be
|
|
23
|
+
// a pre-authored standalone @Component whose template renders `<ng-content>` — the Angular idiom
|
|
24
|
+
// for "render my children" (mount() projects the root's host node into it, the twin of React's
|
|
25
|
+
// createElement(Wrapper, null, rootElement)).
|
|
28
26
|
export type IWrapperComponentProvider = (appParameters: IAppParameters) => Type<unknown>;
|
|
29
27
|
|
|
30
28
|
function runnableFor(
|
package/src/primitives/index.ts
CHANGED
package/src/primitives/shared.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChangeDetectorRef,
|
|
3
3
|
Directive,
|
|
4
|
+
type DoCheck,
|
|
4
5
|
ElementRef,
|
|
5
6
|
inject,
|
|
6
7
|
Input,
|
|
@@ -101,6 +102,85 @@ export function stableAnchorStyle(
|
|
|
101
102
|
|
|
102
103
|
const ON_PREFIX = /^on[A-Z]/;
|
|
103
104
|
|
|
105
|
+
// The one `onX` that fires per FRAME rather than per gesture, and with sticky headers RN pins
|
|
106
|
+
// `scrollEventThrottle` to 1 (ScrollView.js), so 60 times a second. Since markForCheck walks
|
|
107
|
+
// RefreshView|Dirty to the ROOT, wrapping it cost one full ancestor-screen template execution per
|
|
108
|
+
// frame - the canary's ~37fps scroll (components/virtualized-list/scroll-cost.test.ts).
|
|
109
|
+
//
|
|
110
|
+
// Nothing lost its refresh: VirtualizedList marks itself when the window moves, sticky rides the
|
|
111
|
+
// native driver, and a caller's own handler is typically an Animated.event touching no Angular
|
|
112
|
+
// state. Drag/momentum begin/end stay wrapped - once per gesture is not a hot path.
|
|
113
|
+
const PER_FRAME_CALLBACK = 'onScroll';
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Restores the OnPush dirty-marking Angular skips for a `[style]` binding that a directive
|
|
117
|
+
* declares as an input. Attach it to any component declaring a `style` input:
|
|
118
|
+
* `hostDirectives: [{ directive: SymbioteStyleInputDirective, inputs: ['style'] }]`.
|
|
119
|
+
*
|
|
120
|
+
* An ordinary `[foo]` binding compiles to `ɵɵproperty` -> `setPropertyAndInputs`, whose last act
|
|
121
|
+
* for a component host is `markDirtyIfOnPush` (`render3/instructions/shared.ts`) — the flag
|
|
122
|
+
* `detectChangesInView` needs before it will re-execute a non-CheckAlways view. `[style]` and
|
|
123
|
+
* `[class]` do NOT take that path: they compile to the styling instructions, which on finding a
|
|
124
|
+
* directive input of the same name hand off to `setDirectiveInputsWhichShadowsStyling`
|
|
125
|
+
* (`render3/instructions/property.ts`) — two lines that write the input and stop. No dirty flag.
|
|
126
|
+
* The input lands and `ngOnChanges` fires, but the receiving component's own template, and every
|
|
127
|
+
* getter it reads, is frozen at its creation value. Verified against @angular/core 22.0.8.
|
|
128
|
+
*
|
|
129
|
+
* Symbiote sits squarely on that gap: every composed component declares `style` as a real
|
|
130
|
+
* `@Input()` on purpose, because a raw `[style]` binding would otherwise be decomposed key by key
|
|
131
|
+
* by Angular's CSS style engine, which cannot represent an RN StyleProp array or an Animated value
|
|
132
|
+
* (see `SymbiotePrimitiveHost` above). So the one binding name Angular forgets to mark is the one
|
|
133
|
+
* carrying the whole styling surface.
|
|
134
|
+
*
|
|
135
|
+
* `markForCheck()` here is exactly the skipped `markDirtyIfOnPush`, not a bigger hammer: the write
|
|
136
|
+
* happens inside the parent's refresh, and `markViewDirty` uses the `Dirty` bit alone while views
|
|
137
|
+
* are refreshing (`render3/instructions/mark_view_dirty.ts`), which the ancestors then clear at the
|
|
138
|
+
* end of their own `refreshView`. A component that already bridges its inputs into a signal (the
|
|
139
|
+
* `inputsRevision` + `computed` pattern — Pressable, ScrollView) is immune on its own, since a
|
|
140
|
+
* signal write dirties the template consumer, which `detectChangesInView` honors in any mode.
|
|
141
|
+
*/
|
|
142
|
+
@Directive({
|
|
143
|
+
selector: '[symbioteStyleInput]',
|
|
144
|
+
standalone: true,
|
|
145
|
+
inputs: ['style'],
|
|
146
|
+
})
|
|
147
|
+
export class SymbioteStyleInputDirective implements DoCheck, OnChanges {
|
|
148
|
+
private readonly cdr = inject(ChangeDetectorRef);
|
|
149
|
+
// A host directive's ElementRef is the HOST COMPONENT's element - i.e. exactly the anchor a
|
|
150
|
+
// `class=` at the use site resolves onto.
|
|
151
|
+
private readonly elementRef = inject(ElementRef);
|
|
152
|
+
private lastAnchorStyle: unknown;
|
|
153
|
+
|
|
154
|
+
style?: unknown;
|
|
155
|
+
|
|
156
|
+
ngOnChanges(): void {
|
|
157
|
+
this.cdr.markForCheck();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// The `class` half of the same gap, and it needs a POLL, not a hook: a class at the use site
|
|
161
|
+
// never becomes an input, so there is no SimpleChanges entry to fire on. Making the view refresh
|
|
162
|
+
// is enough - every composed component re-reads the anchor in a getter on its own.
|
|
163
|
+
//
|
|
164
|
+
// ngDoCheck runs during the PARENT's refresh even when this view would be skipped, so the mark
|
|
165
|
+
// lands in the same pass rather than one behind. The dedup gate is load-bearing: marking on every
|
|
166
|
+
// check re-dirties the view every tick and free-runs CD, the hazard `stableAnchorStyle` above
|
|
167
|
+
// prevents one level up.
|
|
168
|
+
ngDoCheck(): void {
|
|
169
|
+
const current = anchorHostStyle(this.elementRef);
|
|
170
|
+
if (isAnchorStyleUnchanged(this.lastAnchorStyle, current)) return;
|
|
171
|
+
this.lastAnchorStyle = current;
|
|
172
|
+
this.cdr.markForCheck();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Reference equality is the normal path (the engine replaces props.style only on a real class
|
|
177
|
+
// change); the shallow compare guards against an equal-but-fresh object, which would turn the poll
|
|
178
|
+
// above into a CD loop.
|
|
179
|
+
function isAnchorStyleUnchanged(previous: unknown, next: unknown): boolean {
|
|
180
|
+
if (Object.is(previous, next)) return true;
|
|
181
|
+
return isRecord(previous) && isRecord(next) && shallowStyleEqual(previous, next);
|
|
182
|
+
}
|
|
183
|
+
|
|
104
184
|
/**
|
|
105
185
|
* Base for all Angular primitive host components (`symbiote-view`, `symbiote-text`, ...).
|
|
106
186
|
* RN's `StyleProp` can be an object, an array, nested arrays, falsy entries, and Animated
|
|
@@ -156,6 +236,17 @@ export class SymbioteHostPropsDirective {
|
|
|
156
236
|
// The directive lives in its HOST component's template, so its injected ChangeDetectorRef IS
|
|
157
237
|
// that component's own view detector — the one thing that can refresh it (see wrapCallback).
|
|
158
238
|
private readonly cdr = inject(ChangeDetectorRef);
|
|
239
|
+
// One wrapper per ORIGINAL handler, for the lifetime of this directive. Without it every push
|
|
240
|
+
// of the props bag allocated a brand-new closure for every `onX` key, and the engine stored it
|
|
241
|
+
// as a new listener (`setEventListener` allocates its own closure on top) - per key, per push,
|
|
242
|
+
// on a path a scroll frame reaches 60 times a second. The wrapper body closes over nothing but
|
|
243
|
+
// `value` and this instance's `cdr`, so the handler alone is a complete cache key; `key` is
|
|
244
|
+
// read only by the ON_PREFIX guard above and never enters the wrapper.
|
|
245
|
+
//
|
|
246
|
+
// Reference stability is the point, not just the allocation: a fresh function on every push
|
|
247
|
+
// defeats every downstream identity check on the bag, so nothing upstream could ever conclude
|
|
248
|
+
// "this bag is unchanged" while a callback prop was in it.
|
|
249
|
+
private readonly wrappers = new WeakMap<object, (...args: unknown[]) => unknown>();
|
|
159
250
|
|
|
160
251
|
get node(): unknown {
|
|
161
252
|
return this.elementRef.nativeElement;
|
|
@@ -180,11 +271,19 @@ export class SymbioteHostPropsDirective {
|
|
|
180
271
|
// for free (setState / proxy reactivity).
|
|
181
272
|
private wrapCallback(key: string, value: unknown): unknown {
|
|
182
273
|
if (!ON_PREFIX.test(key) || typeof value !== 'function') return value;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
274
|
+
if (key === PER_FRAME_CALLBACK) return value;
|
|
275
|
+
const cached = this.wrappers.get(value);
|
|
276
|
+
if (cached !== undefined) return cached;
|
|
277
|
+
// Reflect.apply rather than calling a cast-to-signature local: `typeof value === 'function'`
|
|
278
|
+
// narrows to `Function`, which has no call signature TypeScript will accept, and this repo
|
|
279
|
+
// does not use `as` to paper over that. `undefined` as the receiver preserves the previous
|
|
280
|
+
// unbound call.
|
|
281
|
+
const wrapper = (...args: unknown[]): unknown => {
|
|
282
|
+
const result: unknown = Reflect.apply(value, undefined, args);
|
|
186
283
|
this.cdr.markForCheck();
|
|
187
284
|
return result;
|
|
188
285
|
};
|
|
286
|
+
this.wrappers.set(value, wrapper);
|
|
287
|
+
return wrapper;
|
|
189
288
|
}
|
|
190
289
|
}
|
package/src/render/index.ts
CHANGED
|
@@ -119,43 +119,31 @@ export function mount(
|
|
|
119
119
|
useValue: new SymbioteRendererFactory(surface),
|
|
120
120
|
},
|
|
121
121
|
{ provide: DOCUMENT, useValue: { head: surface, body: surface } },
|
|
122
|
-
// createEnvironmentInjector with a null parent
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// R3Injector's constructor reads that provider and self-tags this.scopes with
|
|
130
|
-
// 'root'. We do the same here — no PlatformRef, no DOM, just this one provider.
|
|
122
|
+
// createEnvironmentInjector with a null parent scopes this injector to {'environment'}
|
|
123
|
+
// only (see EnvironmentNgModuleRefAdapter), so providedIn:'root' tokens — ApplicationRef
|
|
124
|
+
// included — never resolve (R3Injector.get walks up for a `scopes` containing 'root', and
|
|
125
|
+
// a null parent always dead-ends in NullInjector). platform-browser solves this the same
|
|
126
|
+
// way for real DOM apps via BROWSER_MODULE_PROVIDERS: hand { provide: INJECTOR_SCOPE,
|
|
127
|
+
// useValue: 'root' } to the app-level providers, which R3Injector's constructor reads to
|
|
128
|
+
// self-tag this.scopes with 'root'. Same trick here, no PlatformRef, no DOM.
|
|
131
129
|
{ provide: INJECTOR_SCOPE, useValue: 'root' },
|
|
132
|
-
// Supplies the real ChangeDetectionSchedulerImpl (microtask-batched
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// go through, see SymbioteHostPropsDirective) unconditionally sets RefreshView on
|
|
143
|
-
// EVERY ancestor up to the root; that is fundamental, unavoidable Angular zoneless
|
|
144
|
-
// behavior, true in every Angular app, not something this swap changes. What DOES
|
|
145
|
-
// still protect a sibling branch from an unrelated press is a genuine child
|
|
146
|
-
// `@Component` boundary — SignalView-compiled children are skip-eligible regardless of
|
|
147
|
-
// this scheduler.
|
|
130
|
+
// Supplies the real ChangeDetectionSchedulerImpl (microtask-batched via
|
|
131
|
+
// ApplicationRef.afterTick) + NoopNgZone + ZONELESS_ENABLED: true — the exact bundle
|
|
132
|
+
// internalCreateApplication() uses. Replaces the old unconditional
|
|
133
|
+
// `rootView.detectChanges(); cmpView.detectChanges()` (force-ran both root views on every
|
|
134
|
+
// tick) with Angular's own tick(), which only enters a view something actually marked
|
|
135
|
+
// dirty. This does NOT stop the root's own template from re-running on a plain press or
|
|
136
|
+
// `markForCheck()` anywhere in the tree — `markViewDirty` unconditionally sets RefreshView
|
|
137
|
+
// on every ancestor up to the root; that's fundamental Angular zoneless behavior, not
|
|
138
|
+
// something this swap changes. A genuine child `@Component` boundary still protects a
|
|
139
|
+
// sibling branch from an unrelated press.
|
|
148
140
|
...provideZonelessChangeDetectionInternal(),
|
|
149
|
-
// Angular's
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// propagated out of a bare Timeout callback uncaught (nothing above it in the stack to
|
|
156
|
-
// catch it), i.e. any async tick() exception, anywhere in the app, crashed hard instead
|
|
157
|
-
// of being reported. Providing the default `ErrorHandler` (same one bootstrapApplication
|
|
158
|
-
// ships) restores the intended behavior: `console.error('ERROR', e)` and keep running.
|
|
141
|
+
// Angular's INTERNAL_APPLICATION_ERROR_HANDLER reports a tick() exception via
|
|
142
|
+
// `injector.get(ErrorHandler)`; a normal `bootstrapApplication` registers that token by
|
|
143
|
+
// default, but our from-scratch environment injector never did, so the lookup itself threw
|
|
144
|
+
// NG0201 and replaced the real error with "No provider found for ErrorHandler" — any async
|
|
145
|
+
// tick() exception crashed hard, uncaught, instead of being reported. Providing the
|
|
146
|
+
// default `ErrorHandler` restores `console.error('ERROR', e)` and keep running.
|
|
159
147
|
{ provide: ErrorHandler, useClass: ErrorHandler },
|
|
160
148
|
ColorSchemeService,
|
|
161
149
|
WindowDimensionsService,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { type ISymbioteNode } from '@symbiote-native/engine';
|
|
2
|
-
export declare class AnimatedLeafBinder {
|
|
3
|
-
private readonly resolveNode;
|
|
4
|
-
private readonly label;
|
|
5
|
-
private attached;
|
|
6
|
-
private cancelBind;
|
|
7
|
-
private eventDetachers;
|
|
8
|
-
constructor(resolveNode: () => ISymbioteNode | null, label: string);
|
|
9
|
-
reconcile(props: Record<string, unknown>, wantsNative: boolean): void;
|
|
10
|
-
private bindNode;
|
|
11
|
-
private attachEvents;
|
|
12
|
-
private detachEvents;
|
|
13
|
-
destroy(): void;
|
|
14
|
-
}
|