@symbiote-native/angular 0.7.0 → 0.8.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 (158) hide show
  1. package/README.md +3 -3
  2. package/build/angular/bootstrap.js +1 -1
  3. package/build/angular/bootstrap.js.map +1 -1
  4. package/build/angular/components/activity-indicator/index.android.js +7 -2
  5. package/build/angular/components/activity-indicator/index.android.js.map +1 -1
  6. package/build/angular/components/activity-indicator/index.ios.js +3 -1
  7. package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
  8. package/build/angular/components/activity-indicator/shared.d.ts +1 -1
  9. package/build/angular/components/activity-indicator/shared.js +1 -1
  10. package/build/angular/components/activity-indicator/shared.js.map +1 -1
  11. package/build/angular/components/button.js +13 -5
  12. package/build/angular/components/button.js.map +1 -1
  13. package/build/angular/components/flat-list/index.d.ts +1 -1
  14. package/build/angular/components/flat-list/index.js +47 -14
  15. package/build/angular/components/flat-list/index.js.map +1 -1
  16. package/build/angular/components/image/index.android.js +4 -2
  17. package/build/angular/components/image/index.android.js.map +1 -1
  18. package/build/angular/components/image/index.ios.js +4 -2
  19. package/build/angular/components/image/index.ios.js.map +1 -1
  20. package/build/angular/components/image/shared.js +18 -6
  21. package/build/angular/components/image/shared.js.map +1 -1
  22. package/build/angular/components/image-background.js +22 -8
  23. package/build/angular/components/image-background.js.map +1 -1
  24. package/build/angular/components/input-accessory-view/index.js +7 -2
  25. package/build/angular/components/input-accessory-view/index.js.map +1 -1
  26. package/build/angular/components/keyboard-avoiding-view/index.d.ts +1 -0
  27. package/build/angular/components/keyboard-avoiding-view/index.js +39 -9
  28. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  29. package/build/angular/components/modal/index.js +7 -2
  30. package/build/angular/components/modal/index.js.map +1 -1
  31. package/build/angular/components/pressable/index.d.ts +1 -1
  32. package/build/angular/components/pressable/index.js +6 -2
  33. package/build/angular/components/pressable/index.js.map +1 -1
  34. package/build/angular/components/refresh-control/index.js +3 -1
  35. package/build/angular/components/refresh-control/index.js.map +1 -1
  36. package/build/angular/components/safe-area-view/index.js +3 -1
  37. package/build/angular/components/safe-area-view/index.js.map +1 -1
  38. package/build/angular/components/scroll-view/index.android.js +44 -14
  39. package/build/angular/components/scroll-view/index.android.js.map +1 -1
  40. package/build/angular/components/scroll-view/index.ios.js +13 -5
  41. package/build/angular/components/scroll-view/index.ios.js.map +1 -1
  42. package/build/angular/components/scroll-view/projection.d.ts +32 -0
  43. package/build/angular/components/scroll-view/projection.js +212 -47
  44. package/build/angular/components/scroll-view/projection.js.map +1 -1
  45. package/build/angular/components/scroll-view/shared.js +28 -7
  46. package/build/angular/components/scroll-view/shared.js.map +1 -1
  47. package/build/angular/components/scroll-view/sticky-header.js +7 -3
  48. package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
  49. package/build/angular/components/section-list/index.d.ts +6 -1
  50. package/build/angular/components/section-list/index.js +63 -18
  51. package/build/angular/components/section-list/index.js.map +1 -1
  52. package/build/angular/components/switch/index.android.js +17 -3
  53. package/build/angular/components/switch/index.android.js.map +1 -1
  54. package/build/angular/components/switch/index.ios.js +17 -3
  55. package/build/angular/components/switch/index.ios.js.map +1 -1
  56. package/build/angular/components/switch/shared.d.ts +1 -1
  57. package/build/angular/components/switch/shared.js +5 -2
  58. package/build/angular/components/switch/shared.js.map +1 -1
  59. package/build/angular/components/text-input.d.ts +1 -1
  60. package/build/angular/components/text-input.js +25 -5
  61. package/build/angular/components/text-input.js.map +1 -1
  62. package/build/angular/components/touchable/index.d.ts +29 -7
  63. package/build/angular/components/touchable/index.js +246 -69
  64. package/build/angular/components/touchable/index.js.map +1 -1
  65. package/build/angular/components/touchable-native-feedback/index.js +2 -2
  66. package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
  67. package/build/angular/components/virtualized-list/directives.js +4 -0
  68. package/build/angular/components/virtualized-list/directives.js.map +1 -1
  69. package/build/angular/components/virtualized-list/index.d.ts +5 -2
  70. package/build/angular/components/virtualized-list/index.js +197 -60
  71. package/build/angular/components/virtualized-list/index.js.map +1 -1
  72. package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
  73. package/build/angular/components/virtualized-section-list/index.d.ts +18 -1
  74. package/build/angular/components/virtualized-section-list/index.js +90 -21
  75. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  76. package/build/angular/components.d.ts +1 -1
  77. package/build/angular/components.js.map +1 -1
  78. package/build/angular/create-portal/index.js +5 -1
  79. package/build/angular/create-portal/index.js.map +1 -1
  80. package/build/angular/create-tunnel/index.js.map +1 -1
  81. package/build/angular/descriptor-to-angular/index.js +3 -1
  82. package/build/angular/descriptor-to-angular/index.js.map +1 -1
  83. package/build/angular/diagnostics.d.ts +33 -0
  84. package/build/angular/diagnostics.js +82 -0
  85. package/build/angular/diagnostics.js.map +1 -0
  86. package/build/angular/host-instance/index.js +1 -1
  87. package/build/angular/host-instance/index.js.map +1 -1
  88. package/build/angular/index.d.ts +5 -2
  89. package/build/angular/index.js +15 -1
  90. package/build/angular/index.js.map +1 -1
  91. package/build/angular/modules/animated/create-animated-component.js +27 -9
  92. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  93. package/build/angular/modules/app-registry/index.js.map +1 -1
  94. package/build/angular/modules/status-bar/index.js +1 -1
  95. package/build/angular/modules/status-bar/index.js.map +1 -1
  96. package/build/angular/primitives/index.d.ts +19 -2
  97. package/build/angular/primitives/index.js +39 -3
  98. package/build/angular/primitives/index.js.map +1 -1
  99. package/build/angular/primitives/shared.d.ts +9 -1
  100. package/build/angular/primitives/shared.js +49 -3
  101. package/build/angular/primitives/shared.js.map +1 -1
  102. package/build/angular/render/index.js +48 -5
  103. package/build/angular/render/index.js.map +1 -1
  104. package/build/angular/renderer/index.d.ts +1 -0
  105. package/build/angular/renderer/index.js +37 -2
  106. package/build/angular/renderer/index.js.map +1 -1
  107. package/build/angular/services/color-scheme.service.js.map +1 -1
  108. package/build/angular/services/window-dimensions.service.js +1 -1
  109. package/build/angular/services/window-dimensions.service.js.map +1 -1
  110. package/package.json +5 -5
  111. package/src/bootstrap.ts +4 -1
  112. package/src/components/activity-indicator/index.android.ts +7 -2
  113. package/src/components/activity-indicator/index.ios.ts +3 -1
  114. package/src/components/activity-indicator/shared.ts +32 -13
  115. package/src/components/button.ts +19 -6
  116. package/src/components/flat-list/index.ts +91 -29
  117. package/src/components/image/index.android.ts +9 -2
  118. package/src/components/image/index.ios.ts +9 -2
  119. package/src/components/image/shared.ts +58 -19
  120. package/src/components/image-background.ts +29 -10
  121. package/src/components/input-accessory-view/index.ts +27 -10
  122. package/src/components/keyboard-avoiding-view/index.ts +55 -15
  123. package/src/components/modal/index.ts +22 -8
  124. package/src/components/pressable/index.ts +51 -21
  125. package/src/components/refresh-control/index.ts +22 -8
  126. package/src/components/safe-area-view/index.ts +16 -7
  127. package/src/components/scroll-view/index.android.ts +31 -9
  128. package/src/components/scroll-view/index.ios.ts +17 -4
  129. package/src/components/scroll-view/projection.ts +290 -63
  130. package/src/components/scroll-view/shared.ts +93 -26
  131. package/src/components/scroll-view/sticky-header.ts +25 -8
  132. package/src/components/section-list/index.ts +61 -19
  133. package/src/components/switch/index.android.ts +14 -3
  134. package/src/components/switch/index.ios.ts +14 -3
  135. package/src/components/switch/shared.ts +15 -4
  136. package/src/components/text-input.ts +45 -13
  137. package/src/components/touchable/index.ts +296 -72
  138. package/src/components/touchable-native-feedback/index.ts +32 -10
  139. package/src/components/virtualized-list/directives.ts +19 -5
  140. package/src/components/virtualized-list/index.ts +274 -72
  141. package/src/components/virtualized-section-list/directives.ts +6 -3
  142. package/src/components/virtualized-section-list/index.ts +146 -34
  143. package/src/components.ts +4 -1
  144. package/src/create-portal/index.ts +8 -2
  145. package/src/create-tunnel/index.ts +3 -1
  146. package/src/descriptor-to-angular/index.ts +29 -8
  147. package/src/diagnostics.ts +138 -0
  148. package/src/host-instance/index.ts +9 -3
  149. package/src/index.ts +31 -2
  150. package/src/modules/animated/create-animated-component.ts +44 -15
  151. package/src/modules/app-registry/index.ts +3 -1
  152. package/src/modules/status-bar/index.ts +13 -4
  153. package/src/primitives/index.ts +46 -2
  154. package/src/primitives/shared.ts +70 -9
  155. package/src/render/index.ts +56 -6
  156. package/src/renderer/index.ts +76 -14
  157. package/src/services/color-scheme.service.ts +3 -1
  158. package/src/services/window-dimensions.service.ts +11 -4
@@ -28,6 +28,12 @@ import { descriptorFor } from '@symbiote-native/components';
28
28
  import type { Renderer2, RendererFactory2, RendererType2 } from '@angular/core';
29
29
  import { isAnchorHostComponent } from '../anchor-host-registry';
30
30
  import {
31
+ countAngular,
32
+ noteAngularCreate,
33
+ noteAngularWrite,
34
+ } from '../diagnostics';
35
+ import {
36
+ flushScrollViewProjections,
31
37
  getScrollViewProjection,
32
38
  removeScrollViewProjectedChild,
33
39
  } from '../components/scroll-view/projection';
@@ -68,7 +74,9 @@ function describeHost(node: IHostElement | null | undefined): string {
68
74
  if (node === null || node === undefined) return 'null';
69
75
  if (isSurface(node)) return 'surface';
70
76
  const anchorId = anchorDebugIds.get(node);
71
- return anchorId !== undefined ? `${node.component}#${anchorId}` : node.component;
77
+ return anchorId !== undefined
78
+ ? `${node.component}#${anchorId}`
79
+ : node.component;
72
80
  }
73
81
 
74
82
  const PRIMITIVE_SELECTOR_ALIAS: Record<string, string> = {
@@ -86,7 +94,9 @@ const PRIMITIVE_SELECTOR_ALIAS: Record<string, string> = {
86
94
  // but guard anyway for parity with the Vue adapter and to fail loudly on a bad template.
87
95
  function assertTextPlacement(child: ISymbioteNode, parent: IHostElement): void {
88
96
  if (isRawText(child) && (isSurface(parent) || !parent.isText)) {
89
- throw new Error(`Text string "${String(child.props.text)}" must be rendered inside a <Text>`);
97
+ throw new Error(
98
+ `Text string "${String(child.props.text)}" must be rendered inside a <Text>`,
99
+ );
90
100
  }
91
101
  }
92
102
 
@@ -111,10 +121,14 @@ export class SymbioteRenderer implements Renderer2 {
111
121
  // any other string flows through as a raw Fabric name (events/processors derived from its
112
122
  // ViewConfig). toPublicInstance grafts the imperative API (measure / setNativeProps /
113
123
  // focus) onto the raw node in place, returning the SAME identity the commit mirror keys on.
124
+ countAngular('nodesCreated');
125
+ noteAngularCreate(name);
114
126
  const engineName = PRIMITIVE_SELECTOR_ALIAS[name] ?? name;
115
127
  if (isAnchorHostComponent(engineName)) {
116
128
  const anchor = tagAnchorForDebug(createAnchor());
117
- dlog(`angular createElement ${name} -> anchor host ${describeHost(anchor)}`);
129
+ dlog(
130
+ `angular createElement ${name} -> anchor host ${describeHost(anchor)}`,
131
+ );
118
132
  return anchor;
119
133
  }
120
134
 
@@ -128,12 +142,14 @@ export class SymbioteRenderer implements Renderer2 {
128
142
  // Angular structural directives (*ngIf / @if / @for) need anchor nodes to track
129
143
  // position. A real retained node the commit walk SKIPS — no native view. Twin of the
130
144
  // Vue createComment path.
145
+ countAngular('nodesCreated');
131
146
  const anchor = tagAnchorForDebug(createAnchor());
132
147
  dlog(`Angular renderer createComment -> ${describeHost(anchor)}`);
133
148
  return anchor;
134
149
  }
135
150
 
136
151
  createText(value: string): IHostNode {
152
+ countAngular('nodesCreated');
137
153
  return createRawText(value);
138
154
  }
139
155
 
@@ -142,9 +158,12 @@ export class SymbioteRenderer implements Renderer2 {
142
158
  // (see parentNode below) — mirrors Angular's own `if (parentRNode !== null)` guard in
143
159
  // addLViewToLContainer: skip silently now, the later projection pass places it correctly.
144
160
  if (parent === null) return;
161
+ countAngular('nodesInserted');
145
162
  assertTextPlacement(newChild, parent);
146
163
  if (isSurface(parent)) {
147
- dlog(`Angular renderer appendChild parent=surface child=${describeHost(newChild)}`);
164
+ dlog(
165
+ `Angular renderer appendChild parent=surface child=${describeHost(newChild)}`,
166
+ );
148
167
  parent.appendChild(newChild);
149
168
  } else {
150
169
  const projection = getScrollViewProjection(parent);
@@ -162,8 +181,13 @@ export class SymbioteRenderer implements Renderer2 {
162
181
  this.surface.requestCommit();
163
182
  }
164
183
 
165
- insertBefore(parent: IHostElement | null, newChild: IHostNode, refChild: IHostNode | null): void {
184
+ insertBefore(
185
+ parent: IHostElement | null,
186
+ newChild: IHostNode,
187
+ refChild: IHostNode | null,
188
+ ): void {
166
189
  if (parent === null) return; // see the appendChild guard above
190
+ countAngular('nodesInserted');
167
191
  assertTextPlacement(newChild, parent);
168
192
  if (isSurface(parent)) {
169
193
  dlog(
@@ -177,10 +201,15 @@ export class SymbioteRenderer implements Renderer2 {
177
201
  `Angular renderer insertBefore parent=${describeHost(parent)} child=${describeHost(newChild)} ref=${refChild ? describeHost(refChild) : 'null'} projection=${projection !== undefined}`,
178
202
  );
179
203
  if (projection !== undefined) {
180
- projection.insertProjectedChild(parent, newChild, refChild, (target, child, before) => {
181
- if (before === undefined) appendChild(target, child);
182
- else insertBefore(target, child, before);
183
- });
204
+ projection.insertProjectedChild(
205
+ parent,
206
+ newChild,
207
+ refChild,
208
+ (target, child, before) => {
209
+ if (before === undefined) appendChild(target, child);
210
+ else insertBefore(target, child, before);
211
+ },
212
+ );
184
213
  } else if (refChild) {
185
214
  insertBefore(parent, newChild, refChild);
186
215
  } else {
@@ -194,10 +223,13 @@ export class SymbioteRenderer implements Renderer2 {
194
223
  // Detach from the child's own retained parent (a top-level node lives in
195
224
  // surface.children with no parent). Angular's `parent` arg is ignored in favor of the
196
225
  // authoritative link, mirroring the Vue adapter's remove.
226
+ countAngular('nodesRemoved');
197
227
  const angularParent = _parent !== null ? describeHost(_parent) : 'null';
198
- const retainedParent = oldChild.parent !== undefined ? describeHost(oldChild.parent) : 'none';
199
- const wasProjected = removeScrollViewProjectedChild(oldChild, (parent, child) =>
200
- removeChild(parent, child),
228
+ const retainedParent =
229
+ oldChild.parent !== undefined ? describeHost(oldChild.parent) : 'none';
230
+ const wasProjected = removeScrollViewProjectedChild(
231
+ oldChild,
232
+ (parent, child) => removeChild(parent, child),
201
233
  );
202
234
  dlog(
203
235
  `Angular renderer removeChild angularParent=${angularParent} retainedParent=${retainedParent} child=${describeHost(oldChild)} viaProjection=${wasProjected}`,
@@ -228,7 +260,8 @@ export class SymbioteRenderer implements Renderer2 {
228
260
  }
229
261
 
230
262
  nextSibling(node: IHostNode): IHostNode | null {
231
- const siblings = node.parent !== undefined ? node.parent.children : this.surface.children;
263
+ const siblings =
264
+ node.parent !== undefined ? node.parent.children : this.surface.children;
232
265
  const index = siblings.indexOf(node);
233
266
  return index >= 0 ? (siblings[index + 1] ?? null) : null;
234
267
  }
@@ -243,12 +276,16 @@ export class SymbioteRenderer implements Renderer2 {
243
276
 
244
277
  setAttribute(el: IHostElement, name: string, value: string): void {
245
278
  if (isSurface(el)) return;
279
+ countAngular('rendererWrites');
280
+ noteAngularWrite(name);
246
281
  routeProp(el, name, value);
247
282
  this.surface.requestCommit();
248
283
  }
249
284
 
250
285
  removeAttribute(el: IHostElement, name: string): void {
251
286
  if (isSurface(el)) return;
287
+ countAngular('rendererWrites');
288
+ noteAngularWrite(name);
252
289
  routeProp(el, name, undefined);
253
290
  this.surface.requestCommit();
254
291
  }
@@ -264,6 +301,8 @@ export class SymbioteRenderer implements Renderer2 {
264
301
 
265
302
  addClass(el: IHostElement, name: string): void {
266
303
  if (isSurface(el)) return;
304
+ countAngular('rendererWrites');
305
+ noteAngularWrite('class');
267
306
  const tokens = this.classTokens.get(el) ?? new Set<string>();
268
307
  tokens.add(name);
269
308
  this.classTokens.set(el, tokens);
@@ -275,6 +314,8 @@ export class SymbioteRenderer implements Renderer2 {
275
314
  if (isSurface(el)) return;
276
315
  const tokens = this.classTokens.get(el);
277
316
  if (tokens === undefined) return;
317
+ countAngular('rendererWrites');
318
+ noteAngularWrite('class');
278
319
  tokens.delete(name);
279
320
  routeProp(el, 'class', tokens.size > 0 ? [...tokens].join(' ') : undefined);
280
321
  this.surface.requestCommit();
@@ -287,6 +328,8 @@ export class SymbioteRenderer implements Renderer2 {
287
328
  // merge writes, and spreading an array as a record would silently produce numeric-index keys.
288
329
  setStyle(el: IHostElement, style: string, value: unknown): void {
289
330
  if (isSurface(el)) return;
331
+ countAngular('rendererWrites');
332
+ noteAngularWrite(`style.${style}`);
290
333
  const current = getExplicitStyle(el);
291
334
  const base = isRecord(current) ? current : {};
292
335
  routeProp(el, 'style', { ...base, [style]: value });
@@ -297,6 +340,8 @@ export class SymbioteRenderer implements Renderer2 {
297
340
  if (isSurface(el)) return;
298
341
  const current = getExplicitStyle(el);
299
342
  if (!isRecord(current)) return;
343
+ countAngular('rendererWrites');
344
+ noteAngularWrite(`style.${style}`);
300
345
  const { [style]: _removed, ...rest } = current;
301
346
  routeProp(el, 'style', rest);
302
347
  this.surface.requestCommit();
@@ -306,11 +351,15 @@ export class SymbioteRenderer implements Renderer2 {
306
351
  // ViewConfig (identical to React/Vue), so the whole flat-bag prop layer is shared.
307
352
  setProperty(el: IHostElement, name: string, value: unknown): void {
308
353
  if (isSurface(el)) return;
354
+ countAngular('rendererWrites');
355
+ noteAngularWrite(name);
309
356
  routeProp(el, name, value);
310
357
  this.surface.requestCommit();
311
358
  }
312
359
 
313
360
  setValue(node: IHostNode, value: string): void {
361
+ countAngular('rendererWrites');
362
+ noteAngularWrite('#text');
314
363
  // A useful permanent seam: text mutations are low-frequency and the one place a stale
315
364
  // `{{binding}}` (a change-detection gap) shows up as "the setValue never fired".
316
365
  dlog(`Angular renderer setValue "${value}" on ${describeHost(node)}`);
@@ -341,7 +390,20 @@ export class SymbioteRendererFactory implements RendererFactory2 {
341
390
 
342
391
  constructor(private readonly surface: SymbioteSurface) {}
343
392
 
344
- createRenderer(_hostElement: unknown, _type: RendererType2 | null): Renderer2 {
393
+ createRenderer(
394
+ _hostElement: unknown,
395
+ _type: RendererType2 | null,
396
+ ): Renderer2 {
345
397
  return (this.renderer ??= new SymbioteRenderer(this.surface));
346
398
  }
399
+
400
+ // Not commit coalescing (requestCommit owns that) — this is the one moment where a ScrollView's
401
+ // two input channels are known to be consistent: Angular has finished writing every `@Input` AND
402
+ // the renderer has finished every projected insert/remove for this pass, while the surface's
403
+ // commit is still only queued. Sticky projection reconciles are batched to here for both reasons;
404
+ // see flushScrollViewProjections.
405
+ end(): void {
406
+ countAngular('cdPasses');
407
+ flushScrollViewProjections();
408
+ }
347
409
  }
@@ -11,7 +11,9 @@ import { Appearance, type IColorSchemeName } from '@symbiote-native/engine';
11
11
  */
12
12
  @Injectable({ providedIn: 'root' })
13
13
  export class ColorSchemeService {
14
- readonly colorScheme = signal<IColorSchemeName | null>(Appearance.getColorScheme());
14
+ readonly colorScheme = signal<IColorSchemeName | null>(
15
+ Appearance.getColorScheme(),
16
+ );
15
17
 
16
18
  constructor() {
17
19
  const destroyRef = inject(DestroyRef);
@@ -1,5 +1,9 @@
1
1
  import { DestroyRef, inject, Injectable, signal } from '@angular/core';
2
- import { Dimensions, type IDimensionsSet, type IDisplayMetrics } from '@symbiote-native/engine';
2
+ import {
3
+ Dimensions,
4
+ type IDimensionsSet,
5
+ type IDisplayMetrics,
6
+ } from '@symbiote-native/engine';
3
7
 
4
8
  /**
5
9
  * Angular twin of React's `useWindowDimensions` and Vue's `useWindowDimensions` composable. The
@@ -28,9 +32,12 @@ export class WindowDimensionsService {
28
32
  }
29
33
  };
30
34
 
31
- const subscription = Dimensions.addEventListener('change', (set: IDimensionsSet) => {
32
- handleChange(set.window);
33
- });
35
+ const subscription = Dimensions.addEventListener(
36
+ 'change',
37
+ (set: IDimensionsSet) => {
38
+ handleChange(set.window);
39
+ },
40
+ );
34
41
  // Re-check once after subscribing to close the gap between construction and the listener.
35
42
  handleChange(Dimensions.get('window'));
36
43