@symbiote-native/angular 3.0.2 → 3.1.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.
Files changed (55) hide show
  1. package/build/angular/callback-host.d.ts +2 -5
  2. package/build/angular/callback-host.js +11 -36
  3. package/build/angular/callback-host.js.map +1 -1
  4. package/build/angular/change-detection-flush.d.ts +1 -0
  5. package/build/angular/change-detection-flush.js +58 -14
  6. package/build/angular/change-detection-flush.js.map +1 -1
  7. package/build/angular/components/flat-list/index.d.ts +4 -2
  8. package/build/angular/components/flat-list/index.js +28 -78
  9. package/build/angular/components/flat-list/index.js.map +1 -1
  10. package/build/angular/components/image-shared.d.ts +8 -2
  11. package/build/angular/components/modal/index.d.ts +1 -0
  12. package/build/angular/components/modal/index.js +23 -16
  13. package/build/angular/components/modal/index.js.map +1 -1
  14. package/build/angular/components/section-list/index.d.ts +3 -1
  15. package/build/angular/components/section-list/index.js +10 -2
  16. package/build/angular/components/section-list/index.js.map +1 -1
  17. package/build/angular/components/virtualized-list/directives.js +7 -4
  18. package/build/angular/components/virtualized-list/directives.js.map +1 -1
  19. package/build/angular/components/virtualized-list/index.d.ts +10 -2
  20. package/build/angular/components/virtualized-list/index.js +45 -25
  21. package/build/angular/components/virtualized-list/index.js.map +1 -1
  22. package/build/angular/components/virtualized-section-list/index.d.ts +5 -1
  23. package/build/angular/components/virtualized-section-list/index.js +10 -2
  24. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  25. package/build/angular/element-props.d.ts +16 -5
  26. package/build/angular/elements.d.ts +5 -21
  27. package/build/angular/elements.js +18 -26
  28. package/build/angular/elements.js.map +1 -1
  29. package/build/angular/index.d.ts +0 -1
  30. package/build/angular/index.js +5 -7
  31. package/build/angular/index.js.map +1 -1
  32. package/build/angular/renderer/index.d.ts +11 -0
  33. package/build/angular/renderer/index.js +97 -12
  34. package/build/angular/renderer/index.js.map +1 -1
  35. package/build/angular/runtime-matching.d.ts +0 -1
  36. package/build/angular/runtime-matching.js +6 -29
  37. package/build/angular/runtime-matching.js.map +1 -1
  38. package/package.json +4 -4
  39. package/src/callback-host.ts +10 -43
  40. package/src/change-detection-flush.ts +67 -14
  41. package/src/components/flat-list/index.ts +22 -54
  42. package/src/components/modal/index.ts +22 -16
  43. package/src/components/section-list/index.ts +4 -0
  44. package/src/components/virtualized-list/directives.ts +10 -8
  45. package/src/components/virtualized-list/index.ts +38 -13
  46. package/src/components/virtualized-section-list/index.ts +6 -0
  47. package/src/element-props.ts +22 -13
  48. package/src/elements.ts +21 -56
  49. package/src/index.ts +5 -7
  50. package/src/renderer/index.ts +107 -11
  51. package/src/runtime-matching.ts +6 -30
  52. package/build/angular/style-host.d.ts +0 -11
  53. package/build/angular/style-host.js +0 -86
  54. package/build/angular/style-host.js.map +0 -1
  55. package/src/style-host.ts +0 -94
@@ -177,6 +177,8 @@ export type ISectionListInputs<ItemT> = Omit<
177
177
  [scrollEventThrottle]="scrollEventThrottle"
178
178
  [keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
179
179
  [keyboardDismissMode]="keyboardDismissMode"
180
+ [removeClippedSubviews]="removeClippedSubviews"
181
+ [nestedScrollEnabled]="nestedScrollEnabled"
180
182
  [style]="resolvedStyle"
181
183
  [contentContainerStyle]="contentContainerStyle"
182
184
  [testID]="testID"
@@ -343,6 +345,8 @@ export class SectionList<ItemT = unknown>
343
345
  @Input() scrollEventThrottle?: number;
344
346
  @Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
345
347
  @Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
348
+ @Input() removeClippedSubviews?: boolean;
349
+ @Input() nestedScrollEnabled?: boolean;
346
350
  @Input() style?: IStyleProp<IViewStyle>;
347
351
  @Input() contentContainerStyle?: IStyleProp<IViewStyle>;
348
352
  @Input() testID?: string;
@@ -35,7 +35,7 @@ import {
35
35
  type OnDestroy,
36
36
  type SimpleChanges,
37
37
  } from '@angular/core';
38
- import { dlog } from '@symbiote-native/engine';
38
+ import { dlog, isDebug } from '@symbiote-native/engine';
39
39
  import { countAngular } from '../../diagnostics';
40
40
  import type { ISeparators } from '@symbiote-native/components';
41
41
 
@@ -129,9 +129,10 @@ export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
129
129
 
130
130
  ngOnChanges(changes: SimpleChanges): void {
131
131
  if (changes['templateRef'] !== undefined) {
132
- dlog(
133
- `Angular VListOutlet#${this.instanceId} templateRef CHANGED (was=${changes['templateRef'].previousValue !== undefined} now=${this.templateRef !== undefined}) -> clear + recreate`,
134
- );
132
+ if (isDebug())
133
+ dlog(
134
+ `Angular VListOutlet#${this.instanceId} templateRef CHANGED (was=${changes['templateRef'].previousValue !== undefined} now=${this.templateRef !== undefined}) -> clear + recreate`,
135
+ );
135
136
  this.viewContainer.clear();
136
137
  countAngular('outletCreates');
137
138
  this.viewRef =
@@ -145,9 +146,10 @@ export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
145
146
  }
146
147
  if (this.viewRef !== null && this.context !== undefined) {
147
148
  countAngular('outletUpdates');
148
- dlog(
149
- `Angular VListOutlet#${this.instanceId} context updated, markForCheck`,
150
- );
149
+ if (isDebug())
150
+ dlog(
151
+ `Angular VListOutlet#${this.instanceId} context updated, markForCheck`,
152
+ );
151
153
  this.updateContext(this.viewRef.context, this.context);
152
154
  this.viewRef.markForCheck();
153
155
  }
@@ -155,7 +157,7 @@ export class VListOutletDirective<C = unknown> implements OnChanges, OnDestroy {
155
157
 
156
158
  ngOnDestroy(): void {
157
159
  countAngular('outletDestroys');
158
- dlog(`Angular VListOutlet#${this.instanceId} destroyed`);
160
+ if (isDebug()) dlog(`Angular VListOutlet#${this.instanceId} destroyed`);
159
161
  this.viewContainer.clear();
160
162
  }
161
163
 
@@ -34,6 +34,7 @@ import {
34
34
  EventEmitter,
35
35
  Input,
36
36
  Output,
37
+ TemplateRef,
37
38
  ViewChild,
38
39
  computed,
39
40
  inject,
@@ -183,6 +184,8 @@ export interface IVirtualizedListProps<ItemT>
183
184
  scrollEventThrottle?: number;
184
185
  keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
185
186
  keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
187
+ removeClippedSubviews?: boolean;
188
+ nestedScrollEnabled?: boolean;
186
189
  style?: IStyleProp<IViewStyle>;
187
190
  contentContainerStyle?: IStyleProp<IViewStyle>;
188
191
  }
@@ -267,7 +270,7 @@ interface IWindowCell<ItemT> {
267
270
  [style]="cellStyle"
268
271
  >
269
272
  <ng-container
270
- [vListOutlet]="itemDir?.templateRef"
273
+ [vListOutlet]="cellTemplate"
271
274
  [vListOutletContext]="forcedStickyCell.context"
272
275
  ></ng-container>
273
276
  </sticky-header>
@@ -282,12 +285,12 @@ interface IWindowCell<ItemT> {
282
285
  [style]="cellStyle"
283
286
  >
284
287
  <ng-container
285
- [vListOutlet]="itemDir?.templateRef"
288
+ [vListOutlet]="cellTemplate"
286
289
  [vListOutletContext]="cell.context"
287
290
  ></ng-container>
288
291
  @if (cell.separatorContext !== undefined) {
289
292
  <ng-container
290
- [vListOutlet]="separatorDir?.templateRef"
293
+ [vListOutlet]="separatorTemplate"
291
294
  [vListOutletContext]="cell.separatorContext"
292
295
  ></ng-container>
293
296
  }
@@ -298,12 +301,12 @@ interface IWindowCell<ItemT> {
298
301
  [style]="cellStyle"
299
302
  >
300
303
  <ng-container
301
- [vListOutlet]="itemDir?.templateRef"
304
+ [vListOutlet]="cellTemplate"
302
305
  [vListOutletContext]="cell.context"
303
306
  ></ng-container>
304
307
  @if (cell.separatorContext !== undefined) {
305
308
  <ng-container
306
- [vListOutlet]="separatorDir?.templateRef"
309
+ [vListOutlet]="separatorTemplate"
307
310
  [vListOutletContext]="cell.separatorContext"
308
311
  ></ng-container>
309
312
  }
@@ -351,7 +354,7 @@ interface IWindowCell<ItemT> {
351
354
  [style]="cellStyle"
352
355
  >
353
356
  <ng-container
354
- [vListOutlet]="itemDir?.templateRef"
357
+ [vListOutlet]="cellTemplate"
355
358
  [vListOutletContext]="forcedStickyCell.context"
356
359
  ></ng-container>
357
360
  </sticky-header>
@@ -373,12 +376,12 @@ interface IWindowCell<ItemT> {
373
376
  [style]="cellStyle"
374
377
  >
375
378
  <ng-container
376
- [vListOutlet]="itemDir?.templateRef"
379
+ [vListOutlet]="cellTemplate"
377
380
  [vListOutletContext]="cell.context"
378
381
  ></ng-container>
379
382
  @if (cell.separatorContext !== undefined) {
380
383
  <ng-container
381
- [vListOutlet]="separatorDir?.templateRef"
384
+ [vListOutlet]="separatorTemplate"
382
385
  [vListOutletContext]="cell.separatorContext"
383
386
  ></ng-container>
384
387
  }
@@ -389,12 +392,12 @@ interface IWindowCell<ItemT> {
389
392
  [style]="cellStyle"
390
393
  >
391
394
  <ng-container
392
- [vListOutlet]="itemDir?.templateRef"
395
+ [vListOutlet]="cellTemplate"
393
396
  [vListOutletContext]="cell.context"
394
397
  ></ng-container>
395
398
  @if (cell.separatorContext !== undefined) {
396
399
  <ng-container
397
- [vListOutlet]="separatorDir?.templateRef"
400
+ [vListOutlet]="separatorTemplate"
398
401
  [vListOutletContext]="cell.separatorContext"
399
402
  ></ng-container>
400
403
  }
@@ -500,6 +503,8 @@ export class VirtualizedList<ItemT = unknown>
500
503
  @Input() scrollEventThrottle?: number;
501
504
  @Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
502
505
  @Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
506
+ @Input() removeClippedSubviews?: boolean;
507
+ @Input() nestedScrollEnabled?: boolean;
503
508
  @Input() style?: IStyleProp<IViewStyle>;
504
509
  @Input() contentContainerStyle?: IStyleProp<IViewStyle>;
505
510
  @Input() testID?: string;
@@ -550,6 +555,20 @@ export class VirtualizedList<ItemT = unknown>
550
555
  @ContentChild(VListSeparatorDirective)
551
556
  separatorDir?: VListSeparatorDirective<ItemT>;
552
557
 
558
+ // A wrapping list (FlatList) hands its app's cell/separator templates straight in, so a cell is
559
+ // one outlet deep instead of a wrapper outlet around the app's own; each wins over projection.
560
+ @Input() itemTemplate?: TemplateRef<IVListItemContext<ItemT>>;
561
+ @Input() itemSeparatorTemplate?: TemplateRef<IVListSeparatorContext<ItemT>>;
562
+
563
+ get cellTemplate(): TemplateRef<IVListItemContext<ItemT>> | undefined {
564
+ return this.itemTemplate ?? this.itemDir?.templateRef;
565
+ }
566
+
567
+ get separatorTemplate():
568
+ TemplateRef<IVListSeparatorContext<ItemT>> | undefined {
569
+ return this.itemSeparatorTemplate ?? this.separatorDir?.templateRef;
570
+ }
571
+
553
572
  // The inner scroll TAG's own engine node — a template ref on a bare intrinsic hands back the
554
573
  // host node directly (`isSymbioteNode(elementRef.nativeElement)`, matching `anchorHostStyle`'s
555
574
  // own reader below), so there is no wrapper component left to read a handle off. Present from
@@ -739,6 +758,11 @@ export class VirtualizedList<ItemT = unknown>
739
758
  scrollEventThrottle: this.scrollEventThrottle,
740
759
  keyboardShouldPersistTaps: this.keyboardShouldPersistTaps,
741
760
  keyboardDismissMode: this.keyboardDismissMode,
761
+ // RN's VirtualizedList spreads its props onto the ScrollView, this one included.
762
+ removeClippedSubviews: this.removeClippedSubviews,
763
+ nestedScrollEnabled: this.nestedScrollEnabled,
764
+ // VirtualizedList.js:1111 — Android moves the scrollbar back after the `scale: -1` flip.
765
+ isInvertedVirtualizedList: this.isInverted ? true : undefined,
742
766
  contentOffset: this.commandedOffset,
743
767
  // `stickyHeaderIndices` is deliberately NOT forwarded — it numbers the scroll view's PAINT
744
768
  // children, and a windowed list paints a header, a spacer and a slice, so the positions move
@@ -1041,7 +1065,7 @@ export class VirtualizedList<ItemT = unknown>
1041
1065
  this.headerDir !== undefined,
1042
1066
  this.footerDir !== undefined,
1043
1067
  this.emptyDir !== undefined,
1044
- this.separatorDir !== undefined,
1068
+ this.separatorTemplate !== undefined,
1045
1069
  ];
1046
1070
  const previous = this.lastRecompute;
1047
1071
  this.lastRecompute = recomputeInputs;
@@ -1076,7 +1100,7 @@ export class VirtualizedList<ItemT = unknown>
1076
1100
  const m = this.listState.metrics;
1077
1101
  this.itemCount = m.count;
1078
1102
  const hasHeader = this.headerDir !== undefined;
1079
- const hasSeparators = this.separatorDir !== undefined;
1103
+ const hasSeparators = this.separatorTemplate !== undefined;
1080
1104
  const stickySet =
1081
1105
  this.stickyHeaderIndices !== undefined
1082
1106
  ? new Set(this.stickyHeaderIndices)
@@ -1087,8 +1111,9 @@ export class VirtualizedList<ItemT = unknown>
1087
1111
  : this.contentContainerStyle;
1088
1112
  this.resolvedStyle = flattenStyle([
1089
1113
  anchorHostStyle(this.elementRef),
1114
+ // VirtualizedList.js: `[inversionStyle, style]` — the app's style can override the flip.
1090
1115
  this.isInverted
1091
- ? [this.style, this.isHorizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE]
1116
+ ? [this.isHorizontal ? INVERTED_X_STYLE : INVERTED_Y_STYLE, this.style]
1092
1117
  : this.style,
1093
1118
  ]);
1094
1119
  this.cellStyle = this.isInverted
@@ -146,6 +146,8 @@ export interface IVirtualizedSectionListProps<ItemT>
146
146
  scrollEventThrottle?: number;
147
147
  keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
148
148
  keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
149
+ removeClippedSubviews?: boolean;
150
+ nestedScrollEnabled?: boolean;
149
151
  style?: IStyleProp<IViewStyle>;
150
152
  contentContainerStyle?: IStyleProp<IViewStyle>;
151
153
  }
@@ -213,6 +215,8 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
213
215
  [scrollEventThrottle]="scrollEventThrottle"
214
216
  [keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
215
217
  [keyboardDismissMode]="keyboardDismissMode"
218
+ [removeClippedSubviews]="removeClippedSubviews"
219
+ [nestedScrollEnabled]="nestedScrollEnabled"
216
220
  [style]="resolvedStyle"
217
221
  [contentContainerStyle]="contentContainerStyle"
218
222
  [testID]="testID"
@@ -375,6 +379,8 @@ export class VirtualizedSectionList<ItemT = unknown>
375
379
  @Input() scrollEventThrottle?: number;
376
380
  @Input() keyboardShouldPersistTaps?: boolean | 'always' | 'never' | 'handled';
377
381
  @Input() keyboardDismissMode?: 'none' | 'on-drag' | 'interactive';
382
+ @Input() removeClippedSubviews?: boolean;
383
+ @Input() nestedScrollEnabled?: boolean;
378
384
  @Input() style?: IStyleProp<IViewStyle>;
379
385
  @Input() contentContainerStyle?: IStyleProp<IViewStyle>;
380
386
  @Input() testID?: string;
@@ -8,13 +8,9 @@
8
8
  // differ, since a bare tag takes children from the template and a handle from `#ref`, so neither
9
9
  // `children` nor `ref` appears here.
10
10
  //
11
- // `style` IS declared, reversing what this comment said until 2026-09-11. Leaving it to Angular's
12
- // styling engine was not the conservative choice it reads as: an array decomposes into
13
- // numeric-index keys there and a press-state CALLBACK throws out of `toStylingKeyValueArray`,
14
- // aborting the enclosing template update — which is what an app writes, and what every deleted
15
- // wrapper component accepted. The claim that a directive can reclaim it "solely by executing a
16
- // linked AOT artifact" was answered rather than assumed: `elements.test.ts` compiles the binding
17
- // through real ngtsc and runs the LINKED output. See `SymbioteElement.style`.
11
+ // `style` and `styleProp` are TWO bindings. `[style]` is Angular's styling binding: an object or CSS
12
+ // string, as on a DOM element, unclaimed because a claim is a directive instance per element. An RN
13
+ // array or press-state callback throws in that engine, so it travels as `[styleProp]`.
18
14
  import type {
19
15
  IAccessibilityProps,
20
16
  IAriaProps,
@@ -31,6 +27,17 @@ import type {
31
27
 
32
28
  type IEventHandler = (event: ISymbioteEvent) => void;
33
29
 
30
+ /**
31
+ * What `[style]` carries on a tag: Angular's own styling binding, decomposed key by key into
32
+ * `Renderer2.setStyle` exactly as on a DOM element. A style OBJECT or a CSS string - never an array
33
+ * or a function, which that engine cannot represent. Those are `[styleProp]`.
34
+ */
35
+ export type IAngularStyleBinding<TStyle> = TStyle | string | null | undefined;
36
+
37
+ /** RN's full StyleProp, press-state callback included: the `[styleProp]` binding. */
38
+ export type IElementStyleProp =
39
+ IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
40
+
34
41
  export interface IElementProps
35
42
  extends IAccessibilityProps, IAriaProps, IResponderProps {
36
43
  /** RN's modern W3C-named alias for `nativeID`; the renderer folds it (`PROP_ALIASES`). */
@@ -43,13 +50,14 @@ export interface IElementProps
43
50
  renderToHardwareTextureAndroid?: boolean;
44
51
  shouldRasterizeIOS?: boolean;
45
52
  needsOffscreenAlphaCompositing?: boolean;
53
+ style?: IAngularStyleBinding<IViewStyle>;
46
54
  /**
47
- * The press-state callback is in the union because a subclass cannot widen an
48
- * inherited property and `<pressable>`/`<touchable-*>` are the tags that take one; on every other
49
- * tag the engine resolves it at `pressed: false`.
55
+ * An RN StyleProp as ONE property binding: arrays, falsy entries, and the press-state callback.
56
+ * The callback is in the union because a subclass cannot widen an inherited property and
57
+ * `<pressable>`/`<touchable-*>` are the tags that take one; on every other tag the engine resolves
58
+ * it at `pressed: false`.
50
59
  */
51
- style?:
52
- IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
60
+ styleProp?: IElementStyleProp;
53
61
  onPress?: IEventHandler;
54
62
  onPressIn?: IEventHandler;
55
63
  onPressOut?: IEventHandler;
@@ -63,7 +71,8 @@ export interface IElementProps
63
71
  /** Text's own surface on top of the shared one. `onTextLayout` is an event, so it is not here. */
64
72
  export interface ITextElementProps {
65
73
  /** Narrows the shared `style` so `fontSize` / `fontWeight` type-check on a `<text>`. */
66
- style?: IStyleProp<ITextStyle>;
74
+ style?: IAngularStyleBinding<ITextStyle>;
75
+ styleProp?: IStyleProp<ITextStyle>;
67
76
  numberOfLines?: number;
68
77
  ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
69
78
  selectable?: boolean;
package/src/elements.ts CHANGED
@@ -58,7 +58,6 @@ import {
58
58
  } from './change-detection-flush';
59
59
  import { SymbioteCallbackHost } from './callback-host';
60
60
  import { withholdFromRuntimeMatching } from './runtime-matching';
61
- import { SymbioteStyleHost } from './style-host';
62
61
  import type {
63
62
  IActivityIndicatorProps,
64
63
  IImageProps,
@@ -190,44 +189,21 @@ export abstract class SymbioteElement implements OnChanges {
190
189
  @Input() shouldRasterizeIOS?: IElementProps['shouldRasterizeIOS'];
191
190
  @Input()
192
191
  needsOffscreenAlphaCompositing?: IElementProps['needsOffscreenAlphaCompositing'];
193
- // Declared, and the reason is the one `element-props.ts` used to give for NOT declaring it —
194
- // reversed by measurement. `[style]` on an element with no directive input reaches Angular's own
195
- // CSS styling engine (`ɵɵstyleMap`), which cannot represent an RN StyleProp: an ARRAY decomposes
196
- // into numeric-index keys and a FUNCTION — the press-state callback this ecosystem writes dies
197
- // in `toStylingKeyValueArray` as "ASSERTION ERROR: Unsupported styling type: function", taking
198
- // the whole enclosing template update with it. Device-reported 2026-09-11: every `ActionButton`
199
- // in examples/angular painted as an empty bordered box, because the throw aborted the update
200
- // before its `<text>` child was ever reached.
201
- //
202
- // A declared input CLAIMS the binding at compile time, so it never reaches the styling engine —
203
- // the same mechanism the primitive host COMPONENTS have always relied on
204
- // (`primitives/shared.ts`), which is why `<Pressable [style]>` worked and its replacement tag did
205
- // not. `[style.borderWidth.px]` is a different instruction (`ɵɵstyleProp`) and is untouched: it
206
- // still reaches `Renderer2.setStyle`, which merges per key.
192
+ // `[style]` and `[class]` are declared for the TYPE CHECK only. Withheld from run-time matching, no
193
+ // input claims them, so Angular's own styling engine decomposes them into `setStyle`/`addClass`
194
+ // exactly as on a DOM element - which is why `style` is typed as the object/string that engine can
195
+ // hold. An RN style ARRAY or press-state FUNCTION throws in there ("indexOf is not a function",
196
+ // "Unsupported styling type: function"), so it is `[styleProp]`: an ordinary property binding the
197
+ // renderer routes to `style` whole. A claim would cost a directive instance per element.
207
198
  //
208
199
  // The press-state callback rides the BASE type rather than `PressableElement` alone: a subclass
209
200
  // cannot widen an inherited property, and only the pressables have a `pressed` to read — so on
210
- // any other tag the engine resolves it at `pressed: false` and the looseness is in the type, not
211
- // in what commits.
201
+ // any other tag the engine resolves it at `pressed: false`.
212
202
  @Input() style?: IElementProps['style'];
203
+ @Input() styleProp?: IElementProps['styleProp'];
213
204
 
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
- */
205
+ // `ɵɵclassMap` hands over a string after joining any static `class=` prefix; `[class.foo]` and
206
+ // `[ngClass]` arrive as `addClass`. The renderer unions both sources (`classStringFor`).
231
207
  @Input() class?: string;
232
208
 
233
209
  // The flat-bag spelling of the events below. `(press)` and `[onPress]` are both supported and
@@ -422,6 +398,7 @@ export class TextElement extends SymbioteElement {
422
398
  // initializer is what TS2612 asks for to accept a redeclaration as deliberate; `declare` would
423
399
  // be the other answer and cannot carry a decorator.
424
400
  @Input() override style?: ITextElementProps['style'] = undefined;
401
+ @Input() override styleProp?: ITextElementProps['styleProp'] = undefined;
425
402
  @Input() numberOfLines?: ITextElementProps['numberOfLines'];
426
403
  @Input() ellipsizeMode?: ITextElementProps['ellipsizeMode'];
427
404
  @Input() selectable?: ITextElementProps['selectable'];
@@ -927,39 +904,27 @@ export const SYMBIOTE_ELEMENTS = [
927
904
  // bind one and carries the `ChangeDetectorRef` their wrapper needs. It rides this list for the same
928
905
  // reason the accessors do, and `elements.test.ts` subtracts it from the tag-coverage check by name.
929
906
  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,
933
907
  ] as const;
934
908
 
935
909
  // THE DIRECTIVES THAT GO ON MATCHING, and the list is the exceptions rather than the rule.
936
910
  //
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:
911
+ // EVERY TAG DIRECTIVE IS WITHHELD. Each is `@Input()` declarations over one inherited `ngOnChanges`
912
+ // forwarding to the renderer (the call `ɵɵproperty` makes directly on an unclaimed element), so the
913
+ // instance bought a compile-time check at ~8 us of run time per element.
944
914
  //
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
915
+ // The read-back tags (text-input, text-input-multiline, switch, refresh-control) need a synchronous
916
+ // view flush; the renderer marks them at creation and the flush finds their view lazily
917
+ // (`change-detection-flush.ts`). `[(value)]` reaches the renderer's `listen('valueChange')` bridge.
949
918
  //
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.
919
+ // `[style]`/`[class]` stay unclaimed as on a DOM element (see `SymbioteElement.style`). What still
920
+ // matches is selected on an ATTRIBUTE an app writes: the two form accessors and
921
+ // `SymbioteCallbackHost`.
952
922
  withholdFromRuntimeMatching(
953
923
  SYMBIOTE_ELEMENTS.filter(
954
924
  directive =>
955
- directive !== TextInputElement &&
956
- directive !== MultilineTextInputElement &&
957
- directive !== SwitchElement &&
958
- directive !== RefreshControlElement &&
959
925
  directive !== TextInputValueAccessor &&
960
926
  directive !== SwitchValueAccessor &&
961
- directive !== SymbioteCallbackHost &&
962
- directive !== SymbioteStyleHost,
927
+ directive !== SymbioteCallbackHost,
963
928
  ),
964
929
  );
965
930
 
package/src/index.ts CHANGED
@@ -9,8 +9,8 @@
9
9
  // `@Component`s on the `view` and `text` SELECTORS — a second mechanism on the same tags
10
10
  // `SYMBIOTE_ELEMENTS` covers, kept for one stated reason their own header gives: "Declaring `style`
11
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.
12
+ // arrays". A tag takes an array or press-state callback as `[styleProp]`, a plain property binding
13
+ // with no instance behind it.
14
14
  //
15
15
  // An app loses nothing it cannot spell better: `@ViewChild('ref')` on a template reference returns
16
16
  // Angular's own `ElementRef<IHostInstance>`, whose `nativeElement` IS the engine host node — the same
@@ -146,10 +146,9 @@ export { setImageSourceResolver } from './components';
146
146
  //
147
147
  // They were removed on 2026-09-18 and put back the same hour. The reason to remove them is real: a
148
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.
149
+ // what makes `<view [onPress]="fn">` work at all is `SymbioteCallbackHost` claiming the name before
150
+ // `setDomProperty` throws NG0306. It rides this array, so a narrow `imports: [ViewElement]`
151
+ // type-checks and then fails on a device the first time the app binds an `on*` prop.
153
152
  //
154
153
  // WHAT PUTS THEM BACK is NG3004: `Unable to import directive ViewElement — the symbol is not
155
154
  // exported from index.d.ts`. ngtsc resolves every directive reachable through an imported array to an
@@ -198,7 +197,6 @@ export {
198
197
  CALLBACK_ATTRIBUTE_SELECTOR,
199
198
  SymbioteCallbackHost,
200
199
  } from './callback-host';
201
- export { STYLE_HOST_SELECTOR, SymbioteStyleHost } from './style-host';
202
200
  export type {
203
201
  IElementProps,
204
202
  IStickyHeaderElementProps,