@symbiote-native/angular 0.3.3 → 0.4.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/babel-register-composed.cjs +119 -0
- package/build/angular/components/image/shared.d.ts +4 -4
- package/build/angular/components/image/shared.js +1 -3
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/input-accessory-view.d.ts +1 -1
- package/build/angular/components/input-accessory-view.js +1 -5
- package/build/angular/components/input-accessory-view.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view.js +1 -6
- package/build/angular/components/keyboard-avoiding-view.js.map +1 -1
- package/build/angular/components/modal.js +1 -6
- package/build/angular/components/modal.js.map +1 -1
- package/build/angular/components/pressable/index.js +13 -14
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control.js +1 -6
- package/build/angular/components/refresh-control.js.map +1 -1
- package/build/angular/components/safe-area-view.d.ts +1 -1
- package/build/angular/components/safe-area-view.js +1 -5
- package/build/angular/components/safe-area-view.js.map +1 -1
- package/build/angular/components/text-input.js +1 -6
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable-native-feedback.js +1 -6
- package/build/angular/components/touchable-native-feedback.js.map +1 -1
- package/build/angular/index.d.ts +1 -1
- package/build/angular/index.js +1 -1
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/animated-leaf-binder.d.ts +14 -0
- package/build/angular/modules/animated/animated-leaf-binder.js +91 -0
- package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -0
- package/build/angular/modules/animated/create-animated-component.d.ts +3 -12
- package/build/angular/modules/animated/create-animated-component.js +48 -113
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/renderer.js +6 -14
- package/build/angular/renderer.js.map +1 -1
- package/build/components/image/shared.d.ts +4 -4
- package/build/components/image/shared.js +1 -3
- package/build/components/input-accessory-view.d.ts +1 -1
- package/build/components/input-accessory-view.js +1 -5
- package/build/components/keyboard-avoiding-view.js +1 -6
- package/build/components/modal.js +1 -6
- package/build/components/pressable/index.js +13 -14
- package/build/components/refresh-control.js +1 -6
- package/build/components/safe-area-view.d.ts +1 -1
- package/build/components/safe-area-view.js +1 -5
- package/build/components/text-input.js +1 -6
- package/build/components/touchable-native-feedback.js +1 -6
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/modules/animated/animated-leaf-binder.d.ts +14 -0
- package/build/modules/animated/animated-leaf-binder.js +90 -0
- package/build/modules/animated/create-animated-component.d.ts +3 -12
- package/build/modules/animated/create-animated-component.js +47 -112
- package/build/renderer.js +6 -14
- package/package.json +8 -4
- package/src/components/image/shared.ts +6 -5
- package/src/components/input-accessory-view.ts +6 -7
- package/src/components/keyboard-avoiding-view.ts +1 -6
- package/src/components/modal.ts +1 -6
- package/src/components/pressable/index.ts +17 -15
- package/src/components/refresh-control.ts +1 -6
- package/src/components/safe-area-view.ts +6 -7
- package/src/components/text-input.ts +1 -6
- package/src/components/touchable-native-feedback.ts +1 -7
- package/src/index.ts +1 -1
- package/src/modules/animated/animated-leaf-binder.test.ts +116 -0
- package/src/modules/animated/animated-leaf-binder.ts +101 -0
- package/src/modules/animated/animated-scroll-view.test.ts +17 -0
- package/src/modules/animated/create-animated-component.ts +53 -123
- package/src/renderer.test.ts +24 -1
- package/src/renderer.ts +6 -14
|
@@ -52,12 +52,14 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
52
52
|
return useValue ? value : void 0;
|
|
53
53
|
};
|
|
54
54
|
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, Directive, ElementRef, inject, ViewChild, } from '@angular/core';
|
|
55
|
-
import {
|
|
55
|
+
import { isNativeAnimatedAvailable, isSymbioteNode, reduceProps, readPassthroughStyle, resolveHostNode, } from '@symbiote-native/engine';
|
|
56
|
+
import { selectScrollIntrinsics } from '@symbiote-native/components';
|
|
56
57
|
import { Image, ScrollView, Text, View } from '../../components';
|
|
57
58
|
import { anchorHostStyle, ViewHost, TextHost, ImageHost, ScrollViewHost, SymbioteHostPropsDirective, } from '../../primitives';
|
|
58
59
|
import { FlatList } from '../../components/flat-list';
|
|
59
60
|
import { IMAGE_INPUTS, IMAGE_OUTPUTS, ImageBase, resolveImageProps, } from '../../components/image/shared';
|
|
60
61
|
import { SectionList } from '../../components/section-list';
|
|
62
|
+
import { AnimatedLeafBinder } from './animated-leaf-binder';
|
|
61
63
|
// RN's prop carrying explicit (already-rasterized) values that override the animated prop in
|
|
62
64
|
// the COMMITTED props (sticky-header passthrough). Named once so the directive input and the
|
|
63
65
|
// reconcile agree.
|
|
@@ -105,12 +107,11 @@ let AnimatedComponentBase = (() => {
|
|
|
105
107
|
// The directive sitting on the inner host primitive; its `node` is the committed SymbioteNode
|
|
106
108
|
// the leaf binds to. Inherited by the decorated subclass (Angular collects base-class queries).
|
|
107
109
|
hostDirective = __runInitializers(this, _hostDirective_initializers, void 0);
|
|
108
|
-
// The leaf
|
|
109
|
-
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
eventDetachers = [];
|
|
110
|
+
// The leaf-lifecycle orchestration (build/attach/swap/bind/detach an AnimatedProps leaf) is a
|
|
111
|
+
// Pure Fabrication shared with AnimatedImage, which cannot extend this class (it must extend
|
|
112
|
+
// ImageBase — see the file header). `resolveNode` stays a resolver, not a captured node: the
|
|
113
|
+
// committed node only exists after `hostDirective` is populated post-view-init.
|
|
114
|
+
leafBinder = (__runInitializers(this, _hostDirective_extraInitializers), new AnimatedLeafBinder(() => this.hostNode(), this.constructor.name));
|
|
114
115
|
// Set once the view (and the inner host directive) exists; ngOnChanges before then is a no-op.
|
|
115
116
|
viewReady = false;
|
|
116
117
|
// The concrete props for the host element each change-detection pass: animated entries reduced
|
|
@@ -134,73 +135,21 @@ let AnimatedComponentBase = (() => {
|
|
|
134
135
|
this.reconcile();
|
|
135
136
|
}
|
|
136
137
|
ngOnDestroy() {
|
|
137
|
-
this.
|
|
138
|
-
this.cancelBind = undefined;
|
|
139
|
-
this.detachEvents();
|
|
140
|
-
if (this.attached !== null) {
|
|
141
|
-
this.attached.__detach();
|
|
142
|
-
this.attached = null;
|
|
143
|
-
}
|
|
138
|
+
this.leafBinder.destroy();
|
|
144
139
|
}
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// committed node, go native if wanted, and rebind native events.
|
|
140
|
+
// A non-stable [animatedProps]/[style] input (a fresh object literal in the template) pushes
|
|
141
|
+
// a new reference on every change-detection pass of the HOST view, re-triggering this on every
|
|
142
|
+
// unrelated interaction elsewhere in the app — DEBUG=1 turns this into a visible tick count in
|
|
143
|
+
// AnimatedLeafBinder.reconcile, so an unexpectedly high rate for a component nobody is
|
|
144
|
+
// animating is the tell (see AnimatedScrollView's stable-reference fix in examples/angular/App.ts).
|
|
151
145
|
reconcile() {
|
|
152
146
|
if (!this.viewReady)
|
|
153
147
|
return;
|
|
154
|
-
// A non-stable [animatedProps]/[style] input (a fresh object literal in the template)
|
|
155
|
-
// pushes a new reference on every change-detection pass of the HOST view, re-triggering
|
|
156
|
-
// this on every unrelated interaction elsewhere in the app — DEBUG=1 turns this into a
|
|
157
|
-
// visible tick count, so an unexpectedly high rate for a component nobody is animating
|
|
158
|
-
// is the tell (see AnimatedScrollView's stable-reference fix in examples/angular/App.ts).
|
|
159
|
-
dlog(`AnimatedComponentBase reconcile (${this.constructor.name})`);
|
|
160
148
|
const props = this.mergedProps();
|
|
161
149
|
const hasPassthroughAnimatedValues = this.passthroughAnimatedPropExplicitValues !== null &&
|
|
162
150
|
this.passthroughAnimatedPropExplicitValues !== undefined;
|
|
163
151
|
const wantsNative = hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
|
|
164
|
-
|
|
165
|
-
newLeaf.__attach();
|
|
166
|
-
if (this.attached !== null && this.attached !== newLeaf)
|
|
167
|
-
this.attached.__detach();
|
|
168
|
-
this.attached = newLeaf;
|
|
169
|
-
this.bindNode(newLeaf, props, wantsNative);
|
|
170
|
-
}
|
|
171
|
-
// Bind the leaf to the host's Fabric node THROUGH whenCommitted: under Angular's zoneless
|
|
172
|
-
// batched change detection the inner host's tag does not exist yet at ngAfterViewInit time
|
|
173
|
-
// (the same async-commit gotcha Vue documents), so binding eagerly would no-op. whenCommitted
|
|
174
|
-
// runs the action now if already committed, else after the commit that assigns the tag.
|
|
175
|
-
bindNode(leaf, props, wantsNative) {
|
|
176
|
-
this.cancelBind?.();
|
|
177
|
-
this.cancelBind = undefined;
|
|
178
|
-
const node = this.hostNode();
|
|
179
|
-
if (node === null)
|
|
180
|
-
return;
|
|
181
|
-
this.cancelBind = whenCommitted(node, () => {
|
|
182
|
-
leaf.setNativeView(node);
|
|
183
|
-
if (wantsNative)
|
|
184
|
-
leaf.__makeNative();
|
|
185
|
-
this.attachEvents(node, props);
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
// Native-attach any Animated.event prop (e.g. onScroll={Animated.event(…,{useNativeDriver:true})})
|
|
189
|
-
// to the committed node. attachNativeEventHandler no-ops (returns undefined) unless the prop is a
|
|
190
|
-
// native event handler with a committed tag, so the JS path stays the fallback. Rebound each
|
|
191
|
-
// reconcile so a new inline event re-attaches; detached first to avoid leaking the prior binding.
|
|
192
|
-
attachEvents(node, props) {
|
|
193
|
-
this.detachEvents();
|
|
194
|
-
for (const key of Object.keys(props)) {
|
|
195
|
-
const attachment = attachNativeEventHandler(node, key, props[key]);
|
|
196
|
-
if (attachment !== undefined)
|
|
197
|
-
this.eventDetachers.push(attachment.detach);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
detachEvents() {
|
|
201
|
-
for (const detach of this.eventDetachers)
|
|
202
|
-
detach();
|
|
203
|
-
this.eventDetachers = [];
|
|
152
|
+
this.leafBinder.reconcile(props, wantsNative);
|
|
204
153
|
}
|
|
205
154
|
// Merge the dedicated `style` input over the generic `animatedProps` bag into one props map —
|
|
206
155
|
// the equivalent of React/Vue `rest`. `style` wins so `[style]` and `[animatedProps].style`
|
|
@@ -333,9 +282,10 @@ let AnimatedImage = (() => {
|
|
|
333
282
|
// comment on AnimatedComponentBase.elementRef.
|
|
334
283
|
elementRef = inject(ElementRef);
|
|
335
284
|
hostDirective = __runInitializers(this, _hostDirective_initializers, void 0);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
285
|
+
// Same Pure Fabrication AnimatedComponentBase holds — see its matching comment. AnimatedImage
|
|
286
|
+
// can't extend AnimatedComponentBase (it must extend ImageBase, see the file header), so it
|
|
287
|
+
// gets the leaf-lifecycle via composition instead of duplicating it.
|
|
288
|
+
leafBinder = (__runInitializers(this, _hostDirective_extraInitializers), new AnimatedLeafBinder(() => this.hostNode(), this.constructor.name));
|
|
339
289
|
viewReady = false;
|
|
340
290
|
get animatedImageProps() {
|
|
341
291
|
const reduced = reduceProps(this.mergedProps());
|
|
@@ -356,13 +306,7 @@ let AnimatedImage = (() => {
|
|
|
356
306
|
this.reconcile();
|
|
357
307
|
}
|
|
358
308
|
ngOnDestroy() {
|
|
359
|
-
this.
|
|
360
|
-
this.cancelBind = undefined;
|
|
361
|
-
this.detachEvents();
|
|
362
|
-
if (this.attached !== null) {
|
|
363
|
-
this.attached.__detach();
|
|
364
|
-
this.attached = null;
|
|
365
|
-
}
|
|
309
|
+
this.leafBinder.destroy();
|
|
366
310
|
}
|
|
367
311
|
reconcile() {
|
|
368
312
|
if (!this.viewReady)
|
|
@@ -371,38 +315,7 @@ let AnimatedImage = (() => {
|
|
|
371
315
|
const hasPassthroughAnimatedValues = this.passthroughAnimatedPropExplicitValues !== null &&
|
|
372
316
|
this.passthroughAnimatedPropExplicitValues !== undefined;
|
|
373
317
|
const wantsNative = hasPassthroughAnimatedValues && isNativeAnimatedAvailable();
|
|
374
|
-
|
|
375
|
-
newLeaf.__attach();
|
|
376
|
-
if (this.attached !== null && this.attached !== newLeaf)
|
|
377
|
-
this.attached.__detach();
|
|
378
|
-
this.attached = newLeaf;
|
|
379
|
-
this.bindNode(newLeaf, props, wantsNative);
|
|
380
|
-
}
|
|
381
|
-
bindNode(leaf, props, wantsNative) {
|
|
382
|
-
this.cancelBind?.();
|
|
383
|
-
this.cancelBind = undefined;
|
|
384
|
-
const node = this.hostNode();
|
|
385
|
-
if (node === null)
|
|
386
|
-
return;
|
|
387
|
-
this.cancelBind = whenCommitted(node, () => {
|
|
388
|
-
leaf.setNativeView(node);
|
|
389
|
-
if (wantsNative)
|
|
390
|
-
leaf.__makeNative();
|
|
391
|
-
this.attachEvents(node, props);
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
attachEvents(node, props) {
|
|
395
|
-
this.detachEvents();
|
|
396
|
-
for (const key of Object.keys(props)) {
|
|
397
|
-
const attachment = attachNativeEventHandler(node, key, props[key]);
|
|
398
|
-
if (attachment !== undefined)
|
|
399
|
-
this.eventDetachers.push(attachment.detach);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
detachEvents() {
|
|
403
|
-
for (const detach of this.eventDetachers)
|
|
404
|
-
detach();
|
|
405
|
-
this.eventDetachers = [];
|
|
318
|
+
this.leafBinder.reconcile(props, wantsNative);
|
|
406
319
|
}
|
|
407
320
|
mergedProps() {
|
|
408
321
|
const props = { ...(this.animatedProps ?? {}) };
|
|
@@ -429,7 +342,7 @@ let AnimatedScrollView = (() => {
|
|
|
429
342
|
inputs: ANIMATED_INPUTS,
|
|
430
343
|
template: `
|
|
431
344
|
<symbiote-scroll-view [symbioteHostProps]="reducedProps">
|
|
432
|
-
<symbiote-scroll-content [
|
|
345
|
+
<symbiote-scroll-content [symbioteHostProps]="contentProps">
|
|
433
346
|
<ng-content></ng-content>
|
|
434
347
|
</symbiote-scroll-content>
|
|
435
348
|
</symbiote-scroll-view>
|
|
@@ -454,11 +367,33 @@ let AnimatedScrollView = (() => {
|
|
|
454
367
|
// receives touch (swallowed by the outer scroll view; iOS doesn't gate on this). The real
|
|
455
368
|
// ScrollView component already defaults this (scroll-view/shared.ts), but AnimatedScrollView
|
|
456
369
|
// talks to the raw primitive directly and has no such defaulting, so it must apply it too.
|
|
370
|
+
//
|
|
371
|
+
// scrollViewBaseStyle (overflow: 'scroll' + the per-axis flexDirection) is the OTHER thing
|
|
372
|
+
// the real ScrollView applies that this bespoke template used to skip entirely — without it,
|
|
373
|
+
// iOS Fabric never clips the scroll view's content to its own frame (Android's native
|
|
374
|
+
// ViewGroup clips regardless of the style prop, which is why this was invisible there). This
|
|
375
|
+
// wrapper has no dedicated `horizontal` input (only the generic `animatedProps`/`style`
|
|
376
|
+
// surface), and its template is hardcoded to the vertical intrinsics
|
|
377
|
+
// (symbiote-scroll-view/symbiote-scroll-content, not the horizontal variants), so
|
|
378
|
+
// selectScrollIntrinsics is always called with isHorizontal=false here — matching what the
|
|
379
|
+
// template can actually render.
|
|
457
380
|
get reducedProps() {
|
|
458
381
|
const reduced = super.reducedProps;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
382
|
+
const { scrollViewBaseStyle } = selectScrollIntrinsics(false, undefined);
|
|
383
|
+
const withScrollBase = {
|
|
384
|
+
...reduced,
|
|
385
|
+
style: [scrollViewBaseStyle, reduced.style],
|
|
386
|
+
};
|
|
387
|
+
return withScrollBase.nestedScrollEnabled === undefined
|
|
388
|
+
? { ...withScrollBase, nestedScrollEnabled: true }
|
|
389
|
+
: withScrollBase;
|
|
390
|
+
}
|
|
391
|
+
// The content (inner) view's props: contentStyle from the same intrinsics selection, plus
|
|
392
|
+
// `collapsable: false` (the Android multi-child fix this file's tests already cover) — mirrors
|
|
393
|
+
// the real ScrollView's own `contentProps` getter (scroll-view/shared.ts).
|
|
394
|
+
get contentProps() {
|
|
395
|
+
const { contentStyle } = selectScrollIntrinsics(false, undefined);
|
|
396
|
+
return { style: contentStyle, collapsable: false };
|
|
462
397
|
}
|
|
463
398
|
};
|
|
464
399
|
return AnimatedScrollView = _classThis;
|
package/build/renderer.js
CHANGED
|
@@ -53,23 +53,15 @@ const ANCHOR_HOST_COMPONENTS = new Set([
|
|
|
53
53
|
// Composed Angular components render their real Fabric descriptor tree from the template;
|
|
54
54
|
// their Angular host element is only a framework bookkeeping node and must not paint. This
|
|
55
55
|
// is NOT limited to adapter-authored components — ANY custom composed @Component used as a
|
|
56
|
-
// plain <Tag> inside another template needs its selector listed here too,
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
56
|
+
// plain <Tag> inside another template needs its selector listed here too, or Angular's
|
|
57
|
+
// automatic host-element creation for it falls through to a raw Fabric createNode call with
|
|
58
|
+
// an unrecognized view name, which paints RN's own "Unimplemented component: <Tag>" fallback
|
|
59
|
+
// view instead (a real device-visible bug, not a silent no-op). This Set holds only
|
|
60
|
+
// adapter/engine-owned selectors; app code and third-party packages self-register their own
|
|
61
|
+
// composed components through registerComposedComponent instead of being hardcoded here.
|
|
62
62
|
'ActivityIndicator',
|
|
63
|
-
'AnimatedDemo',
|
|
64
|
-
'AnimatedParityDemo',
|
|
65
63
|
'Button',
|
|
66
64
|
'FlatList',
|
|
67
|
-
'NativeModulesDemo',
|
|
68
|
-
'ParityDemo',
|
|
69
|
-
'PlatformColorDemo',
|
|
70
|
-
'RefApiDemo',
|
|
71
|
-
'ResponderDemo',
|
|
72
|
-
'Slider',
|
|
73
65
|
'AnimatedView',
|
|
74
66
|
'symbiote-animated-view',
|
|
75
67
|
'AnimatedText',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/angular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "SymbioteNative's Angular adapter — a Renderer2/RendererFactory2 with DOM-less bootstrap driving real native iOS/Android views through the same engine as the other adapters.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"./metro-css-parser": "./metro-css-parser.cjs",
|
|
31
31
|
"./metro-config": "./metro-config.cjs",
|
|
32
32
|
"./babel-linker": "./babel-linker.cjs",
|
|
33
|
+
"./babel-register-composed": "./babel-register-composed.cjs",
|
|
33
34
|
"./tsconfig.angular.base.json": "./tsconfig.angular.base.json"
|
|
34
35
|
},
|
|
35
36
|
"files": [
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
"metro-css-parser.cjs",
|
|
40
41
|
"metro-config.cjs",
|
|
41
42
|
"babel-linker.cjs",
|
|
43
|
+
"babel-register-composed.cjs",
|
|
42
44
|
"tsconfig.angular.base.json"
|
|
43
45
|
],
|
|
44
46
|
"bin": {
|
|
@@ -50,9 +52,9 @@
|
|
|
50
52
|
"dependencies": {
|
|
51
53
|
"@angular/compiler-cli": "^22",
|
|
52
54
|
"@react-native/typescript-config": "0.86.0",
|
|
53
|
-
"@symbiote-native/components": "0.2.
|
|
54
|
-
"@symbiote-native/
|
|
55
|
-
"@symbiote-native/
|
|
55
|
+
"@symbiote-native/components": "0.2.4",
|
|
56
|
+
"@symbiote-native/css-parser": "0.2.1",
|
|
57
|
+
"@symbiote-native/engine": "0.1.5"
|
|
56
58
|
},
|
|
57
59
|
"peerDependencies": {
|
|
58
60
|
"@angular/core": ">=20",
|
|
@@ -61,6 +63,8 @@
|
|
|
61
63
|
"devDependencies": {
|
|
62
64
|
"@angular/compiler": "^22",
|
|
63
65
|
"@angular/core": "^22",
|
|
66
|
+
"@babel/core": "^7.25.2",
|
|
67
|
+
"@types/babel__core": "^7.20.5",
|
|
64
68
|
"@types/node": "^26.0.0",
|
|
65
69
|
"typescript": "~6.0.0",
|
|
66
70
|
"@symbiote-native/test-utils": "0.1.5"
|
|
@@ -9,7 +9,12 @@ import {
|
|
|
9
9
|
type IImageSourceProp,
|
|
10
10
|
type IResizeMode,
|
|
11
11
|
} from '@symbiote-native/components';
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
isSymbioteEvent,
|
|
14
|
+
type IStyleProp,
|
|
15
|
+
type ISymbioteEvent,
|
|
16
|
+
type IViewStyle,
|
|
17
|
+
} from '@symbiote-native/engine';
|
|
13
18
|
|
|
14
19
|
export { setImageSourceResolver } from '@symbiote-native/components';
|
|
15
20
|
export type {
|
|
@@ -153,10 +158,6 @@ function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
|
153
158
|
return typeof value === 'object' && value !== null ? value : undefined;
|
|
154
159
|
}
|
|
155
160
|
|
|
156
|
-
function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
157
|
-
return typeof value === 'object' && value !== null && Reflect.has(value, 'nativeEvent');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
161
|
export function resolveImageProps(input: Record<string, unknown>): Record<string, unknown> {
|
|
161
162
|
const passthrough: IImagePassthroughProps = {
|
|
162
163
|
resizeMethod: input['resizeMethod'] as IImageProps['resizeMethod'],
|
|
@@ -21,15 +21,14 @@ import {
|
|
|
21
21
|
type IAccessibilityStateValue,
|
|
22
22
|
type IAriaProps,
|
|
23
23
|
} from '@symbiote-native/components';
|
|
24
|
-
import
|
|
24
|
+
import {
|
|
25
|
+
isSymbioteEvent,
|
|
26
|
+
type IStyleProp,
|
|
27
|
+
type ISymbioteEvent,
|
|
28
|
+
type IViewStyle,
|
|
29
|
+
} from '@symbiote-native/engine';
|
|
25
30
|
import { anchorHostStyle, InputAccessoryViewHost, SymbioteHostPropsDirective } from '../primitives';
|
|
26
31
|
|
|
27
|
-
// Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
|
|
28
|
-
// so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
|
|
29
|
-
function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
30
|
-
return typeof value === 'object' && value !== null && 'nativeEvent' in value;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
32
|
// Mirrors React's IInputAccessoryViewProps minus children (Angular takes children via
|
|
34
33
|
// <ng-content>), declared per-adapter over the shared a11y base.
|
|
35
34
|
export interface IAngularInputAccessoryViewProps extends IAccessibilityProps, IAriaProps {
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
Keyboard,
|
|
43
43
|
KEYBOARD_EVENT,
|
|
44
44
|
dlog,
|
|
45
|
+
isSymbioteEvent,
|
|
45
46
|
type IEventSubscription,
|
|
46
47
|
type IStyleProp,
|
|
47
48
|
type ISymbioteEvent,
|
|
@@ -51,12 +52,6 @@ import { anchorHostStyle, SymbioteHostPropsDirective, ViewHost } from '../primit
|
|
|
51
52
|
|
|
52
53
|
export type { IKeyboardAvoidingBehavior } from '@symbiote-native/components';
|
|
53
54
|
|
|
54
|
-
// Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
|
|
55
|
-
// so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
|
|
56
|
-
function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
57
|
-
return typeof value === 'object' && value !== null && 'nativeEvent' in value;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
55
|
// Mirrors React's IKeyboardAvoidingViewProps minus children (Angular takes children via
|
|
61
56
|
// <ng-content>), declared per-adapter over the shared accessibility base since a framework-specific
|
|
62
57
|
// children field keeps it from being fully shared across adapters.
|
package/src/components/modal.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
} from '@symbiote-native/components';
|
|
40
40
|
import {
|
|
41
41
|
dlog,
|
|
42
|
+
isSymbioteEvent,
|
|
42
43
|
type IStyleProp,
|
|
43
44
|
type ISymbioteEvent,
|
|
44
45
|
type IViewStyle,
|
|
@@ -52,12 +53,6 @@ export type {
|
|
|
52
53
|
IModalOrientationChangeEvent,
|
|
53
54
|
} from '@symbiote-native/components';
|
|
54
55
|
|
|
55
|
-
// Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
|
|
56
|
-
// so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
|
|
57
|
-
function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
58
|
-
return typeof value === 'object' && value !== null && 'nativeEvent' in value;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
56
|
const ORIENTATIONS: ReadonlyArray<IModalOrientation> = ['portrait', 'landscape'];
|
|
62
57
|
|
|
63
58
|
function readOrientation(event: ISymbioteEvent): IModalOrientationChangeEvent | undefined {
|
|
@@ -14,9 +14,12 @@ import {
|
|
|
14
14
|
createPressHandlers,
|
|
15
15
|
createPressRuntime,
|
|
16
16
|
DEFAULT_DELAY_LONG_PRESS_MS,
|
|
17
|
+
isTerminationAllowed,
|
|
17
18
|
resolveAccessibilityProps,
|
|
18
19
|
resolveDisabledAccessibilityState,
|
|
19
20
|
rippleProps,
|
|
21
|
+
shouldClaimResponder,
|
|
22
|
+
shouldSuppressPress,
|
|
20
23
|
type IAccessibilityProps,
|
|
21
24
|
type IAccessibilityStateValue,
|
|
22
25
|
type IAriaProps,
|
|
@@ -43,7 +46,7 @@ export type { IPressState, IPressableAndroidRippleConfig } from '@symbiote-nativ
|
|
|
43
46
|
|
|
44
47
|
// The full logical Pressable surface, callback-shaped press/hover events included. Touchable* and
|
|
45
48
|
// TouchableNativeFeedback still take their OWN onPress/onPressIn/... as @Input() callbacks (that is
|
|
46
|
-
// a separate, not-yet-converted decision) and derive their public prop types from this
|
|
49
|
+
// a separate, not-yet-converted decision) and derive their public prop types from this - so it keeps
|
|
47
50
|
// the callback fields even though the Pressable component below no longer implements them directly.
|
|
48
51
|
export interface IAngularPressableProps extends IAccessibilityProps, IAriaProps {
|
|
49
52
|
onPress?: IPressHandler;
|
|
@@ -76,7 +79,7 @@ export interface IAngularPressableProps extends IAccessibilityProps, IAriaProps
|
|
|
76
79
|
|
|
77
80
|
// What the Pressable component itself takes as plain @Input()s: the full surface minus the press/
|
|
78
81
|
// hover events and the four accessibility callbacks, all of which it exposes as real @Output()
|
|
79
|
-
// EventEmitters instead (see the class below)
|
|
82
|
+
// EventEmitters instead (see the class below) - the one place in the family where the
|
|
80
83
|
// callback-vs-EventEmitter tradeoff is actually fixed.
|
|
81
84
|
export type IAngularPressableInputs = Omit<
|
|
82
85
|
IAngularPressableProps,
|
|
@@ -131,7 +134,7 @@ function asSymbioteEvent(event: unknown): ISymbioteEvent | undefined {
|
|
|
131
134
|
export class Pressable implements IAngularPressableInputs {
|
|
132
135
|
// The press/hover lifecycle as real Angular events: `(press)="onTap($event)"`, not
|
|
133
136
|
// `[onPress]="onTap"`. createPressHandlers still wants plain IPressHandler callbacks, so
|
|
134
|
-
// emitterHandler() below adapts each EventEmitter into one
|
|
137
|
+
// emitterHandler() below adapts each EventEmitter into one - only while something is actually
|
|
135
138
|
// subscribed (`.observed`), so an unbound onLongPress still skips arming the timer, exactly as
|
|
136
139
|
// the old @Input()-absent case did.
|
|
137
140
|
@Output() readonly press = new EventEmitter<ISymbioteEvent>();
|
|
@@ -223,8 +226,8 @@ export class Pressable implements IAngularPressableInputs {
|
|
|
223
226
|
};
|
|
224
227
|
|
|
225
228
|
private readonly changeDetector = inject(ChangeDetectorRef);
|
|
226
|
-
// This component's OWN host
|
|
227
|
-
// onto (see anchorHostStyle's doc comment)
|
|
229
|
+
// This component's OWN host - the non-painting anchor `class="..."` at the use site resolves
|
|
230
|
+
// onto (see anchorHostStyle's doc comment) - NOT `hostElement` above, which targets the real
|
|
228
231
|
// inner `symbiote-view` one level down.
|
|
229
232
|
private readonly elementRef = inject(ElementRef);
|
|
230
233
|
|
|
@@ -275,8 +278,7 @@ export class Pressable implements IAngularPressableInputs {
|
|
|
275
278
|
return this.android_ripple === undefined ? undefined : rippleProps(this.android_ripple);
|
|
276
279
|
}
|
|
277
280
|
|
|
278
|
-
//
|
|
279
|
-
// flat bag for `[symbioteHostProps]` (Renderer2.setProperty per key) — see primitives/shared.ts.
|
|
281
|
+
// Flat bag for `[symbioteHostProps]` (Renderer2.setProperty per key) - see primitives/shared.ts.
|
|
280
282
|
get hostProps(): Record<string, unknown> {
|
|
281
283
|
return {
|
|
282
284
|
testID: this.testID,
|
|
@@ -298,34 +300,34 @@ export class Pressable implements IAngularPressableInputs {
|
|
|
298
300
|
|
|
299
301
|
handlePressIn(event: unknown): void {
|
|
300
302
|
const symbioteEvent = asSymbioteEvent(event);
|
|
301
|
-
if (this.disabled
|
|
303
|
+
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined) return;
|
|
302
304
|
this.handlers.handlePressIn(symbioteEvent);
|
|
303
305
|
}
|
|
304
306
|
|
|
305
307
|
handlePressOut(event: unknown): void {
|
|
306
308
|
const symbioteEvent = asSymbioteEvent(event);
|
|
307
|
-
if (this.disabled
|
|
309
|
+
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined) return;
|
|
308
310
|
this.handlers.handlePressOut(symbioteEvent);
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
handlePress(event: unknown): void {
|
|
312
314
|
const symbioteEvent = asSymbioteEvent(event);
|
|
313
|
-
if (this.disabled
|
|
315
|
+
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined) return;
|
|
314
316
|
this.handlers.handlePress(symbioteEvent);
|
|
315
317
|
}
|
|
316
318
|
|
|
317
319
|
handleResponderMove(event: unknown): void {
|
|
318
320
|
const symbioteEvent = asSymbioteEvent(event);
|
|
319
|
-
if (this.disabled
|
|
321
|
+
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined) return;
|
|
320
322
|
this.handlers.handleResponderMove(symbioteEvent);
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
claimResponder(): boolean {
|
|
324
|
-
return this.disabled
|
|
326
|
+
return shouldClaimResponder(this.disabled);
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
allowTermination(): boolean {
|
|
328
|
-
return this.cancelable
|
|
330
|
+
return isTerminationAllowed(this.cancelable);
|
|
329
331
|
}
|
|
330
332
|
|
|
331
333
|
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
@@ -342,8 +344,8 @@ export class Pressable implements IAngularPressableInputs {
|
|
|
342
344
|
}
|
|
343
345
|
|
|
344
346
|
// Wraps an @Output() as an IPressHandler only while it has a subscriber, so an unbound
|
|
345
|
-
// (longPress) still skips arming the timer
|
|
346
|
-
//
|
|
347
|
+
// (longPress) still skips arming the timer, matching createPressHandlers' "undefined means
|
|
348
|
+
// nobody cares" contract.
|
|
347
349
|
private emitterHandler(emitter: EventEmitter<ISymbioteEvent>): IPressHandler | undefined {
|
|
348
350
|
return emitter.observed ? event => emitter.emit(event) : undefined;
|
|
349
351
|
}
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import {
|
|
37
37
|
dispatchViewCommand,
|
|
38
38
|
dlog,
|
|
39
|
+
isSymbioteEvent,
|
|
39
40
|
isSymbioteNode,
|
|
40
41
|
type IStyleProp,
|
|
41
42
|
type ISymbioteEvent,
|
|
@@ -47,12 +48,6 @@ import { anchorHostStyle, RefreshControlHost, SymbioteHostPropsDirective } from
|
|
|
47
48
|
// array anchorHostStyle's merge produces (see hostProps below) — no widening needed.
|
|
48
49
|
type IHostProps = Record<string, unknown>;
|
|
49
50
|
|
|
50
|
-
// Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
|
|
51
|
-
// so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
|
|
52
|
-
function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
53
|
-
return typeof value === 'object' && value !== null && 'nativeEvent' in value;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
51
|
// Mirrors React's IRefreshControlProps minus children (Angular takes the Android-wrapped scroll view
|
|
57
52
|
// via <ng-content>), declared per-adapter over the shared accessibility base since the framework-specific
|
|
58
53
|
// children slot keeps it from being fully shared across adapters. Read the React reference for the
|
|
@@ -22,15 +22,14 @@ import {
|
|
|
22
22
|
type IAccessibilityStateValue,
|
|
23
23
|
type IAriaProps,
|
|
24
24
|
} from '@symbiote-native/components';
|
|
25
|
-
import
|
|
25
|
+
import {
|
|
26
|
+
isSymbioteEvent,
|
|
27
|
+
type IStyleProp,
|
|
28
|
+
type ISymbioteEvent,
|
|
29
|
+
type IViewStyle,
|
|
30
|
+
} from '@symbiote-native/engine';
|
|
26
31
|
import { anchorHostStyle, SafeAreaViewHost, SymbioteHostPropsDirective } from '../primitives';
|
|
27
32
|
|
|
28
|
-
// Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
|
|
29
|
-
// so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
|
|
30
|
-
function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
31
|
-
return typeof value === 'object' && value !== null && 'nativeEvent' in value;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
33
|
// Mirrors React's ISafeAreaViewProps minus children (Angular takes children via <ng-content>),
|
|
35
34
|
// declared per-adapter over the shared accessibility base since the framework-specific children
|
|
36
35
|
// slot keeps it from being fully shared across adapters.
|
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
blurTextInput,
|
|
66
66
|
dispatchViewCommand,
|
|
67
67
|
dlog,
|
|
68
|
+
isSymbioteEvent,
|
|
68
69
|
isSymbioteNode,
|
|
69
70
|
setInputBlurred,
|
|
70
71
|
setInputFocused,
|
|
@@ -83,12 +84,6 @@ import {
|
|
|
83
84
|
|
|
84
85
|
export type { ITextInputHandle, ITextInputSelection, IInputMode, IEnterKeyHint, ISubmitBehavior };
|
|
85
86
|
|
|
86
|
-
// Angular infers an (event) binding's $event as the DOM Event for a custom-schema element, so
|
|
87
|
-
// each handler enters as `unknown` and is narrowed here before reaching a typed callback.
|
|
88
|
-
function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
89
|
-
return typeof value === 'object' && value !== null && 'nativeEvent' in value;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
87
|
// Mirrors React's ITextInputProps minus nothing (TextInput has no children) — declared
|
|
93
88
|
// per-adapter over the shared accessibility base because Angular's input surface aliases the
|
|
94
89
|
// aria-* keys to camelCase @Inputs, unlike the plain agnostic fields shared across adapters.
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
type IPressableAndroidRippleConfig,
|
|
45
45
|
type IRectOffset,
|
|
46
46
|
} from '@symbiote-native/components';
|
|
47
|
-
import { dlog, type ISymbioteEvent } from '@symbiote-native/engine';
|
|
47
|
+
import { dlog, isSymbioteEvent, type ISymbioteEvent } from '@symbiote-native/engine';
|
|
48
48
|
import { anchorHostStyle, SymbioteHostPropsDirective, ViewHost } from '../primitives';
|
|
49
49
|
import { Pressable, type IAngularPressableInputs } from './pressable';
|
|
50
50
|
|
|
@@ -63,12 +63,6 @@ export type IAngularTouchableNativeFeedbackProps = Omit<IAngularPressableInputs,
|
|
|
63
63
|
useForeground?: boolean;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
// Angular infers an `(event)` binding's $event as the DOM Event for a custom-schema element,
|
|
67
|
-
// so each handler enters as `unknown` and is narrowed here before reaching a typed callback.
|
|
68
|
-
function isSymbioteEvent(value: unknown): value is ISymbioteEvent {
|
|
69
|
-
return typeof value === 'object' && value !== null && 'nativeEvent' in value;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
66
|
@Component({
|
|
73
67
|
selector: 'TouchableNativeFeedback',
|
|
74
68
|
standalone: true,
|
package/src/index.ts
CHANGED
|
@@ -135,7 +135,7 @@ export { DescriptorOutlet } from './descriptor-to-angular';
|
|
|
135
135
|
// call.
|
|
136
136
|
export { PortalDirective, PortalOutletDirective } from './create-portal';
|
|
137
137
|
export { createTunnel, TunnelInDirective, TunnelOut, type ITunnelStore } from './create-tunnel';
|
|
138
|
-
export { SymbioteRenderer, SymbioteRendererFactory } from './renderer';
|
|
138
|
+
export { registerComposedComponent, SymbioteRenderer, SymbioteRendererFactory } from './renderer';
|
|
139
139
|
export { findNodeHandle } from './host-instance';
|
|
140
140
|
export type { IHostInstance } from './host-instance';
|
|
141
141
|
export { StatusBar } from './modules/status-bar';
|