@symbiote-native/angular 2.0.1 → 3.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 +5 -4
- package/babel-register-composed.cjs +0 -8
- package/build/angular/callback-host.d.ts +25 -0
- package/build/angular/callback-host.js +211 -0
- package/build/angular/callback-host.js.map +1 -0
- package/build/angular/change-detection-flush.d.ts +14 -2
- package/build/angular/change-detection-flush.js +54 -6
- package/build/angular/change-detection-flush.js.map +1 -1
- package/build/angular/components/flat-list/index.js +39 -86
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image-shared.js +49 -110
- package/build/angular/components/image-shared.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +33 -72
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.js +58 -103
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable-props.d.ts +2 -0
- package/build/angular/components/section-list/index.js +20 -103
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/touchable-props.d.ts +1 -0
- package/build/angular/components/virtualized-list/directives.js +32 -22
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +7 -1
- package/build/angular/components/virtualized-list/index.js +350 -359
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js +20 -12
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.js +36 -142
- 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 +1 -1
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.js +9 -8
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js +13 -12
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +6 -6
- package/build/angular/descriptor-to-angular/index.js.map +1 -1
- package/build/angular/diagnostics.d.ts +13 -0
- package/build/angular/diagnostics.js +18 -0
- package/build/angular/diagnostics.js.map +1 -1
- package/build/angular/element-props.d.ts +1 -3
- package/build/angular/elements.d.ts +46 -32
- package/build/angular/elements.js +217 -434
- package/build/angular/elements.js.map +1 -1
- package/build/angular/index.d.ts +3 -1
- package/build/angular/index.js +44 -13
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/animated-leaf-binder.js +0 -2
- package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.js +69 -74
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/animated/index.d.ts +30 -19
- package/build/angular/modules/status-bar/index.js +2 -8
- package/build/angular/modules/status-bar/index.js.map +1 -1
- package/build/angular/primitives/index.d.ts +14 -28
- package/build/angular/primitives/index.js +44 -107
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +1 -0
- package/build/angular/primitives/shared.js +47 -32
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/render/index.js +34 -1
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.d.ts +56 -0
- package/build/angular/renderer/index.js +295 -114
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/runtime-matching.d.ts +9 -0
- package/build/angular/runtime-matching.js +82 -0
- package/build/angular/runtime-matching.js.map +1 -0
- package/build/angular/services/color-scheme.service.js +4 -4
- package/build/angular/services/color-scheme.service.js.map +1 -1
- package/build/angular/services/window-dimensions.service.js +4 -4
- package/build/angular/services/window-dimensions.service.js.map +1 -1
- package/build/angular/style-host.d.ts +11 -0
- package/build/angular/style-host.js +86 -0
- package/build/angular/style-host.js.map +1 -0
- package/package.json +5 -5
- package/src/callback-host.ts +229 -0
- package/src/change-detection-flush.ts +62 -9
- package/src/components/flat-list/index.ts +2 -1
- package/src/components/keyboard-avoiding-view/index.ts +8 -0
- package/src/components/pressable-props.ts +8 -0
- package/src/components/touchable-props.ts +4 -0
- package/src/components/virtualized-list/index.ts +98 -45
- package/src/components.ts +0 -2
- package/src/diagnostics.ts +18 -0
- package/src/element-props.ts +1 -5
- package/src/elements.ts +159 -38
- package/src/index.ts +47 -14
- package/src/primitives/index.ts +14 -70
- package/src/primitives/shared.ts +21 -6
- package/src/render/index.ts +44 -0
- package/src/renderer/index.ts +347 -104
- package/src/runtime-matching.ts +80 -0
- package/src/style-host.ts +94 -0
- package/tsconfig.angular.base.json +11 -0
package/src/elements.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
ChangeDetectorRef,
|
|
37
37
|
Directive,
|
|
38
38
|
ElementRef,
|
|
39
|
+
ErrorHandler,
|
|
39
40
|
EventEmitter,
|
|
40
41
|
Input,
|
|
41
42
|
Output,
|
|
@@ -52,10 +53,12 @@ import type {
|
|
|
52
53
|
import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms';
|
|
53
54
|
import { VALUE_CHANGE_EVENT } from './renderer/value-change';
|
|
54
55
|
import {
|
|
55
|
-
createCallbackWrapper,
|
|
56
56
|
registerViewFlush,
|
|
57
57
|
unregisterViewFlush,
|
|
58
58
|
} from './change-detection-flush';
|
|
59
|
+
import { SymbioteCallbackHost } from './callback-host';
|
|
60
|
+
import { withholdFromRuntimeMatching } from './runtime-matching';
|
|
61
|
+
import { SymbioteStyleHost } from './style-host';
|
|
59
62
|
import type {
|
|
60
63
|
IActivityIndicatorProps,
|
|
61
64
|
IImageProps,
|
|
@@ -89,21 +92,25 @@ import type { IAngularScrollViewProps } from './components/scroll-view-props';
|
|
|
89
92
|
* — so without this loop a bare tag commits nothing at all. It stays generic on purpose: per-prop
|
|
90
93
|
* forwarding code is a component wrapper by another name, which is what this migration removes.
|
|
91
94
|
* `renderer.setProperty` lands in the adapter's own renderer, which routes the value through the
|
|
92
|
-
* engine's `routeProp` and applies the `id` -> `nativeID`
|
|
95
|
+
* engine's `routeProp` and applies the `id` -> `nativeID` alias.
|
|
93
96
|
*/
|
|
94
97
|
@Directive()
|
|
95
98
|
export abstract class SymbioteElement implements OnChanges {
|
|
96
99
|
private readonly renderer = inject(Renderer2);
|
|
97
100
|
protected readonly host = inject(ElementRef);
|
|
98
|
-
protected readonly detector = inject(ChangeDetectorRef);
|
|
99
101
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// `
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
// NO `ChangeDetectorRef` HERE, and its absence is the point. This class is instantiated once per
|
|
103
|
+
// ELEMENT, so injecting one cost ~1.0-2.4 us on every tag of a screen to serve the few that carry
|
|
104
|
+
// an `on*` prop — ten thousand `ViewRef`s on a thousand-row create, read by none of them
|
|
105
|
+
// (`core/engine/cpp/tests/js/angular-directive-cost.itest.ts`). `SymbioteCallbackHost` owns one and
|
|
106
|
+
// MATCHES on the callback attributes instead, registering it against the node; `markViewFor` is how
|
|
107
|
+
// the wrapper reaches it. The inputs did not move, so nothing about this file's public surface did.
|
|
108
|
+
|
|
109
|
+
// THE `on*` WRAPPER LEFT THIS CLASS on 2026-09-18, for `SymbioteRenderer.setProperty`. Most of
|
|
110
|
+
// these directives are withheld from runtime matching now (`./runtime-matching`), so a binding
|
|
111
|
+
// reaches the renderer through `ɵɵproperty` without passing through any directive — a wrapper that
|
|
112
|
+
// lives here would simply stop running. The renderer is reached by every path, which is the home it
|
|
113
|
+
// should have had.
|
|
107
114
|
|
|
108
115
|
@Input() testID?: IElementProps['testID'];
|
|
109
116
|
@Input() nativeID?: IElementProps['nativeID'];
|
|
@@ -183,7 +190,6 @@ export abstract class SymbioteElement implements OnChanges {
|
|
|
183
190
|
@Input() shouldRasterizeIOS?: IElementProps['shouldRasterizeIOS'];
|
|
184
191
|
@Input()
|
|
185
192
|
needsOffscreenAlphaCompositing?: IElementProps['needsOffscreenAlphaCompositing'];
|
|
186
|
-
@Input() symbioteStyle?: IElementProps['symbioteStyle'];
|
|
187
193
|
// Declared, and the reason is the one `element-props.ts` used to give for NOT declaring it —
|
|
188
194
|
// reversed by measurement. `[style]` on an element with no directive input reaches Angular's own
|
|
189
195
|
// CSS styling engine (`ɵɵstyleMap`), which cannot represent an RN StyleProp: an ARRAY decomposes
|
|
@@ -205,6 +211,25 @@ export abstract class SymbioteElement implements OnChanges {
|
|
|
205
211
|
// in what commits.
|
|
206
212
|
@Input() style?: IElementProps['style'];
|
|
207
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Declared so Angular SHADOWS the `[class]` binding into it instead of decomposing the string.
|
|
216
|
+
*
|
|
217
|
+
* A styling binding goes to a directive input when the directive declares that exact public name
|
|
218
|
+
* — `setShadowStylingInputFlags` sets `hasClassInput`/`hasStyleInput` off the input map, and
|
|
219
|
+
* `checkStylingMap` then hands the whole value over and never calls the renderer per key
|
|
220
|
+
* (`view/directives.ts`, `instructions/styling.ts`). `style` has been declared here all along and
|
|
221
|
+
* is shadowed; `class` was not, so every `[class]` reached the renderer one TOKEN at a time — on
|
|
222
|
+
* the element-directive path as much as the bare one. Measured at ~3.3 us per class binding
|
|
223
|
+
* (`adapter-create-cost.itest.ts`, "prices the class channel"), on the channel every example app
|
|
224
|
+
* uses for its static look.
|
|
225
|
+
*
|
|
226
|
+
* `[class.foo]` and `[ngClass]` are NOT shadowed and keep arriving as `addClass`, so a node can
|
|
227
|
+
* now be told its classes both ways at once; the renderer unions the two sources
|
|
228
|
+
* (`classStringFor`). Typed as a string rather than RN's `className`, because that is what
|
|
229
|
+
* `ɵɵclassMap` hands over after it concatenates any static `class=` prefix.
|
|
230
|
+
*/
|
|
231
|
+
@Input() class?: string;
|
|
232
|
+
|
|
208
233
|
// The flat-bag spelling of the events below. `(press)` and `[onPress]` are both supported and
|
|
209
234
|
// land in the same place; an app that already holds a handler bag binds the props.
|
|
210
235
|
@Input() onPress?: IElementProps['onPress'];
|
|
@@ -221,7 +246,9 @@ export abstract class SymbioteElement implements OnChanges {
|
|
|
221
246
|
this.renderer.setProperty(
|
|
222
247
|
this.host.nativeElement,
|
|
223
248
|
name,
|
|
224
|
-
|
|
249
|
+
// Unwrapped on purpose: `SymbioteRenderer.setProperty` is where an `on*` value is wrapped
|
|
250
|
+
// now, and it is the call this line makes.
|
|
251
|
+
changes[name]?.currentValue,
|
|
225
252
|
);
|
|
226
253
|
}
|
|
227
254
|
}
|
|
@@ -239,11 +266,40 @@ export abstract class SymbioteElement implements OnChanges {
|
|
|
239
266
|
*/
|
|
240
267
|
@Directive()
|
|
241
268
|
abstract class ReadBackElement extends SymbioteElement implements OnDestroy {
|
|
269
|
+
// ITS OWN, now that the base has none. Three tags reach this class, so the injection is paid where
|
|
270
|
+
// it is read instead of on every element of a screen.
|
|
271
|
+
private readonly detector = inject(ChangeDetectorRef);
|
|
272
|
+
private readonly errorHandler = inject(ErrorHandler);
|
|
273
|
+
|
|
242
274
|
constructor() {
|
|
243
275
|
super();
|
|
244
276
|
const node: unknown = this.host.nativeElement;
|
|
245
277
|
if (typeof node === 'object' && node !== null) {
|
|
246
|
-
registerViewFlush(node, () => this.
|
|
278
|
+
registerViewFlush(node, () => this.flush());
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// REPORTED, NOT RETHROWN, and it is Angular's own contract rather than a swallow. Every other
|
|
283
|
+
// change detection in an app runs inside `ApplicationRef.tick()`, which catches and hands the
|
|
284
|
+
// error to `ErrorHandler` — `render/index.ts` provides `SymbioteErrorHandler` for exactly that,
|
|
285
|
+
// after an unprovided token once turned every async tick exception into a hard crash. This flush
|
|
286
|
+
// is the ONE change detection that runs outside that boundary: the engine calls it from inside a
|
|
287
|
+
// native event dispatch, where a throw has nowhere to go but `RCTFatal`.
|
|
288
|
+
//
|
|
289
|
+
// Device-diagnosed 2026-09-20 on ApiPlaygroundScreen. `PlaygroundLifecycleLogger` emits from
|
|
290
|
+
// `ngDoCheck`/`ngAfterContentChecked`/`ngAfterViewChecked` and the screen's handler writes a
|
|
291
|
+
// signal its own template reads, so the view re-dirties itself on every pass and
|
|
292
|
+
// `detectChangesInViewWhileDirty` throws NG0103 after MAXIMUM_REFRESH_RERUNS. The scheduler's own
|
|
293
|
+
// ticks were already hitting it and reporting it quietly; the first KEYSTROKE took the same throw
|
|
294
|
+
// through here and killed the app — `Terminating app due to uncaught exception
|
|
295
|
+
// 'RCTFatalException: Unhandled JS Exception: Error: NG0103'`, with no redbox because Release has
|
|
296
|
+
// none. So the app bug is the app's, and a flush that turns a reported error into a fatal one is
|
|
297
|
+
// ours: a keystroke must not be stricter than a tick.
|
|
298
|
+
private flush(): void {
|
|
299
|
+
try {
|
|
300
|
+
this.detector.detectChanges();
|
|
301
|
+
} catch (error: unknown) {
|
|
302
|
+
this.errorHandler.handleError(error);
|
|
247
303
|
}
|
|
248
304
|
}
|
|
249
305
|
|
|
@@ -253,13 +309,31 @@ abstract class ReadBackElement extends SymbioteElement implements OnDestroy {
|
|
|
253
309
|
}
|
|
254
310
|
}
|
|
255
311
|
|
|
256
|
-
|
|
312
|
+
// BOTH SPELLINGS ON THE SEVEN DASHLESS TAGS, and it is a correctness fix rather than a convenience.
|
|
313
|
+
//
|
|
314
|
+
// The renderer has always mapped `symbiote-view` onto `view` (`PRIMITIVE_SELECTOR_ALIAS`), because
|
|
315
|
+
// `CUSTOM_ELEMENTS_SCHEMA` admits an unknown element only when the name carries a hyphen — so the
|
|
316
|
+
// hyphenated form is the spelling an app WITHOUT these directives has to use. It is the same tag and
|
|
317
|
+
// commits the same node.
|
|
318
|
+
//
|
|
319
|
+
// It was not the same tag HERE. A selector of `view` alone meant an app that imports
|
|
320
|
+
// `SYMBIOTE_ELEMENTS` and writes `<symbiote-view>` matched nothing: no type check on its props, no
|
|
321
|
+
// declared inputs, none of `SymbioteElement`'s forwarding or callback wrapping — silently, with the
|
|
322
|
+
// tree still looking right. Measured, that shape also ran ~8.6 us per element FASTER, which is what
|
|
323
|
+
// made it look like an optimization instead of a hole (`angular-directive-cost.itest.ts`).
|
|
324
|
+
//
|
|
325
|
+
// Only the seven dashless tags need it: `hyphenatedIntrinsicAliases` skips any tag that already
|
|
326
|
+
// carries a dash, so `symbiote-scroll-view` resolves nowhere on either side and the two halves agree
|
|
327
|
+
// already.
|
|
328
|
+
@Directive({ selector: 'view, symbiote-view', standalone: true })
|
|
257
329
|
export class ViewElement extends SymbioteElement {}
|
|
258
330
|
|
|
259
|
-
@Directive({ selector: 'pressable', standalone: true })
|
|
331
|
+
@Directive({ selector: 'pressable, symbiote-pressable', standalone: true })
|
|
260
332
|
export class PressableElement extends SymbioteElement {
|
|
261
333
|
@Input() disabled?: IAngularPressableProps['disabled'];
|
|
262
334
|
@Input() cancelable?: IAngularPressableProps['cancelable'];
|
|
335
|
+
@Input()
|
|
336
|
+
blockNativeResponder?: IAngularPressableProps['blockNativeResponder'];
|
|
263
337
|
@Input() delayLongPress?: IAngularPressableProps['delayLongPress'];
|
|
264
338
|
@Input() delayHoverIn?: IAngularPressableProps['delayHoverIn'];
|
|
265
339
|
@Input() delayHoverOut?: IAngularPressableProps['delayHoverOut'];
|
|
@@ -269,6 +343,9 @@ export class PressableElement extends SymbioteElement {
|
|
|
269
343
|
pressRetentionOffset?: IAngularPressableProps['pressRetentionOffset'];
|
|
270
344
|
@Input() unstable_pressDelay?: IAngularPressableProps['unstable_pressDelay'];
|
|
271
345
|
@Input() android_ripple?: IAngularPressableProps['android_ripple'];
|
|
346
|
+
// ON THE BASE, so both touchables inherit it — the prop is Pressable's AND
|
|
347
|
+
// TouchableHighlight's upstream, and they extend this rather than repeat its surface.
|
|
348
|
+
@Input() testOnly_pressed?: IAngularPressableProps['testOnly_pressed'];
|
|
272
349
|
@Input()
|
|
273
350
|
android_disableSound?: IAngularPressableProps['android_disableSound'];
|
|
274
351
|
@Input() hasTVPreferredFocus?: IAngularPressableProps['hasTVPreferredFocus'];
|
|
@@ -300,6 +377,8 @@ export class TouchableHighlightElement extends PressableElement {
|
|
|
300
377
|
@Input() delayPressIn?: number;
|
|
301
378
|
@Input() delayPressOut?: number;
|
|
302
379
|
@Input() minPressDuration?: number;
|
|
380
|
+
// TouchableHighlight.js:205 — forwarded to Pressability as `android_disableSound`.
|
|
381
|
+
@Input() touchSoundDisabled?: boolean;
|
|
303
382
|
}
|
|
304
383
|
|
|
305
384
|
// RN's TouchableNativeFeedback is a Pressable that CLONES onto its single child instead of
|
|
@@ -310,6 +389,8 @@ export class TouchableHighlightElement extends PressableElement {
|
|
|
310
389
|
export class TouchableNativeFeedbackElement extends PressableElement {
|
|
311
390
|
@Input() background?: INativeFeedbackBackground;
|
|
312
391
|
@Input() useForeground?: boolean;
|
|
392
|
+
// TouchableNativeFeedback.js:228 — forwarded to Pressability as `android_disableSound`.
|
|
393
|
+
@Input() touchSoundDisabled?: boolean;
|
|
313
394
|
}
|
|
314
395
|
|
|
315
396
|
// RN's TouchableWithoutFeedback clones onto its single child the same way
|
|
@@ -321,19 +402,21 @@ export class TouchableWithoutFeedbackElement extends PressableElement {
|
|
|
321
402
|
@Input() delayPressIn?: number;
|
|
322
403
|
@Input() delayPressOut?: number;
|
|
323
404
|
@Input() minPressDuration?: number;
|
|
405
|
+
// TouchableWithoutFeedback.js:199 — forwarded to Pressability as `android_disableSound`.
|
|
406
|
+
@Input() touchSoundDisabled?: boolean;
|
|
324
407
|
}
|
|
325
408
|
|
|
326
409
|
// RN's Button IS a TouchableOpacity (Button.js:384), and the behavior builds the view and the
|
|
327
410
|
// label under it — so the tag takes the touchable's surface plus the four props Button owns. There
|
|
328
411
|
// is no `style`: RN's Button has no such prop, and the label/background come from `color`.
|
|
329
|
-
@Directive({ selector: 'button', standalone: true })
|
|
412
|
+
@Directive({ selector: 'button, symbiote-button', standalone: true })
|
|
330
413
|
export class ButtonElement extends TouchableOpacityElement {
|
|
331
414
|
@Input() title?: string;
|
|
332
415
|
@Input() color?: string;
|
|
333
416
|
@Input() touchSoundDisabled?: boolean;
|
|
334
417
|
}
|
|
335
418
|
|
|
336
|
-
@Directive({ selector: 'text', standalone: true })
|
|
419
|
+
@Directive({ selector: 'text, symbiote-text', standalone: true })
|
|
337
420
|
export class TextElement extends SymbioteElement {
|
|
338
421
|
// Narrows the inherited input to a TEXT style so `fontSize`/`fontWeight` type-check here. The
|
|
339
422
|
// initializer is what TS2612 asks for to accept a redeclaration as deliberate; `declare` would
|
|
@@ -350,7 +433,7 @@ export class TextElement extends SymbioteElement {
|
|
|
350
433
|
@Input() disabled?: ITextElementProps['disabled'];
|
|
351
434
|
}
|
|
352
435
|
|
|
353
|
-
@Directive({ selector: 'image', standalone: true })
|
|
436
|
+
@Directive({ selector: 'image, symbiote-image', standalone: true })
|
|
354
437
|
export class ImageElement extends SymbioteElement {
|
|
355
438
|
@Input() source?: IImageProps['source'];
|
|
356
439
|
@Input() src?: IImageProps['src'];
|
|
@@ -499,7 +582,7 @@ export class HorizontalScrollContentElement extends SymbioteElement {}
|
|
|
499
582
|
* obvious reading is wrong: the file header's "an `@Output` CONSUMES the binding" holds for a
|
|
500
583
|
* COMPONENT, and an element is the other case — Angular attaches the renderer listener for the
|
|
501
584
|
* event as well, so `Renderer2.listen` still runs and the engine still hears the change. Measured
|
|
502
|
-
* under JIT by deleting this whole hook: every case in `
|
|
585
|
+
* under JIT by deleting this whole hook: every case in `renderer/two-way-value.test.ts` stayed
|
|
503
586
|
* green, delivery included, and exactly ONCE (nothing double-fires when both paths exist).
|
|
504
587
|
*
|
|
505
588
|
* It is kept because the measurement is JIT-only and this adapter has a recorded case of JIT and
|
|
@@ -580,6 +663,7 @@ export class TextInputElement extends ValueChangeElement {
|
|
|
580
663
|
@Input() textContentType?: ITextInputProps['textContentType'];
|
|
581
664
|
@Input() underlineColorAndroid?: ITextInputProps['underlineColorAndroid'];
|
|
582
665
|
@Input() onValueChange?: ITextInputProps['onValueChange'];
|
|
666
|
+
@Input() onChangeText?: ITextInputProps['onChangeText'];
|
|
583
667
|
@Input() onContentSizeChange?: ITextInputProps['onContentSizeChange'];
|
|
584
668
|
@Input() onEndEditing?: ITextInputProps['onEndEditing'];
|
|
585
669
|
@Input() onKeyPress?: ITextInputProps['onKeyPress'];
|
|
@@ -604,16 +688,7 @@ export class TextInputElement extends ValueChangeElement {
|
|
|
604
688
|
@Directive({ selector: 'text-input-multiline', standalone: true })
|
|
605
689
|
export class MultilineTextInputElement extends TextInputElement {}
|
|
606
690
|
|
|
607
|
-
|
|
608
|
-
// deliberately does not carry (`component-names/shared.ts`). Declared so the tag alphabet is
|
|
609
|
-
// complete; an app writes the plain name.
|
|
610
|
-
@Directive({ selector: 'text-input-managed', standalone: true })
|
|
611
|
-
export class ManagedTextInputElement extends TextInputElement {}
|
|
612
|
-
|
|
613
|
-
@Directive({ selector: 'text-input-multiline-managed', standalone: true })
|
|
614
|
-
export class ManagedMultilineTextInputElement extends TextInputElement {}
|
|
615
|
-
|
|
616
|
-
@Directive({ selector: 'switch', standalone: true })
|
|
691
|
+
@Directive({ selector: 'switch, symbiote-switch', standalone: true })
|
|
617
692
|
export class SwitchElement extends ValueChangeElement {
|
|
618
693
|
@Input() value?: ISwitchProps['value'];
|
|
619
694
|
@Input() disabled?: ISwitchProps['disabled'];
|
|
@@ -635,9 +710,6 @@ export class SwitchElement extends ValueChangeElement {
|
|
|
635
710
|
}
|
|
636
711
|
}
|
|
637
712
|
|
|
638
|
-
@Directive({ selector: 'switch-managed', standalone: true })
|
|
639
|
-
export class ManagedSwitchElement extends SwitchElement {}
|
|
640
|
-
|
|
641
713
|
/**
|
|
642
714
|
* `[(ngModel)]` / `formControlName` on a `<text-input>` or a `<switch>`.
|
|
643
715
|
*
|
|
@@ -677,6 +749,21 @@ abstract class SymbioteValueAccessor
|
|
|
677
749
|
this.host.nativeElement,
|
|
678
750
|
VALUE_CHANGE_EVENT,
|
|
679
751
|
(value: unknown) => {
|
|
752
|
+
// THE HALF THE DOM DOES FOR FREE, and without it a controlled input undoes every
|
|
753
|
+
// keystroke. `<text-input>`'s behavior re-commands the native text whenever `props.value`
|
|
754
|
+
// disagrees with what native last reported — that is what makes it controlled — and the
|
|
755
|
+
// read-back flush exists so the app's new value is on the node by the time the commit runs.
|
|
756
|
+
// @angular/forms does not reach the node in that window: `NgModel.ngOnChanges` defers
|
|
757
|
+
// `_updateValue` through `resolvedPromise.then`, so `writeValue` lands a MICROTASK after
|
|
758
|
+
// the flush, and the commit in between still reads the value from before the keystroke.
|
|
759
|
+
//
|
|
760
|
+
// In a browser there is nothing to do here: `input.value` already holds what the user
|
|
761
|
+
// typed. `node.props.value` is the same slot, and nothing else writes it — so the accessor
|
|
762
|
+
// mirrors it, which is the shape rather than a workaround. A later `writeValue` still wins,
|
|
763
|
+
// so an app that transforms or refuses the value keeps doing so, one microtask on.
|
|
764
|
+
//
|
|
765
|
+
// Device-reported 2026-09-20: every character snapped the field back to its mounted text.
|
|
766
|
+
this.setProp('value', value);
|
|
680
767
|
fn(value);
|
|
681
768
|
},
|
|
682
769
|
);
|
|
@@ -716,8 +803,7 @@ export class TextInputValueAccessor extends SymbioteValueAccessor {
|
|
|
716
803
|
}
|
|
717
804
|
|
|
718
805
|
@Directive({
|
|
719
|
-
selector:
|
|
720
|
-
'switch[ngModel], switch[formControl], switch[formControlName], switch-managed[ngModel], switch-managed[formControl], switch-managed[formControlName]',
|
|
806
|
+
selector: 'switch[ngModel], switch[formControl], switch[formControlName]',
|
|
721
807
|
standalone: true,
|
|
722
808
|
providers: [
|
|
723
809
|
{
|
|
@@ -753,7 +839,7 @@ export class ActivityIndicatorSpinnerElement extends ActivityIndicatorElement {}
|
|
|
753
839
|
@Directive({ selector: 'safe-area-view', standalone: true })
|
|
754
840
|
export class SafeAreaViewElement extends SymbioteElement {}
|
|
755
841
|
|
|
756
|
-
@Directive({ selector: 'modal', standalone: true })
|
|
842
|
+
@Directive({ selector: 'modal, symbiote-modal', standalone: true })
|
|
757
843
|
export class ModalElement extends SymbioteElement {
|
|
758
844
|
@Input() visible?: IModalViewProps['visible'];
|
|
759
845
|
@Input() transparent?: IModalViewProps['transparent'];
|
|
@@ -824,10 +910,7 @@ export const SYMBIOTE_ELEMENTS = [
|
|
|
824
910
|
HorizontalScrollContentElement,
|
|
825
911
|
TextInputElement,
|
|
826
912
|
MultilineTextInputElement,
|
|
827
|
-
ManagedTextInputElement,
|
|
828
|
-
ManagedMultilineTextInputElement,
|
|
829
913
|
SwitchElement,
|
|
830
|
-
ManagedSwitchElement,
|
|
831
914
|
ActivityIndicatorElement,
|
|
832
915
|
ActivityIndicatorSpinnerElement,
|
|
833
916
|
SafeAreaViewElement,
|
|
@@ -840,8 +923,46 @@ export const SYMBIOTE_ELEMENTS = [
|
|
|
840
923
|
// provided, and `elements.test.ts` subtracts them from its tag-coverage check by name.
|
|
841
924
|
TextInputValueAccessor,
|
|
842
925
|
SwitchValueAccessor,
|
|
926
|
+
// Also not a tag: it matches on the callback ATTRIBUTES, so it lands only on the elements that
|
|
927
|
+
// bind one and carries the `ChangeDetectorRef` their wrapper needs. It rides this list for the same
|
|
928
|
+
// reason the accessors do, and `elements.test.ts` subtracts it from the tag-coverage check by name.
|
|
929
|
+
SymbioteCallbackHost,
|
|
930
|
+
// The other attribute-matched one: it claims `[style]` and `[class]` so an RN style ARRAY never
|
|
931
|
+
// reaches Angular's styling engine, which cannot represent one and throws. See `style-host.ts`.
|
|
932
|
+
SymbioteStyleHost,
|
|
843
933
|
] as const;
|
|
844
934
|
|
|
935
|
+
// THE DIRECTIVES THAT GO ON MATCHING, and the list is the exceptions rather than the rule.
|
|
936
|
+
//
|
|
937
|
+
// A tag directive here is, with four exceptions, nothing but `@Input()` declarations over one
|
|
938
|
+
// inherited `ngOnChanges` that forwards each of them to the renderer — which is the call
|
|
939
|
+
// `ɵɵproperty` makes directly on an element nothing claimed. So the instance buys a compile-time
|
|
940
|
+
// check at ~8.5-9.4 us of run time per element, and `./runtime-matching` keeps the check while
|
|
941
|
+
// dropping the instance.
|
|
942
|
+
//
|
|
943
|
+
// These four cannot go, because they DO something when they are built:
|
|
944
|
+
//
|
|
945
|
+
// text-input, text-input-multiline, switch `ValueChangeElement` — listens for the engine's value
|
|
946
|
+
// event and registers a view flush, which is what stops
|
|
947
|
+
// a controlled value being undone inside one microtask
|
|
948
|
+
// refresh-control `ReadBackElement`, the same flush for the same reason
|
|
949
|
+
//
|
|
950
|
+
// The two form accessors and `SymbioteCallbackHost` are absent from both lists deliberately: they
|
|
951
|
+
// match on an ATTRIBUTE rather than a tag, so they already land only where they are needed.
|
|
952
|
+
withholdFromRuntimeMatching(
|
|
953
|
+
SYMBIOTE_ELEMENTS.filter(
|
|
954
|
+
directive =>
|
|
955
|
+
directive !== TextInputElement &&
|
|
956
|
+
directive !== MultilineTextInputElement &&
|
|
957
|
+
directive !== SwitchElement &&
|
|
958
|
+
directive !== RefreshControlElement &&
|
|
959
|
+
directive !== TextInputValueAccessor &&
|
|
960
|
+
directive !== SwitchValueAccessor &&
|
|
961
|
+
directive !== SymbioteCallbackHost &&
|
|
962
|
+
directive !== SymbioteStyleHost,
|
|
963
|
+
),
|
|
964
|
+
);
|
|
965
|
+
|
|
845
966
|
// A prop this file forgets is not a silent gap — it is `Can't bind to 'x'` in the app that tries
|
|
846
967
|
// it, which is the failure mode a hand-written list produces here. So the lists are checked
|
|
847
968
|
// against the prop TYPES rather than trusted: each assertion below resolves to `true` only when
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
//
|
|
5
5
|
// SEAM SCAFFOLD: mount/unmount + the renderer seam + host intrinsic selectors. Full RN-like
|
|
6
6
|
// composed components still flow through the shared @symbiote-native/components bridge.
|
|
7
|
+
//
|
|
8
|
+
// `View` AND `Text` ARE NO LONGER WORTH IMPORTING, and on 2026-09-18 the examples stopped. They are
|
|
9
|
+
// `@Component`s on the `view` and `text` SELECTORS — a second mechanism on the same tags
|
|
10
|
+
// `SYMBIOTE_ELEMENTS` covers, kept for one stated reason their own header gives: "Declaring `style`
|
|
11
|
+
// as a real Angular input prevents Angular's CSS style engine from decomposing RN `StyleProp`
|
|
12
|
+
// arrays". `SymbioteStyleHost` does exactly that now, for every tag, at a directive's cost rather
|
|
13
|
+
// than a component's.
|
|
14
|
+
//
|
|
15
|
+
// An app loses nothing it cannot spell better: `@ViewChild('ref')` on a template reference returns
|
|
16
|
+
// Angular's own `ElementRef<IHostInstance>`, whose `nativeElement` IS the engine host node — the same
|
|
17
|
+
// property the component exposed, reached without a component instance per element.
|
|
18
|
+
//
|
|
19
|
+
// They stay EXPORTED because the composed components render them and ngtsc resolves a component's
|
|
20
|
+
// template dependencies to importable names (NG3004, the same rule that put the element directives
|
|
21
|
+
// back below). Retiring them means retiring them from those nine components first.
|
|
7
22
|
|
|
8
23
|
import './register';
|
|
9
24
|
|
|
@@ -123,10 +138,28 @@ export type {
|
|
|
123
138
|
} from './components';
|
|
124
139
|
export { setImageSourceResolver } from './components';
|
|
125
140
|
// The element directives that make a HAND-WRITTEN intrinsic tag (`<view>`, `<text-input>`, ...)
|
|
126
|
-
// compile under ngtsc with no schema, and with a real type on every declared prop. `
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// `
|
|
141
|
+
// compile under ngtsc with no schema, and with a real type on every declared prop. See `elements.ts`
|
|
142
|
+
// for why a directive rather than `CUSTOM_ELEMENTS_SCHEMA`/`NO_ERRORS_SCHEMA`.
|
|
143
|
+
//
|
|
144
|
+
// `imports: [SYMBIOTE_ELEMENTS]` IS THE ONLY SUPPORTED SPELLING — and the individual classes are
|
|
145
|
+
// exported anyway, because ngtsc requires it and a build proves it.
|
|
146
|
+
//
|
|
147
|
+
// They were removed on 2026-09-18 and put back the same hour. The reason to remove them is real: a
|
|
148
|
+
// withheld tag directive (`./runtime-matching`) is a compile-time declaration and nothing else, so
|
|
149
|
+
// what makes `<view [style]="[a, b]">` work at RUN time is `SymbioteStyleHost`, and what makes
|
|
150
|
+
// `<view [onPress]="fn">` work at all is `SymbioteCallbackHost` claiming the name before
|
|
151
|
+
// `setDomProperty` throws NG0306. Both ride this array. A narrow `imports: [ViewElement]` type-checks
|
|
152
|
+
// and then fails on a device the first time the app writes an array style.
|
|
153
|
+
//
|
|
154
|
+
// WHAT PUTS THEM BACK is NG3004: `Unable to import directive ViewElement — the symbol is not
|
|
155
|
+
// exported from index.d.ts`. ngtsc resolves every directive reachable through an imported array to an
|
|
156
|
+
// IMPORTABLE NAME in the package's public types, so a class inside `SYMBIOTE_ELEMENTS` that the
|
|
157
|
+
// barrel does not name breaks AOT for every screen that uses the array. The removal passed its own
|
|
158
|
+
// vitest guard and every headless suite, and failed on the first real `ngc` run — the same
|
|
159
|
+
// wrong-harness green this repo keeps finding.
|
|
160
|
+
//
|
|
161
|
+
// So the narrow spelling is DISCOURAGED and not prevented, and `elements-are-exported-for-aot.test.ts`
|
|
162
|
+
// now asserts the opposite invariant: every member of the array is named here.
|
|
130
163
|
export {
|
|
131
164
|
SYMBIOTE_ELEMENTS,
|
|
132
165
|
SymbioteElement,
|
|
@@ -138,9 +171,6 @@ export {
|
|
|
138
171
|
ImageBackgroundElement,
|
|
139
172
|
ImageElement,
|
|
140
173
|
InputAccessoryViewElement,
|
|
141
|
-
ManagedMultilineTextInputElement,
|
|
142
|
-
ManagedSwitchElement,
|
|
143
|
-
ManagedTextInputElement,
|
|
144
174
|
ModalElement,
|
|
145
175
|
MultilineTextInputElement,
|
|
146
176
|
PressableElement,
|
|
@@ -150,22 +180,25 @@ export {
|
|
|
150
180
|
ScrollViewElement,
|
|
151
181
|
StickyHeaderElement,
|
|
152
182
|
SwitchElement,
|
|
183
|
+
SwitchValueAccessor,
|
|
153
184
|
TextElement,
|
|
154
185
|
TextInputElement,
|
|
155
|
-
// The `[(ngModel)]` / `formControl*` accessors for the two controlled tags — where the deleted
|
|
156
|
-
// wrappers' `NG_VALUE_ACCESSOR` went. Both ride `SYMBIOTE_ELEMENTS`; named separately for an app
|
|
157
|
-
// that imports narrowly.
|
|
158
|
-
SwitchValueAccessor,
|
|
159
186
|
TextInputValueAccessor,
|
|
160
|
-
// The three the list omitted while their components still existed. These are now the ONLY
|
|
161
|
-
// replacements for their deleted components in an app's `imports`, so a narrower import has to be
|
|
162
|
-
// able to name them.
|
|
163
187
|
TouchableHighlightElement,
|
|
164
188
|
TouchableNativeFeedbackElement,
|
|
165
189
|
TouchableOpacityElement,
|
|
166
190
|
TouchableWithoutFeedbackElement,
|
|
167
191
|
ViewElement,
|
|
168
192
|
} from './elements';
|
|
193
|
+
// `CALLBACK_ATTRIBUTE_SELECTOR` is exported so a MEASUREMENT can carry the real string rather than a
|
|
194
|
+
// copy of it — the ladder in `core/engine/cpp/tests/js/angular-directive-cost.itest.ts` prices what
|
|
195
|
+
// this selector costs to match, and a second copy there would price a different one the day either
|
|
196
|
+
// drifts. The two host classes are exported for the same reason the array is: they are part of it.
|
|
197
|
+
export {
|
|
198
|
+
CALLBACK_ATTRIBUTE_SELECTOR,
|
|
199
|
+
SymbioteCallbackHost,
|
|
200
|
+
} from './callback-host';
|
|
201
|
+
export { STYLE_HOST_SELECTOR, SymbioteStyleHost } from './style-host';
|
|
169
202
|
export type {
|
|
170
203
|
IElementProps,
|
|
171
204
|
IStickyHeaderElementProps,
|
package/src/primitives/index.ts
CHANGED
|
@@ -3,16 +3,7 @@
|
|
|
3
3
|
// component imports them and renders them in its template. Declaring `style` as a real
|
|
4
4
|
// Angular input prevents Angular's CSS style engine from decomposing RN `StyleProp` arrays.
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
Component,
|
|
8
|
-
Input,
|
|
9
|
-
type OnInit,
|
|
10
|
-
type SimpleChanges,
|
|
11
|
-
} from '@angular/core';
|
|
12
|
-
import {
|
|
13
|
-
resolveTextProps,
|
|
14
|
-
type IEllipsizeMode,
|
|
15
|
-
} from '@symbiote-native/components';
|
|
6
|
+
import { Component } from '@angular/core';
|
|
16
7
|
import { SymbiotePrimitiveHost } from './shared';
|
|
17
8
|
|
|
18
9
|
export {
|
|
@@ -31,46 +22,26 @@ export {
|
|
|
31
22
|
export class ViewHost extends SymbiotePrimitiveHost {}
|
|
32
23
|
|
|
33
24
|
/**
|
|
34
|
-
* Text
|
|
35
|
-
*
|
|
36
|
-
* clamped Text cuts mid-word with no ellipsis — device-observed on the other adapters 2026-08-19.
|
|
25
|
+
* Text is an ORDINARY primitive host as of 2026-09-18, and the two `@Input()`s it used to declare
|
|
26
|
+
* are gone with the code that needed them.
|
|
37
27
|
*
|
|
38
|
-
* They
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
28
|
+
* They existed for one stated reason: a default can only be applied by code that can SEE whether the
|
|
29
|
+
* caller supplied a value, and a pass-through host binding is invisible to the component — so the
|
|
30
|
+
* defaults had to be applied where the inputs were readable. That argument was sound and it is now
|
|
31
|
+
* answered one layer down. The payload builder reads the AUTHORED bag, so it can tell an absent
|
|
32
|
+
* `ellipsizeMode` from an explicit `clip` without anyone declaring anything; the rule is keyed on the
|
|
33
|
+
* component (`foldTextDefaults`, `SymbioteFabricProps.cpp`) and reaches every `RCTText` however it
|
|
34
|
+
* was spelled.
|
|
35
|
+
*
|
|
36
|
+
* The pass-through is therefore the CORRECT path for both props now, and `text-defaults.test.ts`'s
|
|
37
|
+
* "never overwrites a value the caller supplied" is what proves the authored value still arrives.
|
|
42
38
|
*/
|
|
43
39
|
@Component({
|
|
44
40
|
selector: 'text',
|
|
45
41
|
standalone: true,
|
|
46
42
|
template: '<ng-content></ng-content>',
|
|
47
43
|
})
|
|
48
|
-
export class TextHost extends SymbiotePrimitiveHost
|
|
49
|
-
@Input() ellipsizeMode?: IEllipsizeMode;
|
|
50
|
-
@Input() allowFontScaling?: boolean;
|
|
51
|
-
|
|
52
|
-
// ngOnChanges fires before ngOnInit when a binding exists, and not at all when none does —
|
|
53
|
-
// so both hooks are needed to guarantee the defaults land exactly once per settled value.
|
|
54
|
-
ngOnInit(): void {
|
|
55
|
-
this.applyTextDefaults();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
override ngOnChanges(changes: SimpleChanges): void {
|
|
59
|
-
super.ngOnChanges(changes);
|
|
60
|
-
if ('ellipsizeMode' in changes || 'allowFontScaling' in changes) {
|
|
61
|
-
this.applyTextDefaults();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private applyTextDefaults(): void {
|
|
66
|
-
const resolved = resolveTextProps({
|
|
67
|
-
ellipsizeMode: this.ellipsizeMode,
|
|
68
|
-
allowFontScaling: this.allowFontScaling,
|
|
69
|
-
});
|
|
70
|
-
this.setHostProp('ellipsizeMode', resolved.ellipsizeMode);
|
|
71
|
-
this.setHostProp('allowFontScaling', resolved.allowFontScaling);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
44
|
+
export class TextHost extends SymbiotePrimitiveHost {}
|
|
74
45
|
|
|
75
46
|
@Component({
|
|
76
47
|
selector: 'image',
|
|
@@ -107,9 +78,6 @@ export class HorizontalScrollView extends SymbiotePrimitiveHost {}
|
|
|
107
78
|
})
|
|
108
79
|
export class HorizontalScrollContentView extends SymbiotePrimitiveHost {}
|
|
109
80
|
|
|
110
|
-
// The pair the LOWERED path commits — declared so the tag alphabet stays complete, though no
|
|
111
|
-
// Angular template renders them: this adapter has no lowering transform. The `-managed` pair below
|
|
112
|
-
// is what its own TextInput renders.
|
|
113
81
|
@Component({
|
|
114
82
|
selector: 'text-input',
|
|
115
83
|
standalone: true,
|
|
@@ -124,20 +92,6 @@ export class TextInputHost extends SymbiotePrimitiveHost {}
|
|
|
124
92
|
})
|
|
125
93
|
export class MultilineTextInputHost extends SymbiotePrimitiveHost {}
|
|
126
94
|
|
|
127
|
-
@Component({
|
|
128
|
-
selector: 'text-input-managed',
|
|
129
|
-
standalone: true,
|
|
130
|
-
template: '<ng-content></ng-content>',
|
|
131
|
-
})
|
|
132
|
-
export class ManagedTextInputHost extends SymbiotePrimitiveHost {}
|
|
133
|
-
|
|
134
|
-
@Component({
|
|
135
|
-
selector: 'text-input-multiline-managed',
|
|
136
|
-
standalone: true,
|
|
137
|
-
template: '<ng-content></ng-content>',
|
|
138
|
-
})
|
|
139
|
-
export class ManagedMultilineTextInputHost extends SymbiotePrimitiveHost {}
|
|
140
|
-
|
|
141
95
|
@Component({
|
|
142
96
|
selector: 'switch',
|
|
143
97
|
standalone: true,
|
|
@@ -145,16 +99,6 @@ export class ManagedMultilineTextInputHost extends SymbiotePrimitiveHost {}
|
|
|
145
99
|
})
|
|
146
100
|
export class SwitchHost extends SymbiotePrimitiveHost {}
|
|
147
101
|
|
|
148
|
-
// The component path's spelling — same native Switch/AndroidSwitch, a tag the engine's Switch
|
|
149
|
-
// behavior does not carry. See `component-names/shared.ts` for why the wrapper may not share the
|
|
150
|
-
// lowered tag; mirrors `ManagedTextInputHost` above.
|
|
151
|
-
@Component({
|
|
152
|
-
selector: 'switch-managed',
|
|
153
|
-
standalone: true,
|
|
154
|
-
template: '<ng-content></ng-content>',
|
|
155
|
-
})
|
|
156
|
-
export class ManagedSwitchHost extends SymbiotePrimitiveHost {}
|
|
157
|
-
|
|
158
102
|
// The centering RCTView RN wraps the spinner in (ActivityIndicator.js:112), not the spinner —
|
|
159
103
|
// which is what this tag resolved to until 2026-09-09. The native view moved to
|
|
160
104
|
// `activity-indicator-spinner`, built by the engine's ActivityIndicator behavior, so nothing writes
|
package/src/primitives/shared.ts
CHANGED
|
@@ -10,10 +10,15 @@ import {
|
|
|
10
10
|
type SimpleChanges,
|
|
11
11
|
} from '@angular/core';
|
|
12
12
|
import { countAngular } from '../diagnostics';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createCallbackWrapper,
|
|
15
|
+
registerViewMarker,
|
|
16
|
+
type ICallbackWrapper,
|
|
17
|
+
} from '../change-detection-flush';
|
|
14
18
|
import {
|
|
15
19
|
flattenStyle,
|
|
16
20
|
isSymbioteNode,
|
|
21
|
+
propOf,
|
|
17
22
|
type IHostInstance,
|
|
18
23
|
type IStyleProp,
|
|
19
24
|
} from '@symbiote-native/engine';
|
|
@@ -43,7 +48,7 @@ import {
|
|
|
43
48
|
*/
|
|
44
49
|
export function anchorHostStyle(elementRef: ElementRef<unknown>): unknown {
|
|
45
50
|
const node = elementRef.nativeElement;
|
|
46
|
-
return isSymbioteNode(node) ? node
|
|
51
|
+
return isSymbioteNode(node) ? propOf(node, 'style') : undefined;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
// anchorHostStyle returns `unknown` (it reads whatever already resolved onto the anchor node's
|
|
@@ -259,10 +264,20 @@ export class SymbioteHostPropsDirective {
|
|
|
259
264
|
// binds its result: the "pan does nothing" bug. Shared with the TAG path's `SymbioteElement`,
|
|
260
265
|
// which has the identical deficit; the mechanism, the `onScroll` exemption and why the wrapper
|
|
261
266
|
// is cached per original handler live in `createCallbackWrapper`.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
267
|
+
//
|
|
268
|
+
// THE REF REACHES THE WRAPPER BY THE NODE NOW, not as an argument. `SymbioteElement` stopped
|
|
269
|
+
// injecting one per element — it was ~1-2.4 us on every tag of a screen to serve the few that carry
|
|
270
|
+
// a callback — so `createCallbackWrapper` looks its marker up in `markViewFor`. This directive
|
|
271
|
+
// keeps its own injection because it is matched by an ATTRIBUTE a composed component writes
|
|
272
|
+
// deliberately, not by every element, and registers it here for the same node the wrapper holds.
|
|
273
|
+
private readonly wrapCallback = this.registerMarkerAndWrap();
|
|
274
|
+
|
|
275
|
+
private registerMarkerAndWrap(): ICallbackWrapper {
|
|
276
|
+
const node: unknown = this.elementRef.nativeElement;
|
|
277
|
+
if (typeof node === 'object' && node !== null)
|
|
278
|
+
registerViewMarker(node, this.cdr);
|
|
279
|
+
return createCallbackWrapper(node);
|
|
280
|
+
}
|
|
266
281
|
|
|
267
282
|
// Values pushed by the previous bag, so a push writes only what actually moved. Copied rather
|
|
268
283
|
// than aliased: a component is free to hand back the same object with mutated fields.
|