@symbiote-native/angular 0.8.0 → 2.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 +13 -10
- package/babel-register-composed.cjs +58 -18
- package/bin/symbiote-angular-dev.cjs +16 -4
- package/build/angular/anchor-host-registry.js +1 -15
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/change-detection-flush.d.ts +31 -0
- package/build/angular/change-detection-flush.js +110 -0
- package/build/angular/change-detection-flush.js.map +1 -0
- package/build/angular/components/activity-indicator-props.d.ts +2 -0
- package/build/angular/components/activity-indicator-props.js +2 -0
- package/build/angular/components/activity-indicator-props.js.map +1 -0
- package/build/angular/components/button-props.d.ts +2 -0
- package/build/angular/components/button-props.js +2 -0
- package/build/angular/components/button-props.js.map +1 -0
- package/build/angular/components/flat-list/index.d.ts +2 -1
- package/build/angular/components/flat-list/index.js +22 -10
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image-background-props.d.ts +6 -0
- package/build/angular/components/image-background-props.js +2 -0
- package/build/angular/components/image-background-props.js.map +1 -0
- package/build/angular/components/{image/shared.d.ts → image-shared.d.ts} +4 -4
- package/build/angular/components/{image/shared.js → image-shared.js} +39 -28
- package/build/angular/components/image-shared.js.map +1 -0
- package/build/angular/components/input-accessory-view-props.d.ts +7 -0
- package/build/angular/components/input-accessory-view-props.js +9 -0
- package/build/angular/components/input-accessory-view-props.js.map +1 -0
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +2 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +28 -27
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +2 -0
- package/build/angular/components/modal/index.js +33 -20
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable-props.d.ts +32 -0
- package/build/angular/components/pressable-props.js +11 -0
- package/build/angular/components/pressable-props.js.map +1 -0
- package/build/angular/components/refresh-control-props.d.ts +16 -0
- package/build/angular/components/refresh-control-props.js +14 -0
- package/build/angular/components/refresh-control-props.js.map +1 -0
- package/build/angular/components/safe-area-view-props.d.ts +7 -0
- package/build/angular/components/safe-area-view-props.js +11 -0
- package/build/angular/components/safe-area-view-props.js.map +1 -0
- package/build/angular/components/scroll-view-props.d.ts +76 -0
- package/build/angular/components/scroll-view-props.js +18 -0
- package/build/angular/components/scroll-view-props.js.map +1 -0
- package/build/angular/components/section-list/index.d.ts +3 -0
- package/build/angular/components/section-list/index.js +10 -1
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/text-input-props.d.ts +45 -0
- package/build/angular/components/text-input-props.js +18 -0
- package/build/angular/components/text-input-props.js.map +1 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +6 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js +2 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js.map +1 -0
- package/build/angular/components/touchable-props.d.ts +14 -0
- package/build/angular/components/touchable-props.js +13 -0
- package/build/angular/components/touchable-props.js.map +1 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +3 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js +2 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js.map +1 -0
- package/build/angular/components/virtualized-list/index.d.ts +8 -1
- package/build/angular/components/virtualized-list/index.js +373 -278
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +3 -0
- package/build/angular/components/virtualized-section-list/index.js +10 -1
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +21 -27
- package/build/angular/components.js +13 -15
- 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 +37 -11
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js +1 -1
- package/build/angular/element-props.d.ts +53 -0
- package/build/angular/element-props.js +2 -0
- package/build/angular/element-props.js.map +1 -0
- package/build/angular/elements.d.ts +528 -0
- package/build/angular/elements.js +1444 -0
- package/build/angular/elements.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/index.d.ts +5 -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 +6 -6
- package/build/angular/modules/animated/create-animated-component.js +73 -81
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/animated/index.js +1 -1
- package/build/angular/modules/animated/index.js.map +1 -1
- package/build/angular/modules/image/index.d.ts +3 -0
- package/build/angular/modules/image/index.js +16 -0
- package/build/angular/modules/image/index.js.map +1 -0
- package/build/angular/primitives/index.d.ts +28 -16
- package/build/angular/primitives/index.js +77 -31
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +2 -3
- package/build/angular/primitives/shared.js +8 -53
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/register.d.ts +1 -0
- package/build/angular/register.js +16 -0
- package/build/angular/register.js.map +1 -0
- package/build/angular/render/index.js +4 -2
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.js +207 -60
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/renderer/value-change.d.ts +2 -0
- package/build/angular/renderer/value-change.js +9 -0
- package/build/angular/renderer/value-change.js.map +1 -0
- package/metro-config.cjs +23 -5
- package/metro-css-parser.cjs +2 -1
- package/package.json +17 -7
- package/src/anchor-host-registry.ts +1 -15
- package/src/change-detection-flush.ts +120 -0
- package/src/components/activity-indicator-props.ts +21 -0
- package/src/components/button-props.ts +26 -0
- package/src/components/flat-list/index.ts +12 -4
- package/src/components/image-background-props.ts +24 -0
- package/src/components/{image/shared.ts → image-shared.ts} +57 -26
- package/src/components/input-accessory-view-props.ts +21 -0
- package/src/components/keyboard-avoiding-view/index.ts +27 -16
- package/src/components/modal/index.ts +32 -11
- package/src/components/pressable-props.ts +73 -0
- package/src/components/refresh-control-props.ts +43 -0
- package/src/components/safe-area-view-props.ts +28 -0
- package/src/components/scroll-view-props.ts +109 -0
- package/src/components/section-list/index.ts +18 -0
- package/src/components/text-input-props.ts +72 -0
- package/src/components/touchable-native-feedback/touchable-native-feedback-props.ts +23 -0
- package/src/components/touchable-props.ts +30 -0
- package/src/components/touchable-without-feedback/touchable-without-feedback-props.ts +21 -0
- package/src/components/virtualized-list/index.ts +254 -153
- package/src/components/virtualized-section-list/index.ts +18 -0
- package/src/components.ts +86 -35
- package/src/create-portal/index.ts +39 -9
- package/src/create-tunnel/index.ts +1 -1
- package/src/diagnostics.ts +1 -1
- package/src/element-props.ts +90 -0
- package/src/elements.ts +919 -0
- package/src/gate-demand.ts +109 -0
- package/src/host-instance/index.ts +1 -1
- package/src/index.ts +54 -15
- package/src/modules/animated/create-animated-component.ts +73 -59
- package/src/modules/animated/index.ts +1 -1
- package/src/modules/image/index.ts +18 -0
- package/src/primitives/index.ts +47 -16
- package/src/primitives/shared.ts +11 -44
- package/src/register.ts +15 -0
- package/src/render/index.ts +4 -2
- package/src/renderer/index.ts +239 -80
- package/src/renderer/value-change.ts +8 -0
- package/build/angular/components/activity-indicator/index.android.d.ts +0 -13
- package/build/angular/components/activity-indicator/index.android.js +0 -29
- package/build/angular/components/activity-indicator/index.android.js.map +0 -1
- package/build/angular/components/activity-indicator/index.d.ts +0 -1
- package/build/angular/components/activity-indicator/index.ios.d.ts +0 -9
- package/build/angular/components/activity-indicator/index.ios.js +0 -26
- package/build/angular/components/activity-indicator/index.ios.js.map +0 -1
- package/build/angular/components/activity-indicator/index.js +0 -4
- package/build/angular/components/activity-indicator/index.js.map +0 -1
- package/build/angular/components/activity-indicator/shared.d.ts +0 -67
- package/build/angular/components/activity-indicator/shared.js +0 -260
- package/build/angular/components/activity-indicator/shared.js.map +0 -1
- package/build/angular/components/button.d.ts +0 -65
- package/build/angular/components/button.js +0 -351
- package/build/angular/components/button.js.map +0 -1
- package/build/angular/components/image/index.android.d.ts +0 -15
- package/build/angular/components/image/index.android.js +0 -91
- package/build/angular/components/image/index.android.js.map +0 -1
- package/build/angular/components/image/index.d.ts +0 -1
- package/build/angular/components/image/index.ios.d.ts +0 -15
- package/build/angular/components/image/index.ios.js +0 -91
- package/build/angular/components/image/index.ios.js.map +0 -1
- package/build/angular/components/image/index.js +0 -3
- package/build/angular/components/image/index.js.map +0 -1
- package/build/angular/components/image/shared.js.map +0 -1
- package/build/angular/components/image-background.d.ts +0 -82
- package/build/angular/components/image-background.js +0 -434
- package/build/angular/components/image-background.js.map +0 -1
- package/build/angular/components/input-accessory-view/index.d.ts +0 -65
- package/build/angular/components/input-accessory-view/index.js +0 -290
- package/build/angular/components/input-accessory-view/index.js.map +0 -1
- package/build/angular/components/pressable/index.d.ts +0 -129
- package/build/angular/components/pressable/index.js +0 -473
- package/build/angular/components/pressable/index.js.map +0 -1
- package/build/angular/components/refresh-control/index.d.ts +0 -90
- package/build/angular/components/refresh-control/index.js +0 -380
- package/build/angular/components/refresh-control/index.js.map +0 -1
- package/build/angular/components/safe-area-view/index.d.ts +0 -62
- package/build/angular/components/safe-area-view/index.js +0 -287
- package/build/angular/components/safe-area-view/index.js.map +0 -1
- package/build/angular/components/scroll-view/index.android.d.ts +0 -20
- package/build/angular/components/scroll-view/index.android.js +0 -262
- package/build/angular/components/scroll-view/index.android.js.map +0 -1
- package/build/angular/components/scroll-view/index.d.ts +0 -3
- package/build/angular/components/scroll-view/index.ios.d.ts +0 -8
- package/build/angular/components/scroll-view/index.ios.js +0 -92
- package/build/angular/components/scroll-view/index.ios.js.map +0 -1
- package/build/angular/components/scroll-view/index.js +0 -6
- package/build/angular/components/scroll-view/index.js.map +0 -1
- package/build/angular/components/scroll-view/projection.d.ts +0 -67
- package/build/angular/components/scroll-view/projection.js +0 -560
- package/build/angular/components/scroll-view/projection.js.map +0 -1
- package/build/angular/components/scroll-view/shared.d.ts +0 -239
- package/build/angular/components/scroll-view/shared.js +0 -832
- package/build/angular/components/scroll-view/shared.js.map +0 -1
- package/build/angular/components/scroll-view/sticky-header.d.ts +0 -40
- package/build/angular/components/scroll-view/sticky-header.js +0 -224
- package/build/angular/components/scroll-view/sticky-header.js.map +0 -1
- package/build/angular/components/switch/index.android.d.ts +0 -24
- package/build/angular/components/switch/index.android.js +0 -112
- package/build/angular/components/switch/index.android.js.map +0 -1
- package/build/angular/components/switch/index.d.ts +0 -1
- package/build/angular/components/switch/index.ios.d.ts +0 -23
- package/build/angular/components/switch/index.ios.js +0 -111
- package/build/angular/components/switch/index.ios.js.map +0 -1
- package/build/angular/components/switch/index.js +0 -4
- package/build/angular/components/switch/index.js.map +0 -1
- package/build/angular/components/switch/shared.d.ts +0 -49
- package/build/angular/components/switch/shared.js +0 -191
- package/build/angular/components/switch/shared.js.map +0 -1
- package/build/angular/components/text-input.d.ts +0 -161
- package/build/angular/components/text-input.js +0 -659
- package/build/angular/components/text-input.js.map +0 -1
- package/build/angular/components/touchable/index.d.ts +0 -276
- package/build/angular/components/touchable/index.js +0 -1422
- package/build/angular/components/touchable/index.js.map +0 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +0 -86
- package/build/angular/components/touchable-native-feedback/index.js +0 -407
- package/build/angular/components/touchable-native-feedback/index.js.map +0 -1
- package/src/components/activity-indicator/index.android.ts +0 -23
- package/src/components/activity-indicator/index.ios.ts +0 -21
- package/src/components/activity-indicator/index.ts +0 -4
- package/src/components/activity-indicator/shared.ts +0 -225
- package/src/components/button.ts +0 -220
- package/src/components/image/index.android.ts +0 -99
- package/src/components/image/index.ios.ts +0 -99
- package/src/components/image/index.ts +0 -3
- package/src/components/image-background.ts +0 -290
- package/src/components/input-accessory-view/index.ts +0 -240
- package/src/components/pressable/index.ts +0 -462
- package/src/components/refresh-control/index.ts +0 -340
- package/src/components/safe-area-view/index.ts +0 -231
- package/src/components/scroll-view/index.android.ts +0 -205
- package/src/components/scroll-view/index.ios.ts +0 -81
- package/src/components/scroll-view/index.ts +0 -7
- package/src/components/scroll-view/projection.ts +0 -713
- package/src/components/scroll-view/shared.ts +0 -1066
- package/src/components/scroll-view/sticky-header.ts +0 -267
- package/src/components/switch/index.android.ts +0 -113
- package/src/components/switch/index.ios.ts +0 -112
- package/src/components/switch/index.ts +0 -4
- package/src/components/switch/shared.ts +0 -284
- package/src/components/text-input.ts +0 -613
- package/src/components/touchable/index.ts +0 -913
- package/src/components/touchable-native-feedback/index.ts +0 -317
package/src/primitives/shared.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type SimpleChanges,
|
|
11
11
|
} from '@angular/core';
|
|
12
12
|
import { countAngular } from '../diagnostics';
|
|
13
|
+
import { createCallbackWrapper } from '../change-detection-flush';
|
|
13
14
|
import {
|
|
14
15
|
flattenStyle,
|
|
15
16
|
isSymbioteNode,
|
|
@@ -108,18 +109,6 @@ export function stableAnchorStyle(
|
|
|
108
109
|
: next;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
const ON_PREFIX = /^on[A-Z]/;
|
|
112
|
-
|
|
113
|
-
// The one `onX` that fires per FRAME rather than per gesture, and with sticky headers RN pins
|
|
114
|
-
// `scrollEventThrottle` to 1 (ScrollView.js), so 60 times a second. Since markForCheck walks
|
|
115
|
-
// RefreshView|Dirty to the ROOT, wrapping it cost one full ancestor-screen template execution per
|
|
116
|
-
// frame - the canary's ~37fps scroll (components/virtualized-list/scroll-cost.test.ts).
|
|
117
|
-
//
|
|
118
|
-
// Nothing lost its refresh: VirtualizedList marks itself when the window moves, sticky rides the
|
|
119
|
-
// native driver, and a caller's own handler is typically an Animated.event touching no Angular
|
|
120
|
-
// state. Drag/momentum begin/end stay wrapped - once per gesture is not a hot path.
|
|
121
|
-
const PER_FRAME_CALLBACK = 'onScroll';
|
|
122
|
-
|
|
123
112
|
/**
|
|
124
113
|
* Restores the OnPush dirty-marking Angular skips for a `[style]` binding that a directive
|
|
125
114
|
* declares as an input. Attach it to any component declaring a `style` input:
|
|
@@ -194,7 +183,7 @@ function isAnchorStyleUnchanged(previous: unknown, next: unknown): boolean {
|
|
|
194
183
|
}
|
|
195
184
|
|
|
196
185
|
/**
|
|
197
|
-
* Base for all Angular primitive host components (`
|
|
186
|
+
* Base for all Angular primitive host components (`view`, `text`, ...).
|
|
198
187
|
* RN's `StyleProp` can be an object, an array, nested arrays, falsy entries, and Animated
|
|
199
188
|
* values. Angular's raw `[style]` binding on a custom element routes through the CSS style
|
|
200
189
|
* engine (`setStyle` per key), which crashes on numeric array keys. By declaring `style` as
|
|
@@ -265,20 +254,15 @@ export class SymbioteHostPropsDirective {
|
|
|
265
254
|
// The directive lives in its HOST component's template, so its injected ChangeDetectorRef IS
|
|
266
255
|
// that component's own view detector — the one thing that can refresh it (see wrapCallback).
|
|
267
256
|
private readonly cdr = inject(ChangeDetectorRef);
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// "this bag is unchanged" while a callback prop was in it.
|
|
278
|
-
private readonly wrappers = new WeakMap<
|
|
279
|
-
object,
|
|
280
|
-
(...args: unknown[]) => unknown
|
|
281
|
-
>();
|
|
257
|
+
// A flat-bag `onX` callback (responder negotiation, onLongPress, …) is invoked by the engine's
|
|
258
|
+
// event dispatch — a plain JS call, entirely outside Angular — so nothing dirties the view that
|
|
259
|
+
// binds its result: the "pan does nothing" bug. Shared with the TAG path's `SymbioteElement`,
|
|
260
|
+
// which has the identical deficit; the mechanism, the `onScroll` exemption and why the wrapper
|
|
261
|
+
// is cached per original handler live in `createCallbackWrapper`.
|
|
262
|
+
private readonly wrapCallback = createCallbackWrapper(
|
|
263
|
+
this.cdr,
|
|
264
|
+
this.elementRef.nativeElement,
|
|
265
|
+
);
|
|
282
266
|
|
|
283
267
|
// Values pushed by the previous bag, so a push writes only what actually moved. Copied rather
|
|
284
268
|
// than aliased: a component is free to hand back the same object with mutated fields.
|
|
@@ -330,21 +314,4 @@ export class SymbioteHostPropsDirective {
|
|
|
330
314
|
// (`_cdRefInjectingView`, i.e. the host component), unlike `detectChanges()` which acts on a
|
|
331
315
|
// resolved `_lView` that is NOT the host component here. The Angular twin of what React/Vue get
|
|
332
316
|
// for free (setState / proxy reactivity).
|
|
333
|
-
private wrapCallback(key: string, value: unknown): unknown {
|
|
334
|
-
if (!ON_PREFIX.test(key) || typeof value !== 'function') return value;
|
|
335
|
-
if (key === PER_FRAME_CALLBACK) return value;
|
|
336
|
-
const cached = this.wrappers.get(value);
|
|
337
|
-
if (cached !== undefined) return cached;
|
|
338
|
-
// Reflect.apply rather than calling a cast-to-signature local: `typeof value === 'function'`
|
|
339
|
-
// narrows to `Function`, which has no call signature TypeScript will accept, and this repo
|
|
340
|
-
// does not use `as` to paper over that. `undefined` as the receiver preserves the previous
|
|
341
|
-
// unbound call.
|
|
342
|
-
const wrapper = (...args: unknown[]): unknown => {
|
|
343
|
-
const result: unknown = Reflect.apply(value, undefined, args);
|
|
344
|
-
this.cdr.markForCheck();
|
|
345
|
-
return result;
|
|
346
|
-
};
|
|
347
|
-
this.wrappers.set(value, wrapper);
|
|
348
|
-
return wrapper;
|
|
349
|
-
}
|
|
350
317
|
}
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Side-effect ONLY. Exports nothing, and must never be re-exported from the barrel.
|
|
2
|
+
//
|
|
3
|
+
// The fourteen calls live once, in `@symbiote-native/components/register` — they were identical in
|
|
4
|
+
// all five adapters, and each copy carried a migration ledger justifying a call against a wrapper
|
|
5
|
+
// that no longer exists. What is per-adapter is only the SPELLING of the import below: every
|
|
6
|
+
// `*-tag.test.*` here reaches the registry through `import './register';`, and this adapter's own
|
|
7
|
+
// barrel guard pins that line.
|
|
8
|
+
//
|
|
9
|
+
// WHY A SIDE-EFFECT SUBPATH AND NOT A BARREL EXPORT. Metro turns on `inlineRequires` for PRODUCTION
|
|
10
|
+
// only: it moves a `require` down to the first place its binding is used as a VALUE, and
|
|
11
|
+
// `export { X } from './x'` compiles to a lazy getter. A registration reached that way never
|
|
12
|
+
// evaluates in a Release build — invisible to tsc, to vitest, and to grepping the bundle. A bare
|
|
13
|
+
// `import` placed NEXT TO a re-export of the same specifier does not help either: Babel merges the
|
|
14
|
+
// two into one dependency and the merged dependency stays lazy.
|
|
15
|
+
import '@symbiote-native/components/register';
|
package/src/render/index.ts
CHANGED
|
@@ -54,7 +54,7 @@ function asAngularHost(hostNode: SymbioteSurface | ISymbioteNode): Element {
|
|
|
54
54
|
return hostNode as unknown as Element;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// A bare `
|
|
57
|
+
// A bare `view` node, created directly through the engine (bypassing Angular's own
|
|
58
58
|
// createElement, which only resolves KNOWN symbiote primitives, never an arbitrary component's
|
|
59
59
|
// selector — see SymbioteRenderer.createElement). Used as the AppRegistry root's host when a
|
|
60
60
|
// wrapperComponentProvider is set: Angular has no hostElement-less bootstrap for our renderer
|
|
@@ -62,7 +62,9 @@ function asAngularHost(hostNode: SymbioteSurface | ISymbioteNode): Element {
|
|
|
62
62
|
// that isn't one of our primitives), so the root needs an explicit host too, one we then hand
|
|
63
63
|
// to the wrapper as projectable content.
|
|
64
64
|
function createDetachedViewHost(): ISymbioteNode {
|
|
65
|
-
|
|
65
|
+
// 'view', not 'View': the public name is not a Fabric view name, and until
|
|
66
|
+
// makeDescriptorFor learned to reject one it fell through to a view literally named `View`.
|
|
67
|
+
const descriptor = descriptorFor('view');
|
|
66
68
|
return toPublicInstance(
|
|
67
69
|
createEngineElement(descriptor.component, descriptor.isText),
|
|
68
70
|
);
|
package/src/renderer/index.ts
CHANGED
|
@@ -14,17 +14,23 @@ import {
|
|
|
14
14
|
getExplicitStyle,
|
|
15
15
|
insertBefore,
|
|
16
16
|
isDebug,
|
|
17
|
+
isSymbioteEvent,
|
|
17
18
|
isSymbioteNode,
|
|
18
19
|
removeChild,
|
|
19
20
|
routeProp,
|
|
20
21
|
setEventListener,
|
|
22
|
+
setProp,
|
|
21
23
|
setText,
|
|
22
24
|
toPublicInstance,
|
|
23
25
|
RAW_TEXT_COMPONENT,
|
|
24
26
|
SymbioteSurface,
|
|
25
27
|
type ISymbioteNode,
|
|
26
28
|
} from '@symbiote-native/engine';
|
|
27
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
COMPONENT_DESCRIPTORS,
|
|
31
|
+
descriptorFor,
|
|
32
|
+
} from '@symbiote-native/components';
|
|
33
|
+
import { foldHostBag } from '@symbiote-native/components/fold-host-bag';
|
|
28
34
|
import type { Renderer2, RendererFactory2, RendererType2 } from '@angular/core';
|
|
29
35
|
import { isAnchorHostComponent } from '../anchor-host-registry';
|
|
30
36
|
import {
|
|
@@ -32,12 +38,6 @@ import {
|
|
|
32
38
|
noteAngularCreate,
|
|
33
39
|
noteAngularWrite,
|
|
34
40
|
} from '../diagnostics';
|
|
35
|
-
import {
|
|
36
|
-
flushScrollViewProjections,
|
|
37
|
-
getScrollViewProjection,
|
|
38
|
-
removeScrollViewProjectedChild,
|
|
39
|
-
} from '../components/scroll-view/projection';
|
|
40
|
-
|
|
41
41
|
// Angular host nodes are all SymbioteNode (elements, raw text, anchors). The mount
|
|
42
42
|
// container is the surface, so a parent can be either a node or the surface root.
|
|
43
43
|
type IHostNode = ISymbioteNode;
|
|
@@ -55,6 +55,89 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
55
55
|
return typeof value === 'object' && value !== null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// RN's Text.js applies two defaults on the way to native (core/components/host-primitives.cjs's
|
|
59
|
+
// `Text.defaults`; the authority on what they MEAN is core/components/src/text-props.ts's
|
|
60
|
+
// resolveTextProps, which the composed `Text` @Component already calls). That component's own
|
|
61
|
+
// host paints directly (Text is not anchor-hosted — see the top-level "View/Text's own component
|
|
62
|
+
// doesn't have this split" reasoning elsewhere in this file), so createElement runs for its INNER
|
|
63
|
+
// text node too; seeding here therefore covers both the composed Text and any bare
|
|
64
|
+
// `text` a future lowering emits, uniformly. Found missing 2026-08-31 (a cross-adapter
|
|
65
|
+
// key-count diff against Vue's real BenchmarkRow.vue) — without this a lowered Text's
|
|
66
|
+
// `numberOfLines` clips with no ellipsis, silently, on device only. Vue's renderer already does
|
|
67
|
+
// this (`adapters/vue/src/renderer/index.ts`'s `seedTextDefaults`); Angular's simply never did.
|
|
68
|
+
//
|
|
69
|
+
// Sourced from `foldHostBag` (`@symbiote-native/components/fold-host-bag`, driven by
|
|
70
|
+
// `HOST_PRIMITIVES.Text.defaults`) rather than a second hardcoded copy — React and Svelte call the
|
|
71
|
+
// same function directly; this used to be a THIRD, independent restatement of the same two
|
|
72
|
+
// defaults, with nothing to catch it drifting from the spec if a default's value ever changed.
|
|
73
|
+
// `foldHostBag('text', {})` on an EMPTY bag folds every default with no authored value to
|
|
74
|
+
// override it (the alias loop has nothing to fold — `id` is only rewritten when present), which is
|
|
75
|
+
// exactly the seed this function needs.
|
|
76
|
+
function seedTextDefaults(node: ISymbioteNode): void {
|
|
77
|
+
const seeded = foldHostBag('text', {});
|
|
78
|
+
for (const [key, value] of Object.entries(seeded)) setProp(node, key, value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// An explicit `undefined` must NOT clear one of those defaults — RN treats a missing prop and an
|
|
82
|
+
// explicit undefined alike, and only a literal `false` opts allowFontScaling out. Reached only
|
|
83
|
+
// when a later write clears a key back to undefined, so it costs nothing on the common path.
|
|
84
|
+
// `foldHostBag` folds every Text default when called this way (not just `key`), because its
|
|
85
|
+
// contract is "fold a whole bag" — the extra key computed alongside `key` is simply unread here.
|
|
86
|
+
function textDefaultFor(el: IHostElement, key: string): unknown {
|
|
87
|
+
if (isSurface(el) || !el.isText) return undefined;
|
|
88
|
+
return foldHostBag('text', { [key]: undefined })[key];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// RN's `id` is the modern W3C-named alias for `nativeID` (core/components/host-primitives.cjs's
|
|
92
|
+
// `ID_ALIAS`) — View.js/Text.js copy it over unconditionally, so the two name ONE native prop.
|
|
93
|
+
// React/Solid/Svelte fold it in a wrapper or transform; Angular had it nowhere, so `<view
|
|
94
|
+
// id="x">`/`[id]="x"` reached Fabric with an unknown `id` key and no `nativeID` — silently, on
|
|
95
|
+
// device only. Lives in the renderer (mirroring Vue's `PROP_ALIASES`) so it covers every path
|
|
96
|
+
// that can set a prop — `setAttribute`, `setProperty`, and (should a future lowering emit one) a
|
|
97
|
+
// hand-built call — not just the composed component's own `id` @Input.
|
|
98
|
+
// `symbioteStyle` is what the lowering transform emits in place of `[style]`: Angular routes a
|
|
99
|
+
// `style` binding to its own CSS styling engine, which cannot represent an RN StyleProp (an array
|
|
100
|
+
// throws inside change detection). Under any other name it is an ordinary property binding and
|
|
101
|
+
// arrives here.
|
|
102
|
+
// Angular's two-way sugar `[(value)]` compiles to a `(valueChange)` binding; the engine knows the
|
|
103
|
+
// same fold as the function prop `onValueChange`. See `listen()`. The two names live in a leaf
|
|
104
|
+
// module so `elements.ts`'s ControlValueAccessor can name them without importing this cyclic file.
|
|
105
|
+
import { VALUE_CHANGE_EVENT, VALUE_CHANGE_PROP } from './value-change';
|
|
106
|
+
import { flushViewFor } from '../change-detection-flush';
|
|
107
|
+
|
|
108
|
+
const PROP_ALIASES: ReadonlyMap<string, string> = new Map([
|
|
109
|
+
['id', 'nativeID'],
|
|
110
|
+
['symbioteStyle', 'style'],
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
function aliasedPropName(name: string): string {
|
|
114
|
+
return PROP_ALIASES.get(name) ?? name;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// The app callbacks an engine behavior READS BACK inside the same microtask turn, as an Angular
|
|
118
|
+
// `(event)` binding — `valueChange` is handled in `listen` on its own, since it also needs the field
|
|
119
|
+
// unwrapped. Zoneless change detection is a macrotask, so without a flush the behavior reads the
|
|
120
|
+
// PRE-event value; `../change-detection-flush` holds the whole mechanism, the three behaviors, and
|
|
121
|
+
// why this is scoped rather than wired to the engine's event-dispatch seam.
|
|
122
|
+
//
|
|
123
|
+
// The other spelling — the `[onRefresh]` flat-bag PROP — is not here: an `on*` function prop is
|
|
124
|
+
// wrapped by `SymbioteElement` itself, which reaches every one of them rather than a named three,
|
|
125
|
+
// and adds the `markForCheck` a prop callback needs and an event binding gets from Angular.
|
|
126
|
+
const READ_BACK_EVENTS: ReadonlySet<string> = new Set(['refresh']);
|
|
127
|
+
|
|
128
|
+
type IReadBackListener = (event: unknown) => unknown;
|
|
129
|
+
|
|
130
|
+
function withChangeDetection(
|
|
131
|
+
node: IHostElement,
|
|
132
|
+
listener: IReadBackListener,
|
|
133
|
+
): IReadBackListener {
|
|
134
|
+
return (event: unknown): unknown => {
|
|
135
|
+
const result = listener(event);
|
|
136
|
+
flushViewFor(node);
|
|
137
|
+
return result;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
58
141
|
// Diagnostic-only: tags each anchor with a sequential id so log lines can tell distinct
|
|
59
142
|
// anchors apart (they all otherwise print the same generic '#anchor' component name).
|
|
60
143
|
// Gated behind isDebug() so a production build pays zero cost (no WeakMap writes).
|
|
@@ -79,23 +162,51 @@ function describeHost(node: IHostElement | null | undefined): string {
|
|
|
79
162
|
: node.component;
|
|
80
163
|
}
|
|
81
164
|
|
|
165
|
+
// A hand-written tag ngtsc accepts must contain a HYPHEN, so the six dashless intrinsics
|
|
166
|
+
// (`view`/`text`/`pressable`/`image`/`switch`/`modal`) need a second spelling that has one.
|
|
167
|
+
// `hasElement`/`hasProperty` gate the CUSTOM_ELEMENTS_SCHEMA branch behind
|
|
168
|
+
// `normalizedTag.includes('-')` (compiler/src/schema/dom_element_schema_registry.ts), so a dashless
|
|
169
|
+
// tag is NG8001 under every schema but NO_ERRORS_SCHEMA — which switches off element checking
|
|
170
|
+
// entirely. Dropping the `symbiote-` prefix from the tag alphabet is what took the dash away.
|
|
171
|
+
//
|
|
172
|
+
// A per-tag directive is the other repair, and it is the one an app should reach for now that
|
|
173
|
+
// `SYMBIOTE_ELEMENTS` exists (`../elements.ts`). Matching a directive does turn every bound prop
|
|
174
|
+
// into an input lookup — the sentence that used to end this paragraph — but that is a repair
|
|
175
|
+
// rather than a dead end: declare the input and the lookup succeeds, with a real TYPE, which no
|
|
176
|
+
// schema gives. This alias map stays for the `symbiote-*` spelling and for an app that has not
|
|
177
|
+
// imported the directives.
|
|
178
|
+
//
|
|
179
|
+
// Derived, so a future dashless intrinsic joins by existing; when the alphabet regains its dashes
|
|
180
|
+
// upstream every entry becomes identity and the map can go. See `bare-intrinsic-tag-aot.test.ts`.
|
|
181
|
+
const HYPHEN_PREFIX = 'symbiote-';
|
|
182
|
+
|
|
183
|
+
function hyphenatedIntrinsicAliases(): Record<string, string> {
|
|
184
|
+
const aliases: Record<string, string> = {};
|
|
185
|
+
for (const tag of Object.keys(COMPONENT_DESCRIPTORS)) {
|
|
186
|
+
if (tag.includes('-')) continue;
|
|
187
|
+
aliases[`${HYPHEN_PREFIX}${tag}`] = tag;
|
|
188
|
+
}
|
|
189
|
+
return aliases;
|
|
190
|
+
}
|
|
191
|
+
|
|
82
192
|
const PRIMITIVE_SELECTOR_ALIAS: Record<string, string> = {
|
|
83
193
|
// Public ergonomic selectors map directly to the engine primitive descriptors.
|
|
84
|
-
View: '
|
|
85
|
-
Text: '
|
|
194
|
+
View: 'view',
|
|
195
|
+
Text: 'text',
|
|
196
|
+
...hyphenatedIntrinsicAliases(),
|
|
86
197
|
};
|
|
87
198
|
|
|
88
199
|
// The ANCHOR_HOST_COMPONENTS Set and registerComposedComponent now live in the dependency-free
|
|
89
200
|
// leaf module ../anchor-host-registry (imported above as isAnchorHostComponent) — see its header
|
|
90
201
|
// for why the registry must NOT sit in this require-cyclic renderer module.
|
|
91
202
|
|
|
92
|
-
// Inserting a bare raw-text node anywhere but inside a <
|
|
93
|
-
// stray RCTRawText would paint). Angular's ɵɵtext only ever lands text inside a <
|
|
203
|
+
// Inserting a bare raw-text node anywhere but inside a <text> is invalid in Fabric (a
|
|
204
|
+
// stray RCTRawText would paint). Angular's ɵɵtext only ever lands text inside a <text>,
|
|
94
205
|
// but guard anyway for parity with the Vue adapter and to fail loudly on a bad template.
|
|
95
206
|
function assertTextPlacement(child: ISymbioteNode, parent: IHostElement): void {
|
|
96
207
|
if (isRawText(child) && (isSurface(parent) || !parent.isText)) {
|
|
97
208
|
throw new Error(
|
|
98
|
-
`Text string "${String(child.props.text)}" must be rendered inside a <
|
|
209
|
+
`Text string "${String(child.props.text)}" must be rendered inside a <text>`,
|
|
99
210
|
);
|
|
100
211
|
}
|
|
101
212
|
}
|
|
@@ -114,27 +225,55 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
114
225
|
destroy(): void {}
|
|
115
226
|
|
|
116
227
|
createElement(name: string): IHostNode {
|
|
117
|
-
// `name` is the component's host tag — a symbiote intrinsic (`
|
|
118
|
-
// `
|
|
228
|
+
// `name` is the component's host tag — a symbiote intrinsic (`view`,
|
|
229
|
+
// `text`, …), a public ergonomic alias (`View`, `Text`), or a raw Fabric view
|
|
119
230
|
// name for a native leaf. Public aliases are normalized to their engine primitive name
|
|
120
231
|
// before descriptor lookup. descriptorFor resolves it; an unknown `symbiote-*` is a typo,
|
|
121
232
|
// any other string flows through as a raw Fabric name (events/processors derived from its
|
|
122
|
-
// ViewConfig).
|
|
123
|
-
//
|
|
233
|
+
// ViewConfig). The imperative API (measure / setNativeProps / focus) is on the node's
|
|
234
|
+
// prototype, so toPublicInstance hands back the SAME identity the commit mirror keys on.
|
|
124
235
|
countAngular('nodesCreated');
|
|
125
236
|
noteAngularCreate(name);
|
|
126
237
|
const engineName = PRIMITIVE_SELECTOR_ALIAS[name] ?? name;
|
|
127
|
-
|
|
238
|
+
|
|
239
|
+
// An INTRINSIC is never an anchor, and the check has to come first now that the tags carry no
|
|
240
|
+
// prefix. The anchor registry is keyed on lowercased selectors — Angular lowercases a
|
|
241
|
+
// dynamically-mounted component's selector at runtime, so it must be — and the composed
|
|
242
|
+
// wrappers are named after the primitives they render: `Text`.toLowerCase() IS the tag `text`.
|
|
243
|
+
// While both layers exist, every `<text>` would anchor instead of painting.
|
|
244
|
+
//
|
|
245
|
+
// Derived from the descriptor table rather than an exclusion list, so it cannot go stale; and
|
|
246
|
+
// it disappears on its own when the wrappers do, which is what this migration is for. The
|
|
247
|
+
// collision is exactly the single-word names — `TextInput` lowercases to `textinput`, which is
|
|
248
|
+
// no tag of ours.
|
|
249
|
+
if (
|
|
250
|
+
COMPONENT_DESCRIPTORS[engineName] === undefined &&
|
|
251
|
+
isAnchorHostComponent(engineName)
|
|
252
|
+
) {
|
|
128
253
|
const anchor = tagAnchorForDebug(createAnchor());
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
254
|
+
if (isDebug()) {
|
|
255
|
+
dlog(
|
|
256
|
+
`angular createElement ${name} -> anchor host ${describeHost(anchor)}`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
132
259
|
return anchor;
|
|
133
260
|
}
|
|
134
261
|
|
|
135
262
|
const descriptor = descriptorFor(engineName);
|
|
136
|
-
|
|
137
|
-
|
|
263
|
+
// The tag, not just the resolved Fabric name: the host-behavior registry is keyed by the
|
|
264
|
+
// INTRINSIC tag (host-behavior.ts's own comment), and a node only ever carries the resolved
|
|
265
|
+
// name afterward. Omitting this argument means attachHostBehavior looks up the WRONG key
|
|
266
|
+
// (e.g. 'RCTView') and any registered behavior silently never attaches — Vue's renderer
|
|
267
|
+
// already passes this correctly (`createElement(descriptor.component, descriptor.isText, type)`).
|
|
268
|
+
const node = createElement(
|
|
269
|
+
descriptor.component,
|
|
270
|
+
descriptor.isText,
|
|
271
|
+
engineName,
|
|
272
|
+
);
|
|
273
|
+
if (descriptor.isText) seedTextDefaults(node);
|
|
274
|
+
if (isDebug()) {
|
|
275
|
+
dlog(`angular createElement ${name} -> ${descriptor.component}`);
|
|
276
|
+
}
|
|
138
277
|
return toPublicInstance(node);
|
|
139
278
|
}
|
|
140
279
|
|
|
@@ -144,7 +283,9 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
144
283
|
// Vue createComment path.
|
|
145
284
|
countAngular('nodesCreated');
|
|
146
285
|
const anchor = tagAnchorForDebug(createAnchor());
|
|
147
|
-
|
|
286
|
+
if (isDebug()) {
|
|
287
|
+
dlog(`Angular renderer createComment -> ${describeHost(anchor)}`);
|
|
288
|
+
}
|
|
148
289
|
return anchor;
|
|
149
290
|
}
|
|
150
291
|
|
|
@@ -161,22 +302,22 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
161
302
|
countAngular('nodesInserted');
|
|
162
303
|
assertTextPlacement(newChild, parent);
|
|
163
304
|
if (isSurface(parent)) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
305
|
+
if (isDebug()) {
|
|
306
|
+
dlog(
|
|
307
|
+
`Angular renderer appendChild parent=surface child=${describeHost(newChild)}`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
167
310
|
parent.appendChild(newChild);
|
|
168
311
|
} else {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
appendChild(target, child),
|
|
312
|
+
// The engine's own `appendChild` already redirects through `parent.childHost` when the
|
|
313
|
+
// primitive's behavior builds one (ScrollView's content node, e.g.) — the adapter-side
|
|
314
|
+
// projection bridge this used to route through is gone with the ScrollView component.
|
|
315
|
+
if (isDebug()) {
|
|
316
|
+
dlog(
|
|
317
|
+
`Angular renderer appendChild parent=${describeHost(parent)} child=${describeHost(newChild)}`,
|
|
176
318
|
);
|
|
177
|
-
} else {
|
|
178
|
-
appendChild(parent, newChild);
|
|
179
319
|
}
|
|
320
|
+
appendChild(parent, newChild);
|
|
180
321
|
}
|
|
181
322
|
this.surface.requestCommit();
|
|
182
323
|
}
|
|
@@ -190,31 +331,21 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
190
331
|
countAngular('nodesInserted');
|
|
191
332
|
assertTextPlacement(newChild, parent);
|
|
192
333
|
if (isSurface(parent)) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
334
|
+
if (isDebug()) {
|
|
335
|
+
dlog(
|
|
336
|
+
`Angular renderer insertBefore parent=surface child=${describeHost(newChild)} ref=${refChild ? describeHost(refChild) : 'null'}`,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
196
339
|
if (refChild) parent.insertBefore(newChild, refChild);
|
|
197
340
|
else parent.appendChild(newChild);
|
|
198
341
|
} else {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
);
|
|
203
|
-
if (projection !== undefined) {
|
|
204
|
-
projection.insertProjectedChild(
|
|
205
|
-
parent,
|
|
206
|
-
newChild,
|
|
207
|
-
refChild,
|
|
208
|
-
(target, child, before) => {
|
|
209
|
-
if (before === undefined) appendChild(target, child);
|
|
210
|
-
else insertBefore(target, child, before);
|
|
211
|
-
},
|
|
342
|
+
if (isDebug()) {
|
|
343
|
+
dlog(
|
|
344
|
+
`Angular renderer insertBefore parent=${describeHost(parent)} child=${describeHost(newChild)} ref=${refChild ? describeHost(refChild) : 'null'}`,
|
|
212
345
|
);
|
|
213
|
-
} else if (refChild) {
|
|
214
|
-
insertBefore(parent, newChild, refChild);
|
|
215
|
-
} else {
|
|
216
|
-
appendChild(parent, newChild);
|
|
217
346
|
}
|
|
347
|
+
if (refChild) insertBefore(parent, newChild, refChild);
|
|
348
|
+
else appendChild(parent, newChild);
|
|
218
349
|
}
|
|
219
350
|
this.surface.requestCommit();
|
|
220
351
|
}
|
|
@@ -224,21 +355,17 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
224
355
|
// surface.children with no parent). Angular's `parent` arg is ignored in favor of the
|
|
225
356
|
// authoritative link, mirroring the Vue adapter's remove.
|
|
226
357
|
countAngular('nodesRemoved');
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
dlog(
|
|
235
|
-
`Angular renderer removeChild angularParent=${angularParent} retainedParent=${retainedParent} child=${describeHost(oldChild)} viaProjection=${wasProjected}`,
|
|
236
|
-
);
|
|
237
|
-
if (!wasProjected) {
|
|
238
|
-
const parent = oldChild.parent;
|
|
239
|
-
if (parent !== undefined) removeChild(parent, oldChild);
|
|
240
|
-
else this.surface.removeChild(oldChild);
|
|
358
|
+
if (isDebug()) {
|
|
359
|
+
const angularParent = _parent !== null ? describeHost(_parent) : 'null';
|
|
360
|
+
const retainedParent =
|
|
361
|
+
oldChild.parent !== undefined ? describeHost(oldChild.parent) : 'none';
|
|
362
|
+
dlog(
|
|
363
|
+
`Angular renderer removeChild angularParent=${angularParent} retainedParent=${retainedParent} child=${describeHost(oldChild)}`,
|
|
364
|
+
);
|
|
241
365
|
}
|
|
366
|
+
const parent = oldChild.parent;
|
|
367
|
+
if (parent !== undefined) removeChild(parent, oldChild);
|
|
368
|
+
else this.surface.removeChild(oldChild);
|
|
242
369
|
this.surface.requestCommit();
|
|
243
370
|
}
|
|
244
371
|
|
|
@@ -278,7 +405,7 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
278
405
|
if (isSurface(el)) return;
|
|
279
406
|
countAngular('rendererWrites');
|
|
280
407
|
noteAngularWrite(name);
|
|
281
|
-
routeProp(el, name, value);
|
|
408
|
+
routeProp(el, aliasedPropName(name), value);
|
|
282
409
|
this.surface.requestCommit();
|
|
283
410
|
}
|
|
284
411
|
|
|
@@ -286,7 +413,8 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
286
413
|
if (isSurface(el)) return;
|
|
287
414
|
countAngular('rendererWrites');
|
|
288
415
|
noteAngularWrite(name);
|
|
289
|
-
|
|
416
|
+
const aliased = aliasedPropName(name);
|
|
417
|
+
routeProp(el, aliased, textDefaultFor(el, aliased));
|
|
290
418
|
this.surface.requestCommit();
|
|
291
419
|
}
|
|
292
420
|
|
|
@@ -353,7 +481,12 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
353
481
|
if (isSurface(el)) return;
|
|
354
482
|
countAngular('rendererWrites');
|
|
355
483
|
noteAngularWrite(name);
|
|
356
|
-
|
|
484
|
+
const aliased = aliasedPropName(name);
|
|
485
|
+
routeProp(
|
|
486
|
+
el,
|
|
487
|
+
aliased,
|
|
488
|
+
value === undefined ? textDefaultFor(el, aliased) : value,
|
|
489
|
+
);
|
|
357
490
|
this.surface.requestCommit();
|
|
358
491
|
}
|
|
359
492
|
|
|
@@ -362,7 +495,9 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
362
495
|
noteAngularWrite('#text');
|
|
363
496
|
// A useful permanent seam: text mutations are low-frequency and the one place a stale
|
|
364
497
|
// `{{binding}}` (a change-detection gap) shows up as "the setValue never fired".
|
|
365
|
-
|
|
498
|
+
if (isDebug()) {
|
|
499
|
+
dlog(`Angular renderer setValue "${value}" on ${describeHost(node)}`);
|
|
500
|
+
}
|
|
366
501
|
setText(node, value);
|
|
367
502
|
this.surface.requestCommit();
|
|
368
503
|
}
|
|
@@ -377,7 +512,34 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
377
512
|
callback: (event: unknown) => boolean | void,
|
|
378
513
|
): () => void {
|
|
379
514
|
if (!isSymbioteNode(target)) return () => {};
|
|
380
|
-
|
|
515
|
+
// `[(value)]` desugars to `(valueChange)`, which is the spelling every Angular template writes
|
|
516
|
+
// for a Switch or a TextInput. On the COMPONENT path it is an `@Output()` the wrapper derives
|
|
517
|
+
// from the raw `change` payload; on a LOWERED element there is no component, and registering
|
|
518
|
+
// `valueChange` as an engine event would wait forever for a Fabric event of that name.
|
|
519
|
+
//
|
|
520
|
+
// The lowered path already carries the same fold under RN's own spelling: both behaviors call
|
|
521
|
+
// `node.props.onValueChange(event)` — a plain function PROP, not an event
|
|
522
|
+
// (`behaviors/switch.ts`, `behaviors/text-input.ts`), with `text`/`value` carried as a FIELD on
|
|
523
|
+
// the event object rather than a second argument (Svelte forces every individual `on*` prop
|
|
524
|
+
// through a native listener that calls with exactly one argument, always a real object). So
|
|
525
|
+
// Angular's own `[(value)]` sugar needs one extra step its React/Vue/Solid counterparts do not:
|
|
526
|
+
// unwrap that field back to a bare value before handing it to Angular's callback, or `text =
|
|
527
|
+
// $event` would assign the whole event object instead of the typed string/boolean.
|
|
528
|
+
if (eventName === VALUE_CHANGE_EVENT) {
|
|
529
|
+
const forwardValue = withChangeDetection(target, (event: unknown) => {
|
|
530
|
+
if (isSymbioteEvent(event)) {
|
|
531
|
+
if ('text' in event) return callback(event.text);
|
|
532
|
+
if ('value' in event) return callback(event.value);
|
|
533
|
+
}
|
|
534
|
+
return callback(event);
|
|
535
|
+
});
|
|
536
|
+
routeProp(target, VALUE_CHANGE_PROP, forwardValue);
|
|
537
|
+
return () => routeProp(target, VALUE_CHANGE_PROP, undefined);
|
|
538
|
+
}
|
|
539
|
+
const listener = READ_BACK_EVENTS.has(eventName)
|
|
540
|
+
? withChangeDetection(target, callback)
|
|
541
|
+
: callback;
|
|
542
|
+
setEventListener(target, eventName, listener);
|
|
381
543
|
return () => setEventListener(target, eventName, undefined);
|
|
382
544
|
}
|
|
383
545
|
}
|
|
@@ -397,13 +559,10 @@ export class SymbioteRendererFactory implements RendererFactory2 {
|
|
|
397
559
|
return (this.renderer ??= new SymbioteRenderer(this.surface));
|
|
398
560
|
}
|
|
399
561
|
|
|
400
|
-
// Not commit coalescing (requestCommit owns that) —
|
|
401
|
-
//
|
|
402
|
-
// the
|
|
403
|
-
// commit is still only queued. Sticky projection reconciles are batched to here for both reasons;
|
|
404
|
-
// see flushScrollViewProjections.
|
|
562
|
+
// Not commit coalescing (requestCommit owns that) — a per-CD-pass counter only, now that the
|
|
563
|
+
// ScrollView projection bridge this used to also flush is gone (`../register.ts`'s
|
|
564
|
+
// `registerScrollViewBehavior` owns the content node and the sticky seam for every adapter).
|
|
405
565
|
end(): void {
|
|
406
566
|
countAngular('cdPasses');
|
|
407
|
-
flushScrollViewProjections();
|
|
408
567
|
}
|
|
409
568
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The two names Angular's `[(value)]` sugar rides on, in a dependency-free LEAF so `elements.ts`
|
|
2
|
+
// can name the event without importing the require-cyclic `renderer/index.ts` (same reasoning as
|
|
3
|
+
// `anchor-host-registry.ts`).
|
|
4
|
+
//
|
|
5
|
+
// `[(value)]` desugars to a `(valueChange)` binding, which is no Fabric event — the renderer
|
|
6
|
+
// translates it to the `onValueChange` PROP both behaviors already call.
|
|
7
|
+
export const VALUE_CHANGE_EVENT = 'valueChange';
|
|
8
|
+
export const VALUE_CHANGE_PROP = 'onValueChange';
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ActivityIndicatorBase } from './shared';
|
|
2
|
-
import * as i0 from "@angular/core";
|
|
3
|
-
import * as i1 from "../../primitives/shared";
|
|
4
|
-
export type { IActivityIndicatorProps } from './shared';
|
|
5
|
-
export declare class ActivityIndicator extends ActivityIndicatorBase {
|
|
6
|
-
protected readonly defaultColor: null;
|
|
7
|
-
protected readonly nativeExtras: {
|
|
8
|
-
styleAttr: string;
|
|
9
|
-
indeterminate: boolean;
|
|
10
|
-
};
|
|
11
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<ActivityIndicator, never>;
|
|
12
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<ActivityIndicator, "ActivityIndicator", never, {}, {}, never, never, true, [{ directive: typeof i1.SymbioteStyleInputDirective; inputs: { "style": "style"; }; outputs: {}; }]>;
|
|
13
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
-
import { DescriptorOutlet } from '../../descriptor-to-angular';
|
|
3
|
-
import { ActivityIndicatorBase } from './shared';
|
|
4
|
-
import { SymbioteStyleInputDirective } from '../../primitives';
|
|
5
|
-
import * as i0 from "@angular/core";
|
|
6
|
-
import * as i1 from "../../primitives/shared";
|
|
7
|
-
export class ActivityIndicator extends ActivityIndicatorBase {
|
|
8
|
-
defaultColor = null;
|
|
9
|
-
nativeExtras = {
|
|
10
|
-
styleAttr: 'Normal',
|
|
11
|
-
indeterminate: true,
|
|
12
|
-
};
|
|
13
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ActivityIndicator, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
14
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: ActivityIndicator, isStandalone: true, selector: "ActivityIndicator", usesInheritance: true, hostDirectives: [{ directive: i1.SymbioteStyleInputDirective, inputs: ["style", "style"] }], ngImport: i0, template: `<symbiote-descriptor-outlet [node]="descriptor" />`, isInline: true, dependencies: [{ kind: "component", type: DescriptorOutlet, selector: "symbiote-descriptor-outlet", inputs: ["node"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15
|
-
}
|
|
16
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ActivityIndicator, decorators: [{
|
|
17
|
-
type: Component,
|
|
18
|
-
args: [{
|
|
19
|
-
selector: 'ActivityIndicator',
|
|
20
|
-
standalone: true,
|
|
21
|
-
hostDirectives: [
|
|
22
|
-
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
23
|
-
],
|
|
24
|
-
imports: [DescriptorOutlet],
|
|
25
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
26
|
-
template: `<symbiote-descriptor-outlet [node]="descriptor" />`,
|
|
27
|
-
}]
|
|
28
|
-
}] });
|
|
29
|
-
//# sourceMappingURL=index.android.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.android.js","sourceRoot":"","sources":["../../../../src/components/activity-indicator/index.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;;;AAa/D,MAAM,OAAO,iBAAkB,SAAQ,qBAAqB;IACvC,YAAY,GAAG,IAAI,CAAC;IACX,YAAY,GAAG;QACzC,SAAS,EAAE,QAAQ;QACnB,aAAa,EAAE,IAAI;KACpB,CAAC;uGALS,iBAAiB;2FAAjB,iBAAiB,iMAFlB,oDAAoD,4DAFpD,gBAAgB;;2FAIf,iBAAiB;kBAV7B,SAAS;mBAAC;oBACT,QAAQ,EAAE,mBAAmB;oBAC7B,UAAU,EAAE,IAAI;oBAChB,cAAc,EAAE;wBACd,EAAE,SAAS,EAAE,2BAA2B,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE;qBAC9D;oBACD,OAAO,EAAE,CAAC,gBAAgB,CAAC;oBAC3B,eAAe,EAAE,uBAAuB,CAAC,MAAM;oBAC/C,QAAQ,EAAE,oDAAoD;iBAC/D"}
|