@symbiote-native/angular 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/babel-register-composed.cjs +2 -0
  2. package/build/angular/accessibility-inputs.d.ts +2 -1
  3. package/build/angular/accessibility-inputs.js +4 -1
  4. package/build/angular/accessibility-inputs.js.map +1 -1
  5. package/build/angular/callback-host.d.ts +1 -1
  6. package/build/angular/callback-host.js +57 -9
  7. package/build/angular/callback-host.js.map +1 -1
  8. package/build/angular/components/checkbox-props.d.ts +2 -0
  9. package/build/angular/components/checkbox-props.js +2 -0
  10. package/build/angular/components/checkbox-props.js.map +1 -0
  11. package/build/angular/components/flat-list/flat-list-handle.d.ts +35 -0
  12. package/build/angular/components/flat-list/flat-list-handle.js +48 -0
  13. package/build/angular/components/flat-list/flat-list-handle.js.map +1 -0
  14. package/build/angular/components/flat-list/flat-list-template.d.ts +1 -1
  15. package/build/angular/components/flat-list/flat-list-template.js +16 -0
  16. package/build/angular/components/flat-list/flat-list-template.js.map +1 -1
  17. package/build/angular/components/flat-list/index.d.ts +6 -30
  18. package/build/angular/components/flat-list/index.js +6 -36
  19. package/build/angular/components/flat-list/index.js.map +1 -1
  20. package/build/angular/components/image-inputs.d.ts +4 -0
  21. package/build/angular/components/image-inputs.js +81 -0
  22. package/build/angular/components/image-inputs.js.map +1 -0
  23. package/build/angular/components/image-props-resolve.d.ts +3 -0
  24. package/build/angular/components/image-props-resolve.js +133 -0
  25. package/build/angular/components/image-props-resolve.js.map +1 -0
  26. package/build/angular/components/image-shared.d.ts +8 -6
  27. package/build/angular/components/image-shared.js +23 -212
  28. package/build/angular/components/image-shared.js.map +1 -1
  29. package/build/angular/components/input-accessory-view-props.d.ts +4 -4
  30. package/build/angular/components/keyboard-avoiding-view/index.d.ts +3 -6
  31. package/build/angular/components/keyboard-avoiding-view/index.js +29 -56
  32. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  33. package/build/angular/components/layout-conformance-props.d.ts +4 -0
  34. package/build/angular/components/layout-conformance-props.js +2 -0
  35. package/build/angular/components/layout-conformance-props.js.map +1 -0
  36. package/build/angular/components/modal/index.d.ts +6 -5
  37. package/build/angular/components/modal/index.js +17 -2
  38. package/build/angular/components/modal/index.js.map +1 -1
  39. package/build/angular/components/refresh-control-props.d.ts +7 -7
  40. package/build/angular/components/scroll-view-props.d.ts +8 -5
  41. package/build/angular/components/section-list/index.d.ts +17 -8
  42. package/build/angular/components/section-list/index.js +37 -28
  43. package/build/angular/components/section-list/index.js.map +1 -1
  44. package/build/angular/components/touchable-props.d.ts +2 -2
  45. package/build/angular/components/virtualized-list/cell-renderer-context.d.ts +12 -0
  46. package/build/angular/components/virtualized-list/cell-renderer-context.js +16 -0
  47. package/build/angular/components/virtualized-list/cell-renderer-context.js.map +1 -0
  48. package/build/angular/components/virtualized-list/directives.d.ts +26 -6
  49. package/build/angular/components/virtualized-list/directives.js +59 -5
  50. package/build/angular/components/virtualized-list/directives.js.map +1 -1
  51. package/build/angular/components/virtualized-list/index.d.ts +19 -15
  52. package/build/angular/components/virtualized-list/index.js +91 -61
  53. package/build/angular/components/virtualized-list/index.js.map +1 -1
  54. package/build/angular/components/virtualized-list/list-bags.d.ts +5 -1
  55. package/build/angular/components/virtualized-list/list-bags.js +31 -19
  56. package/build/angular/components/virtualized-list/list-bags.js.map +1 -1
  57. package/build/angular/components/virtualized-list/list-effects.d.ts +2 -2
  58. package/build/angular/components/virtualized-list/list-effects.js +17 -27
  59. package/build/angular/components/virtualized-list/list-effects.js.map +1 -1
  60. package/build/angular/components/virtualized-list/list-handle.d.ts +4 -1
  61. package/build/angular/components/virtualized-list/list-handle.js +13 -3
  62. package/build/angular/components/virtualized-list/list-handle.js.map +1 -1
  63. package/build/angular/components/virtualized-list/list-inputs.d.ts +19 -5
  64. package/build/angular/components/virtualized-list/list-inputs.js +47 -4
  65. package/build/angular/components/virtualized-list/list-inputs.js.map +1 -1
  66. package/build/angular/components/virtualized-list/list-props.d.ts +9 -3
  67. package/build/angular/components/virtualized-list/list-rows.d.ts +25 -0
  68. package/build/angular/components/virtualized-list/list-rows.js +25 -0
  69. package/build/angular/components/virtualized-list/list-rows.js.map +1 -0
  70. package/build/angular/components/virtualized-list/list-styles.d.ts +4 -0
  71. package/build/angular/components/virtualized-list/list-styles.js +10 -2
  72. package/build/angular/components/virtualized-list/list-styles.js.map +1 -1
  73. package/build/angular/components/virtualized-list/list-template.d.ts +1 -1
  74. package/build/angular/components/virtualized-list/list-template.js +159 -73
  75. package/build/angular/components/virtualized-list/list-template.js.map +1 -1
  76. package/build/angular/components/virtualized-list/nested-scope.d.ts +6 -0
  77. package/build/angular/components/virtualized-list/nested-scope.js +17 -0
  78. package/build/angular/components/virtualized-list/nested-scope.js.map +1 -0
  79. package/build/angular/components/virtualized-section-list/directives.d.ts +21 -9
  80. package/build/angular/components/virtualized-section-list/directives.js +8 -28
  81. package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
  82. package/build/angular/components/virtualized-section-list/index.d.ts +39 -60
  83. package/build/angular/components/virtualized-section-list/index.js +67 -33
  84. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  85. package/build/angular/components/virtualized-section-list/section-cell.d.ts +29 -0
  86. package/build/angular/components/virtualized-section-list/section-cell.js +82 -0
  87. package/build/angular/components/virtualized-section-list/section-cell.js.map +1 -0
  88. package/build/angular/components/virtualized-section-list/section-list-props.d.ts +56 -0
  89. package/build/angular/components/virtualized-section-list/section-list-props.js +3 -0
  90. package/build/angular/components/virtualized-section-list/section-list-props.js.map +1 -0
  91. package/build/angular/components/virtualized-section-list/section-list-template.d.ts +1 -1
  92. package/build/angular/components/virtualized-section-list/section-list-template.js +34 -26
  93. package/build/angular/components/virtualized-section-list/section-list-template.js.map +1 -1
  94. package/build/angular/components.d.ts +8 -3
  95. package/build/angular/components.js +2 -1
  96. package/build/angular/components.js.map +1 -1
  97. package/build/angular/descriptor-to-angular/descriptor-host.d.ts +8 -0
  98. package/build/angular/descriptor-to-angular/descriptor-host.js +38 -0
  99. package/build/angular/descriptor-to-angular/descriptor-host.js.map +1 -0
  100. package/build/angular/descriptor-to-angular/index.d.ts +1 -0
  101. package/build/angular/descriptor-to-angular/index.js +4 -0
  102. package/build/angular/descriptor-to-angular/index.js.map +1 -1
  103. package/build/angular/element-base.d.ts +169 -0
  104. package/build/angular/element-base.js +304 -0
  105. package/build/angular/element-base.js.map +1 -0
  106. package/build/angular/element-props.d.ts +7 -7
  107. package/build/angular/element-text-input-native.d.ts +27 -0
  108. package/build/angular/element-text-input-native.js +52 -0
  109. package/build/angular/element-text-input-native.js.map +1 -0
  110. package/build/angular/elements-controls.d.ts +171 -0
  111. package/build/angular/elements-controls.js +394 -0
  112. package/build/angular/elements-controls.js.map +1 -0
  113. package/build/angular/elements-layout.d.ts +139 -0
  114. package/build/angular/elements-layout.js +276 -0
  115. package/build/angular/elements-layout.js.map +1 -0
  116. package/build/angular/elements-press.d.ts +103 -0
  117. package/build/angular/elements-press.js +239 -0
  118. package/build/angular/elements-press.js.map +1 -0
  119. package/build/angular/elements.d.ts +6 -516
  120. package/build/angular/elements.js +9 -1091
  121. package/build/angular/elements.js.map +1 -1
  122. package/build/angular/host-instance/index.d.ts +2 -0
  123. package/build/angular/host-instance/index.js +4 -4
  124. package/build/angular/host-instance/index.js.map +1 -1
  125. package/build/angular/index.d.ts +17 -9
  126. package/build/angular/index.js +24 -27
  127. package/build/angular/index.js.map +1 -1
  128. package/build/angular/modules/animated/create-animated-component.d.ts +1 -1
  129. package/build/angular/modules/animated/create-animated-component.js +1 -1
  130. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  131. package/build/angular/modules/animated/index.d.ts +54 -41
  132. package/build/angular/modules/animated/index.js +2 -26
  133. package/build/angular/modules/animated/index.js.map +1 -1
  134. package/build/angular/modules/app-registry/index.d.ts +3 -3
  135. package/build/angular/modules/app-registry/index.js +2 -2
  136. package/build/angular/modules/app-registry/index.js.map +1 -1
  137. package/build/angular/modules/status-bar/index.d.ts +9 -5
  138. package/build/angular/modules/status-bar/index.js +21 -10
  139. package/build/angular/modules/status-bar/index.js.map +1 -1
  140. package/build/angular/native-view-base.d.ts +10 -2
  141. package/build/angular/native-view-base.js +23 -2
  142. package/build/angular/native-view-base.js.map +1 -1
  143. package/build/angular/primitives/index.d.ts +4 -0
  144. package/build/angular/primitives/index.js +12 -0
  145. package/build/angular/primitives/index.js.map +1 -1
  146. package/build/angular/services/color-scheme.service.d.ts +1 -1
  147. package/build/angular/services/index.d.ts +1 -0
  148. package/build/angular/services/index.js +1 -0
  149. package/build/angular/services/index.js.map +1 -1
  150. package/build/angular/services/inject-event.d.ts +6 -0
  151. package/build/angular/services/inject-event.js +24 -0
  152. package/build/angular/services/inject-event.js.map +1 -0
  153. package/package.json +6 -6
  154. package/src/accessibility-inputs.ts +3 -0
  155. package/src/callback-host.ts +56 -8
  156. package/src/components/checkbox-props.ts +12 -0
  157. package/src/components/flat-list/flat-list-handle.ts +64 -0
  158. package/src/components/flat-list/flat-list-template.ts +16 -0
  159. package/src/components/flat-list/index.ts +6 -48
  160. package/src/components/image-inputs.ts +94 -0
  161. package/src/components/image-props-resolve.ts +172 -0
  162. package/src/components/image-shared.ts +26 -291
  163. package/src/components/input-accessory-view-props.ts +12 -8
  164. package/src/components/keyboard-avoiding-view/index.ts +39 -79
  165. package/src/components/layout-conformance-props.ts +8 -0
  166. package/src/components/modal/index.ts +42 -20
  167. package/src/components/refresh-control-props.ts +29 -25
  168. package/src/components/scroll-view-props.ts +84 -76
  169. package/src/components/section-list/index.ts +34 -37
  170. package/src/components/touchable-props.ts +6 -2
  171. package/src/components/virtualized-list/cell-renderer-context.ts +36 -0
  172. package/src/components/virtualized-list/directives.ts +84 -8
  173. package/src/components/virtualized-list/index.ts +118 -85
  174. package/src/components/virtualized-list/list-bags.ts +38 -19
  175. package/src/components/virtualized-list/list-effects.ts +20 -32
  176. package/src/components/virtualized-list/list-handle.ts +17 -4
  177. package/src/components/virtualized-list/list-inputs.ts +47 -0
  178. package/src/components/virtualized-list/list-props.ts +57 -50
  179. package/src/components/virtualized-list/list-rows.ts +66 -0
  180. package/src/components/virtualized-list/list-styles.ts +30 -1
  181. package/src/components/virtualized-list/list-template.ts +159 -73
  182. package/src/components/virtualized-list/nested-scope.ts +22 -0
  183. package/src/components/virtualized-section-list/directives.ts +41 -40
  184. package/src/components/virtualized-section-list/index.ts +114 -118
  185. package/src/components/virtualized-section-list/section-cell.ts +109 -0
  186. package/src/components/virtualized-section-list/section-list-props.ts +79 -0
  187. package/src/components/virtualized-section-list/section-list-template.ts +34 -26
  188. package/src/components.ts +9 -0
  189. package/src/descriptor-to-angular/descriptor-host.ts +27 -0
  190. package/src/descriptor-to-angular/index.ts +5 -0
  191. package/src/element-base.ts +289 -0
  192. package/src/element-props.ts +35 -33
  193. package/src/element-text-input-native.ts +31 -0
  194. package/src/elements-controls.ts +290 -0
  195. package/src/elements-layout.ts +168 -0
  196. package/src/elements-press.ts +153 -0
  197. package/src/elements.ts +76 -788
  198. package/src/host-instance/index.ts +4 -4
  199. package/src/index.ts +78 -23
  200. package/src/modules/animated/index.ts +2 -47
  201. package/src/modules/app-registry/index.ts +3 -1
  202. package/src/modules/status-bar/index.ts +24 -11
  203. package/src/native-view-base.ts +37 -2
  204. package/src/primitives/index.ts +7 -0
  205. package/src/services/color-scheme.service.ts +1 -1
  206. package/src/services/index.ts +1 -0
  207. package/src/services/inject-event.ts +52 -0
@@ -13,6 +13,7 @@ export const VIRTUALIZED_SECTION_LIST_TEMPLATE = `
13
13
  [stickyHeaderIndices]="stickyHeaderIndices"
14
14
  [extraData]="extraData"
15
15
  [inverted]="inverted"
16
+ [horizontal]="horizontal"
16
17
  [refreshing]="refreshing"
17
18
  [progressViewOffset]="progressViewOffset"
18
19
  [refreshRequested]="refreshRequested ?? refresh.observed"
@@ -26,25 +27,32 @@ export const VIRTUALIZED_SECTION_LIST_TEMPLATE = `
26
27
  [maxToRenderPerBatch]="maxToRenderPerBatch"
27
28
  [updateCellsBatchingPeriod]="updateCellsBatchingPeriod"
28
29
  [windowSize]="windowSize"
30
+ [disableVirtualization]="disableVirtualization"
29
31
  [maintainVisibleContentPosition]="maintainVisibleContentPosition"
30
32
  [onScroll]="onScroll"
31
33
  [onScrollBeginDrag]="onScrollBeginDrag"
32
34
  [onScrollEndDrag]="onScrollEndDrag"
33
35
  [onMomentumScrollBegin]="onMomentumScrollBegin"
34
36
  [onMomentumScrollEnd]="onMomentumScrollEnd"
37
+ [onContentSizeChange]="onContentSizeChange"
35
38
  [scrollEventThrottle]="scrollEventThrottle"
36
39
  [keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
37
40
  [keyboardDismissMode]="keyboardDismissMode"
38
41
  [removeClippedSubviews]="removeClippedSubviews"
39
42
  [nestedScrollEnabled]="nestedScrollEnabled"
43
+ [stickyHeaderHiddenOnScroll]="stickyHeaderHiddenOnScroll"
44
+ [innerViewRef]="innerViewRef"
40
45
  [style]="resolvedStyle"
41
46
  [contentContainerStyle]="contentContainerStyle"
47
+ [listHeaderComponentStyle]="listHeaderComponentStyle"
48
+ [listFooterComponentStyle]="listFooterComponentStyle"
49
+ [cellRendererTemplate]="cellRendererTpl"
42
50
  [testID]="testID"
43
51
  [nativeID]="nativeID"
44
52
  ${LIST_ACCESSIBILITY_FORWARD}
45
53
  >
46
54
  <!-- The single synthesized cell template: dispatch per flattened entry tag -->
47
- <ng-template vListItem let-entry let-separators="separators">
55
+ <ng-template vListItem let-entry let-index="index">
48
56
  @switch (entryKind(entry)) {
49
57
  @case ('header') {
50
58
  <ng-container
@@ -58,16 +66,32 @@ export const VIRTUALIZED_SECTION_LIST_TEMPLATE = `
58
66
  [vListOutletContext]="sectionContextOf(entry)"
59
67
  ></ng-container>
60
68
  }
61
- @case ('section-separator') {
62
- <ng-container
63
- [vListOutlet]="sectionSeparatorDir?.templateRef"
64
- ></ng-container>
65
- }
66
69
  @case ('item') {
67
- <ng-container
68
- [vListOutlet]="sectionItemDir?.templateRef"
69
- [vListOutletContext]="itemContextOf(entry, separators)"
70
- ></ng-container>
70
+ @let plan = cellPlanOf(entry, index);
71
+ @if (plan !== undefined) {
72
+ <ng-container
73
+ vSectionCell
74
+ #cell="vSectionCell"
75
+ [board]="board"
76
+ [cellKey]="plan.cellKey"
77
+ [prevCellKey]="plan.prevCellKey"
78
+ [hasLeading]="plan.hasLeading"
79
+ [hasTrailing]="plan.hasTrailing"
80
+ >
81
+ <ng-container
82
+ [vListOutlet]="plan.first.template"
83
+ [vListOutletContext]="cell.contextOf(plan.first)"
84
+ ></ng-container>
85
+ <ng-container
86
+ [vListOutlet]="plan.itemTemplate"
87
+ [vListOutletContext]="itemContextOf(entry, cell.separators)"
88
+ ></ng-container>
89
+ <ng-container
90
+ [vListOutlet]="plan.second.template"
91
+ [vListOutletContext]="cell.contextOf(plan.second)"
92
+ ></ng-container>
93
+ </ng-container>
94
+ }
71
95
  }
72
96
  }
73
97
  </ng-template>
@@ -88,21 +112,5 @@ export const VIRTUALIZED_SECTION_LIST_TEMPLATE = `
88
112
  <ng-container [vListOutlet]="listEmptyDir.templateRef"></ng-container>
89
113
  </ng-template>
90
114
  }
91
- <!-- Item separator: unwrap each flattened entry back to its item -->
92
- @if (itemSeparatorDir !== undefined) {
93
- <ng-template
94
- vListSeparator
95
- let-highlighted
96
- let-leadingItem="leadingItem"
97
- let-trailingItem="trailingItem"
98
- >
99
- <ng-container
100
- [vListOutlet]="itemSeparatorDir.templateRef"
101
- [vListOutletContext]="
102
- itemSeparatorContextOf(highlighted, leadingItem, trailingItem)
103
- "
104
- ></ng-container>
105
- </ng-template>
106
- }
107
115
  </VirtualizedList>
108
116
  `;
package/src/components.ts CHANGED
@@ -48,6 +48,8 @@ export type {
48
48
  // an app's `imports: [SafeAreaView]` becomes `imports: [SafeAreaViewElement]`. Only the prop type
49
49
  // stays.
50
50
  export type { IAngularSafeAreaViewProps } from './components/safe-area-view-props';
51
+ export type { IAngularLayoutConformanceProps } from './components/layout-conformance-props';
52
+ export type { ILayoutConformanceMode } from '@symbiote-native/components';
51
53
  // `Switch` is a TAG — `<switch>`, matched by `SwitchElement` — and there is nothing to import in
52
54
  // its place. RN gives it no statics, so the name exports nothing at all now; `[(value)]` is handled
53
55
  // by the renderer and `[(ngModel)]` by `SwitchValueAccessor` (`../elements`), which is where the
@@ -123,14 +125,19 @@ export type {
123
125
  // its place. RN's Button has no statics, so the name exports nothing at all now; the prop type
124
126
  // stays, for a component forwarding a bag.
125
127
  export type { IButtonProps } from './components/button-props';
128
+ export type { ICheckboxProps } from './components/checkbox-props';
126
129
  export {
127
130
  VirtualizedList,
131
+ VListCellDirective,
128
132
  VListEmptyDirective,
129
133
  VListFooterDirective,
130
134
  VListHeaderDirective,
131
135
  VListItemDirective,
136
+ VListOutletDirective,
132
137
  VListSeparatorDirective,
133
138
  } from './components/virtualized-list';
139
+ export { injectVirtualizedListScope } from './components/virtualized-list/nested-scope';
140
+ export type { IListScope } from '@symbiote-native/components';
134
141
  export type {
135
142
  ICellLayout,
136
143
  ISeparatorProps,
@@ -141,6 +148,7 @@ export type {
141
148
  IViewToken,
142
149
  IVirtualizedListHandle,
143
150
  IVirtualizedListProps,
151
+ IVListCellContext,
144
152
  IVListItemContext,
145
153
  IVListSeparatorContext,
146
154
  } from './components/virtualized-list';
@@ -159,6 +167,7 @@ export type {
159
167
  IVirtualizedSectionListProps,
160
168
  IVSectionContext,
161
169
  IVSectionItemContext,
170
+ IVSectionSeparatorContext,
162
171
  } from './components/virtualized-section-list';
163
172
  export { SectionList } from './components/section-list';
164
173
  export type {
@@ -0,0 +1,27 @@
1
+ import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
2
+ import type { IDescriptor } from '@symbiote-native/components';
3
+ import { SymbioteHostPropsDirective, ViewHost } from '../primitives';
4
+ import { DescriptorOutlet } from './index';
5
+
6
+ // Angular cannot project content into a node a Renderer2 loop built, so the root is a template
7
+ // `<view>` that takes the descriptor's props, and only the native leaf goes through the outlet
8
+ @Component({
9
+ selector: 'symbiote-descriptor-host',
10
+ standalone: true,
11
+ imports: [DescriptorOutlet, SymbioteHostPropsDirective, ViewHost],
12
+ changeDetection: ChangeDetectionStrategy.OnPush,
13
+ template: `<view [symbioteHostProps]="node.props">
14
+ @if (leafOf(node); as leaf) {
15
+ <symbiote-descriptor-outlet [node]="leaf" />
16
+ }
17
+ <ng-content />
18
+ </view>`,
19
+ })
20
+ export class DescriptorHost {
21
+ @Input({ required: true }) node!: IDescriptor;
22
+
23
+ // A native wrapper descriptor carries one own child at most, its absolute-fill native leaf
24
+ leafOf(descriptor: IDescriptor): IDescriptor | undefined {
25
+ return descriptor.children.find(child => typeof child !== 'string');
26
+ }
27
+ }
@@ -50,6 +50,11 @@ export class DescriptorOutlet implements OnChanges, OnDestroy {
50
50
  inject<ElementRef<unknown>>(ElementRef).nativeElement;
51
51
  private rendered: IRenderedElement | undefined;
52
52
 
53
+ // The host node of the root element, for a component that calls a function of its native view
54
+ get rootNode(): unknown {
55
+ return this.rendered?.node ?? null;
56
+ }
57
+
53
58
  ngOnChanges(changes: SimpleChanges): void {
54
59
  if (!('node' in changes) || this.node === undefined) return;
55
60
  this.rendered = this.patchRoot(this.rendered, this.node);
@@ -0,0 +1,289 @@
1
+ import {
2
+ ChangeDetectorRef,
3
+ Directive,
4
+ ElementRef,
5
+ ErrorHandler,
6
+ Input,
7
+ Renderer2,
8
+ inject,
9
+ } from '@angular/core';
10
+ import type {
11
+ OnChanges,
12
+ OnDestroy,
13
+ OnInit,
14
+ SimpleChanges,
15
+ } from '@angular/core';
16
+ import { VALUE_CHANGE_EVENT } from './renderer/value-change';
17
+ import {
18
+ registerViewFlush,
19
+ unregisterViewFlush,
20
+ } from './change-detection-flush';
21
+ import type { IElementProps } from './element-props';
22
+
23
+ /**
24
+ * The shared half of every element directive: the prop surface all tags accept, and the ONE
25
+ * generic forward that puts a claimed binding back on the engine node.
26
+ *
27
+ * The forward is the deciding fact of this whole route. A binding claimed by a directive input
28
+ * never reaches `Renderer2.setProperty` on its own — Angular writes it to the directive instance
29
+ * — so without this loop a bare tag commits nothing at all. It stays generic on purpose: per-prop
30
+ * forwarding code is a component wrapper by another name, which is what this migration removes.
31
+ * `renderer.setProperty` lands in the adapter's own renderer, which routes the value through the
32
+ * engine's `routeProp` and applies the `id` -> `nativeID` alias.
33
+ */
34
+ @Directive()
35
+ export abstract class SymbioteElement implements OnChanges {
36
+ private readonly renderer = inject(Renderer2);
37
+ protected readonly host = inject(ElementRef);
38
+
39
+ // NO `ChangeDetectorRef` HERE, and its absence is the point. This class is instantiated once per
40
+ // ELEMENT, so injecting one cost ~1.0-2.4 us on every tag of a screen to serve the few that carry
41
+ // an `on*` prop — ten thousand `ViewRef`s on a thousand-row create, read by none of them
42
+ // (`core/engine/cpp/tests/js/angular-directive-cost.itest.ts`). `SymbioteCallbackHost` owns one and
43
+ // MATCHES on the callback attributes instead, registering it against the node; `markViewFor` is how
44
+ // the wrapper reaches it. The inputs did not move, so nothing about this file's public surface did.
45
+
46
+ // No `on*` wrapper here: `SymbioteRenderer.setProperty` handles it. Most directives are withheld
47
+ // from runtime matching (`./runtime-matching`), so a binding reaches the renderer through
48
+ // `ɵɵproperty` without passing through any directive — the renderer is reached by every path.
49
+
50
+ @Input() testID?: IElementProps['testID'];
51
+ @Input() nativeID?: IElementProps['nativeID'];
52
+ @Input() id?: IElementProps['id'];
53
+ @Input() accessible?: IElementProps['accessible'];
54
+ @Input() accessibilityLabel?: IElementProps['accessibilityLabel'];
55
+ @Input() accessibilityHint?: IElementProps['accessibilityHint'];
56
+ @Input() accessibilityRole?: IElementProps['accessibilityRole'];
57
+ @Input() accessibilityState?: IElementProps['accessibilityState'];
58
+ @Input() accessibilityValue?: IElementProps['accessibilityValue'];
59
+ @Input() accessibilityActions?: IElementProps['accessibilityActions'];
60
+ @Input()
61
+ experimental_accessibilityOrder?: IElementProps['experimental_accessibilityOrder'];
62
+ @Input() accessibilityLabelledBy?: IElementProps['accessibilityLabelledBy'];
63
+ @Input()
64
+ importantForAccessibility?: IElementProps['importantForAccessibility'];
65
+ @Input() accessibilityLiveRegion?: IElementProps['accessibilityLiveRegion'];
66
+ @Input() screenReaderFocusable?: IElementProps['screenReaderFocusable'];
67
+ @Input() accessibilityViewIsModal?: IElementProps['accessibilityViewIsModal'];
68
+ @Input()
69
+ accessibilityElementsHidden?: IElementProps['accessibilityElementsHidden'];
70
+ @Input()
71
+ accessibilityIgnoresInvertColors?: IElementProps['accessibilityIgnoresInvertColors'];
72
+ @Input() accessibilityLanguage?: IElementProps['accessibilityLanguage'];
73
+ @Input()
74
+ accessibilityRespondsToUserInteraction?: IElementProps['accessibilityRespondsToUserInteraction'];
75
+ @Input()
76
+ accessibilityShowsLargeContentViewer?: IElementProps['accessibilityShowsLargeContentViewer'];
77
+ @Input()
78
+ accessibilityLargeContentTitle?: IElementProps['accessibilityLargeContentTitle'];
79
+ @Input() onAccessibilityAction?: IElementProps['onAccessibilityAction'];
80
+ @Input() onAccessibilityTap?: IElementProps['onAccessibilityTap'];
81
+ @Input() onMagicTap?: IElementProps['onMagicTap'];
82
+ @Input() onAccessibilityEscape?: IElementProps['onAccessibilityEscape'];
83
+
84
+ // The web aliases. Native reads only `accessibility*`; the engine folds these on the way in.
85
+ @Input() role?: IElementProps['role'];
86
+ @Input() 'aria-label'?: IElementProps['aria-label'];
87
+ @Input() 'aria-labelledby'?: IElementProps['aria-labelledby'];
88
+ @Input() 'aria-live'?: IElementProps['aria-live'];
89
+ @Input() 'aria-hidden'?: IElementProps['aria-hidden'];
90
+ @Input() 'aria-busy'?: IElementProps['aria-busy'];
91
+ @Input() 'aria-checked'?: IElementProps['aria-checked'];
92
+ @Input() 'aria-disabled'?: IElementProps['aria-disabled'];
93
+ @Input() 'aria-expanded'?: IElementProps['aria-expanded'];
94
+ @Input() 'aria-selected'?: IElementProps['aria-selected'];
95
+ @Input() 'aria-modal'?: IElementProps['aria-modal'];
96
+ @Input() 'aria-valuemax'?: IElementProps['aria-valuemax'];
97
+ @Input() 'aria-valuemin'?: IElementProps['aria-valuemin'];
98
+ @Input() 'aria-valuenow'?: IElementProps['aria-valuenow'];
99
+ @Input() 'aria-valuetext'?: IElementProps['aria-valuetext'];
100
+
101
+ // The responder gates return a boolean, which an Angular `(event)` binding cannot carry back to
102
+ // the caller — so the whole family is inputs, never outputs.
103
+ @Input()
104
+ onStartShouldSetResponder?: IElementProps['onStartShouldSetResponder'];
105
+ @Input()
106
+ onStartShouldSetResponderCapture?: IElementProps['onStartShouldSetResponderCapture'];
107
+ @Input() onMoveShouldSetResponder?: IElementProps['onMoveShouldSetResponder'];
108
+ @Input()
109
+ onMoveShouldSetResponderCapture?: IElementProps['onMoveShouldSetResponderCapture'];
110
+ @Input() onResponderGrant?: IElementProps['onResponderGrant'];
111
+ @Input() onResponderReject?: IElementProps['onResponderReject'];
112
+ @Input() onResponderStart?: IElementProps['onResponderStart'];
113
+ @Input() onResponderMove?: IElementProps['onResponderMove'];
114
+ @Input() onResponderEnd?: IElementProps['onResponderEnd'];
115
+ @Input() onResponderRelease?: IElementProps['onResponderRelease'];
116
+ @Input() onResponderTerminate?: IElementProps['onResponderTerminate'];
117
+ @Input()
118
+ onResponderTerminationRequest?: IElementProps['onResponderTerminationRequest'];
119
+ @Input() onTouchStart?: IElementProps['onTouchStart'];
120
+ @Input() onTouchStartCapture?: IElementProps['onTouchStartCapture'];
121
+ @Input() onTouchMove?: IElementProps['onTouchMove'];
122
+ @Input() onTouchMoveCapture?: IElementProps['onTouchMoveCapture'];
123
+ @Input() onTouchEnd?: IElementProps['onTouchEnd'];
124
+ @Input() onTouchEndCapture?: IElementProps['onTouchEndCapture'];
125
+ @Input() onTouchCancel?: IElementProps['onTouchCancel'];
126
+ @Input() onTouchCancelCapture?: IElementProps['onTouchCancelCapture'];
127
+ @Input() onFocusCapture?: IElementProps['onFocusCapture'];
128
+ @Input() onBlurCapture?: IElementProps['onBlurCapture'];
129
+ @Input() onClickCapture?: IElementProps['onClickCapture'];
130
+ @Input() onKeyDown?: IElementProps['onKeyDown'];
131
+ @Input() onKeyDownCapture?: IElementProps['onKeyDownCapture'];
132
+ @Input() onKeyUp?: IElementProps['onKeyUp'];
133
+ @Input() onKeyUpCapture?: IElementProps['onKeyUpCapture'];
134
+
135
+ @Input() pointerEvents?: IElementProps['pointerEvents'];
136
+ @Input() hitSlop?: IElementProps['hitSlop'];
137
+ @Input() focusable?: IElementProps['focusable'];
138
+ @Input() collapsable?: IElementProps['collapsable'];
139
+ @Input() removeClippedSubviews?: IElementProps['removeClippedSubviews'];
140
+ @Input()
141
+ renderToHardwareTextureAndroid?: IElementProps['renderToHardwareTextureAndroid'];
142
+ @Input() shouldRasterizeIOS?: IElementProps['shouldRasterizeIOS'];
143
+ @Input()
144
+ needsOffscreenAlphaCompositing?: IElementProps['needsOffscreenAlphaCompositing'];
145
+ // `[style]` and `[class]` are declared for the TYPE CHECK only. Withheld from run-time matching, no
146
+ // input claims them, so Angular's own styling engine decomposes them into `setStyle`/`addClass`
147
+ // exactly as on a DOM element - which is why `style` is typed as the object/string that engine can
148
+ // hold. An RN style ARRAY or press-state FUNCTION throws in there ("indexOf is not a function",
149
+ // "Unsupported styling type: function"), so it is `[styleProp]`: an ordinary property binding the
150
+ // renderer routes to `style` whole. A claim would cost a directive instance per element.
151
+ //
152
+ // The press-state callback rides the BASE type rather than `PressableElement` alone: a subclass
153
+ // cannot widen an inherited property, and only the pressables have a `pressed` to read — so on
154
+ // any other tag the engine resolves it at `pressed: false`.
155
+ @Input() style?: IElementProps['style'];
156
+ @Input() styleProp?: IElementProps['styleProp'];
157
+
158
+ // `ɵɵclassMap` hands over a string after joining any static `class=` prefix; `[class.foo]` and
159
+ // `[ngClass]` arrive as `addClass`. The renderer unions both sources (`classStringFor`).
160
+ @Input() class?: string;
161
+
162
+ // The flat-bag spelling of the events below. `(press)` and `[onPress]` are both supported and
163
+ // land in the same place; an app that already holds a handler bag binds the props.
164
+ @Input() onPress?: IElementProps['onPress'];
165
+ @Input() onPressIn?: IElementProps['onPressIn'];
166
+ @Input() onPressOut?: IElementProps['onPressOut'];
167
+ @Input() onPressMove?: IElementProps['onPressMove'];
168
+ @Input() onLongPress?: IElementProps['onLongPress'];
169
+ @Input() onLayout?: IElementProps['onLayout'];
170
+ @Input() onFocus?: IElementProps['onFocus'];
171
+ @Input() onBlur?: IElementProps['onBlur'];
172
+
173
+ ngOnChanges(changes: SimpleChanges): void {
174
+ for (const name of Object.keys(changes)) {
175
+ this.renderer.setProperty(
176
+ this.host.nativeElement,
177
+ name,
178
+ // Unwrapped on purpose: `SymbioteRenderer.setProperty` is where an `on*` value is wrapped
179
+ // now, and it is the call this line makes.
180
+ changes[name]?.currentValue,
181
+ );
182
+ }
183
+ }
184
+ }
185
+
186
+ /**
187
+ * The base for a tag whose app callback an engine behavior READS BACK inside the same microtask
188
+ * turn — `<text-input>`, `<switch>`, `<refresh-control>`. Zoneless change detection is a macrotask,
189
+ * so without this the behavior sees the PRE-event value and undoes the user; the full mechanism,
190
+ * and why this is `detectChanges()` on one view rather than `ApplicationRef.tick()`, is in
191
+ * `./change-detection-flush`.
192
+ *
193
+ * The directive is the only thing in the adapter that owns a `ChangeDetectorRef` for the view
194
+ * holding the binding, so it hands one to the renderer keyed on its own node.
195
+ */
196
+ @Directive()
197
+ export abstract class ReadBackElement
198
+ extends SymbioteElement
199
+ implements OnDestroy
200
+ {
201
+ // ITS OWN, now that the base has none. Three tags reach this class, so the injection is paid where
202
+ // it is read instead of on every element of a screen.
203
+ private readonly detector = inject(ChangeDetectorRef);
204
+ private readonly errorHandler = inject(ErrorHandler);
205
+
206
+ constructor() {
207
+ super();
208
+ const node: unknown = this.host.nativeElement;
209
+ if (typeof node === 'object' && node !== null) {
210
+ registerViewFlush(node, () => this.flush());
211
+ }
212
+ }
213
+
214
+ // REPORTED, NOT RETHROWN — Angular's own contract. Every other change detection runs inside
215
+ // `ApplicationRef.tick()`, caught by `ErrorHandler` (`SymbioteErrorHandler`); this flush instead
216
+ // runs from a native event dispatch, where an uncaught throw has nowhere to go but `RCTFatal`.
217
+ private flush(): void {
218
+ try {
219
+ this.detector.detectChanges();
220
+ } catch (error: unknown) {
221
+ this.errorHandler.handleError(error);
222
+ }
223
+ }
224
+
225
+ ngOnDestroy(): void {
226
+ const node: unknown = this.host.nativeElement;
227
+ if (typeof node === 'object' && node !== null) unregisterViewFlush(node);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * The `[(value)]` half of a controlled tag — `<switch>` and `<text-input>`.
233
+ *
234
+ * The one `@Output` in this file, and it is FORCED rather than chosen. `[(value)]` desugars to
235
+ * `[value]` + `(valueChange)`, and ngtsc requires both halves to resolve to the SAME target: a
236
+ * declared `value` input beside an event no directive claims is NG8007, "the property and event
237
+ * halves are not bound to the same target". So the sugar this adapter documents cannot work without
238
+ * the output existing, whatever the file header says about events in general.
239
+ *
240
+ * The bridge below is NOT what makes the binding work today, and the comment says so because the
241
+ * obvious reading is wrong: the file header's "an `@Output` CONSUMES the binding" holds for a
242
+ * COMPONENT, and an element is the other case — Angular attaches the renderer listener for the
243
+ * event as well, so `Renderer2.listen` still runs and the engine still hears the change. Measured
244
+ * under JIT by deleting this whole hook: every case in `renderer/two-way-value.test.ts` stayed
245
+ * green, delivery included, and exactly ONCE (nothing double-fires when both paths exist).
246
+ *
247
+ * It is kept because the measurement is JIT-only and this adapter has a recorded case of JIT and
248
+ * AOT resolving a binding differently (`test-harness-false-greens.md` §21). If AOT routes the event
249
+ * exclusively to the output, this hook is the only thing keeping `[(value)]` alive; if it routes to
250
+ * both, it is one extra listener on a control an app explicitly bound. Delete it once something
251
+ * executes the LINKED artifact and shows the renderer listener is attached there too.
252
+ *
253
+ * Opened only when something is SUBSCRIBED: the prop's PRESENCE is what a behavior reads to decide
254
+ * it is controlled, so a `<switch>` given a handler nobody asked for changes how it snaps back.
255
+ * `.observed` is readable from `ngOnInit` because Angular subscribes outputs in the creation pass,
256
+ * before the update pass runs the hook — observed directly (true for the two bound tags, false for
257
+ * an unbound one), not assumed.
258
+ */
259
+ @Directive()
260
+ export abstract class ValueChangeElement
261
+ extends ReadBackElement
262
+ implements OnInit, OnDestroy
263
+ {
264
+ private readonly valueRenderer = inject(Renderer2);
265
+ private readonly valueHost = inject(ElementRef);
266
+ private unlistenValue?: () => void;
267
+
268
+ /** `this.valueChange.observed` — the subclass owns the emitter so its payload type stays exact. */
269
+ protected abstract hasValueSubscriber(): boolean;
270
+
271
+ /** Narrows the engine's unwrapped value to the type THIS tag emits, then emits it. */
272
+ protected abstract emitValue(value: unknown): void;
273
+
274
+ ngOnInit(): void {
275
+ if (!this.hasValueSubscriber()) return;
276
+ this.unlistenValue = this.valueRenderer.listen(
277
+ this.valueHost.nativeElement,
278
+ VALUE_CHANGE_EVENT,
279
+ (value: unknown) => {
280
+ this.emitValue(value);
281
+ },
282
+ );
283
+ }
284
+
285
+ override ngOnDestroy(): void {
286
+ super.ngOnDestroy();
287
+ this.unlistenValue?.();
288
+ }
289
+ }
@@ -17,6 +17,7 @@ import type {
17
17
  IPressState,
18
18
  IRectOffset,
19
19
  IResponderProps,
20
+ ITextNativeOnlyProps,
20
21
  } from '@symbiote-native/components';
21
22
  import type {
22
23
  ISymbioteEvent,
@@ -38,38 +39,39 @@ export type IAngularStyleBinding<TStyle> = TStyle | string | null | undefined;
38
39
  export type IElementStyleProp =
39
40
  IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
40
41
 
41
- export interface IElementProps
42
- extends IAccessibilityProps, IAriaProps, IResponderProps {
43
- /** RN's modern W3C-named alias for `nativeID`; the renderer folds it (`PROP_ALIASES`). */
44
- id?: string;
45
- pointerEvents?: 'auto' | 'none' | 'box-none' | 'box-only';
46
- hitSlop?: IRectOffset | number;
47
- focusable?: boolean;
48
- collapsable?: boolean;
49
- removeClippedSubviews?: boolean;
50
- renderToHardwareTextureAndroid?: boolean;
51
- shouldRasterizeIOS?: boolean;
52
- needsOffscreenAlphaCompositing?: boolean;
53
- style?: IAngularStyleBinding<IViewStyle>;
54
- /**
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`.
59
- */
60
- styleProp?: IElementStyleProp;
61
- onPress?: IEventHandler;
62
- onPressIn?: IEventHandler;
63
- onPressOut?: IEventHandler;
64
- onPressMove?: IEventHandler;
65
- onLongPress?: IEventHandler;
66
- onLayout?: IEventHandler;
67
- onFocus?: IEventHandler;
68
- onBlur?: IEventHandler;
69
- }
42
+ export type IElementProps = IAccessibilityProps &
43
+ IAriaProps &
44
+ IResponderProps & {
45
+ /** RN's modern W3C-named alias for `nativeID`; the renderer folds it (`PROP_ALIASES`). */
46
+ id?: string;
47
+ pointerEvents?: 'auto' | 'none' | 'box-none' | 'box-only';
48
+ hitSlop?: IRectOffset | number;
49
+ focusable?: boolean;
50
+ collapsable?: boolean;
51
+ removeClippedSubviews?: boolean;
52
+ renderToHardwareTextureAndroid?: boolean;
53
+ shouldRasterizeIOS?: boolean;
54
+ needsOffscreenAlphaCompositing?: boolean;
55
+ style?: IAngularStyleBinding<IViewStyle>;
56
+ /**
57
+ * An RN StyleProp as ONE property binding: arrays, falsy entries, and the press-state callback.
58
+ * The callback is in the union because a subclass cannot widen an inherited property and
59
+ * `<pressable>`/`<touchable-*>` are the tags that take one; on every other tag the engine resolves
60
+ * it at `pressed: false`.
61
+ */
62
+ styleProp?: IElementStyleProp;
63
+ onPress?: IEventHandler;
64
+ onPressIn?: IEventHandler;
65
+ onPressOut?: IEventHandler;
66
+ onPressMove?: IEventHandler;
67
+ onLongPress?: IEventHandler;
68
+ onLayout?: IEventHandler;
69
+ onFocus?: IEventHandler;
70
+ onBlur?: IEventHandler;
71
+ };
70
72
 
71
73
  /** Text's own surface on top of the shared one. `onTextLayout` is an event, so it is not here. */
72
- export interface ITextElementProps {
74
+ export type ITextElementProps = ITextNativeOnlyProps & {
73
75
  /** Narrows the shared `style` so `fontSize` / `fontWeight` type-check on a `<text>`. */
74
76
  style?: IAngularStyleBinding<ITextStyle>;
75
77
  styleProp?: IStyleProp<ITextStyle>;
@@ -84,12 +86,12 @@ export interface ITextElementProps {
84
86
  // RN's Text carries it (Text.js) and Button hands it to the label so a screen reader announces
85
87
  // the text as disabled along with the button holding it (Button.js:388).
86
88
  disabled?: boolean;
87
- }
89
+ };
88
90
 
89
91
  /** The sticky-header wrapper RN builds in JS (ScrollViewStickyHeader.js). */
90
- export interface IStickyHeaderElementProps {
92
+ export type IStickyHeaderElementProps = {
91
93
  inverted?: boolean;
92
94
  nextHeaderLayoutY?: number | null;
93
95
  scrollAnimatedValue?: unknown;
94
96
  scrollViewHeight?: number | null;
95
- }
97
+ };
@@ -0,0 +1,31 @@
1
+ import { Directive, Input } from '@angular/core';
2
+ import type { ITextInputProps } from '@symbiote-native/components';
3
+ import { ValueChangeElement } from './element-base';
4
+
5
+ // The inputs only one platform's native view reads, split from `TextInputElement` for file size
6
+ @Directive()
7
+ export abstract class TextInputNativeInputs extends ValueChangeElement {
8
+ @Input() caretHidden?: ITextInputProps['caretHidden'];
9
+ @Input() clearButtonMode?: ITextInputProps['clearButtonMode'];
10
+ @Input() clearTextOnFocus?: ITextInputProps['clearTextOnFocus'];
11
+ @Input() contextMenuHidden?: ITextInputProps['contextMenuHidden'];
12
+ @Input() dataDetectorTypes?: ITextInputProps['dataDetectorTypes'];
13
+ @Input() disableFullscreenUI?: ITextInputProps['disableFullscreenUI'];
14
+ @Input()
15
+ disableKeyboardShortcuts?: ITextInputProps['disableKeyboardShortcuts'];
16
+ @Input()
17
+ enablesReturnKeyAutomatically?: ITextInputProps['enablesReturnKeyAutomatically'];
18
+ @Input() importantForAutofill?: ITextInputProps['importantForAutofill'];
19
+ @Input() inlineImageLeft?: ITextInputProps['inlineImageLeft'];
20
+ @Input() inlineImagePadding?: ITextInputProps['inlineImagePadding'];
21
+ @Input()
22
+ inputAccessoryViewButtonLabel?: ITextInputProps['inputAccessoryViewButtonLabel'];
23
+ @Input() keyboardAppearance?: ITextInputProps['keyboardAppearance'];
24
+ @Input() lineBreakModeIOS?: ITextInputProps['lineBreakModeIOS'];
25
+ @Input() lineBreakStrategyIOS?: ITextInputProps['lineBreakStrategyIOS'];
26
+ @Input() passwordRules?: ITextInputProps['passwordRules'];
27
+ @Input() returnKeyLabel?: ITextInputProps['returnKeyLabel'];
28
+ @Input() smartInsertDelete?: ITextInputProps['smartInsertDelete'];
29
+ @Input() spellCheck?: ITextInputProps['spellCheck'];
30
+ @Input() textBreakStrategy?: ITextInputProps['textBreakStrategy'];
31
+ }