@symbiote-native/angular 2.0.1 → 3.0.1

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 (96) hide show
  1. package/README.md +5 -4
  2. package/babel-register-composed.cjs +0 -8
  3. package/build/angular/callback-host.d.ts +25 -0
  4. package/build/angular/callback-host.js +211 -0
  5. package/build/angular/callback-host.js.map +1 -0
  6. package/build/angular/change-detection-flush.d.ts +14 -2
  7. package/build/angular/change-detection-flush.js +54 -6
  8. package/build/angular/change-detection-flush.js.map +1 -1
  9. package/build/angular/components/flat-list/index.js +39 -86
  10. package/build/angular/components/flat-list/index.js.map +1 -1
  11. package/build/angular/components/image-shared.js +49 -110
  12. package/build/angular/components/image-shared.js.map +1 -1
  13. package/build/angular/components/keyboard-avoiding-view/index.js +33 -72
  14. package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
  15. package/build/angular/components/modal/index.js +58 -103
  16. package/build/angular/components/modal/index.js.map +1 -1
  17. package/build/angular/components/pressable-props.d.ts +2 -0
  18. package/build/angular/components/section-list/index.js +20 -103
  19. package/build/angular/components/section-list/index.js.map +1 -1
  20. package/build/angular/components/touchable-props.d.ts +1 -0
  21. package/build/angular/components/virtualized-list/directives.js +32 -22
  22. package/build/angular/components/virtualized-list/directives.js.map +1 -1
  23. package/build/angular/components/virtualized-list/index.d.ts +7 -1
  24. package/build/angular/components/virtualized-list/index.js +350 -359
  25. package/build/angular/components/virtualized-list/index.js.map +1 -1
  26. package/build/angular/components/virtualized-section-list/directives.js +20 -12
  27. package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
  28. package/build/angular/components/virtualized-section-list/index.js +36 -142
  29. package/build/angular/components/virtualized-section-list/index.js.map +1 -1
  30. package/build/angular/components.d.ts +1 -1
  31. package/build/angular/components.js +1 -1
  32. package/build/angular/components.js.map +1 -1
  33. package/build/angular/create-portal/index.js +9 -8
  34. package/build/angular/create-portal/index.js.map +1 -1
  35. package/build/angular/create-tunnel/index.js +13 -12
  36. package/build/angular/create-tunnel/index.js.map +1 -1
  37. package/build/angular/descriptor-to-angular/index.js +6 -6
  38. package/build/angular/descriptor-to-angular/index.js.map +1 -1
  39. package/build/angular/diagnostics.d.ts +13 -0
  40. package/build/angular/diagnostics.js +18 -0
  41. package/build/angular/diagnostics.js.map +1 -1
  42. package/build/angular/element-props.d.ts +1 -3
  43. package/build/angular/elements.d.ts +46 -32
  44. package/build/angular/elements.js +217 -434
  45. package/build/angular/elements.js.map +1 -1
  46. package/build/angular/index.d.ts +3 -1
  47. package/build/angular/index.js +44 -13
  48. package/build/angular/index.js.map +1 -1
  49. package/build/angular/modules/animated/animated-leaf-binder.js +0 -2
  50. package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
  51. package/build/angular/modules/animated/create-animated-component.js +69 -74
  52. package/build/angular/modules/animated/create-animated-component.js.map +1 -1
  53. package/build/angular/modules/animated/index.d.ts +30 -19
  54. package/build/angular/modules/status-bar/index.js +2 -8
  55. package/build/angular/modules/status-bar/index.js.map +1 -1
  56. package/build/angular/primitives/index.d.ts +14 -28
  57. package/build/angular/primitives/index.js +44 -107
  58. package/build/angular/primitives/index.js.map +1 -1
  59. package/build/angular/primitives/shared.d.ts +1 -0
  60. package/build/angular/primitives/shared.js +47 -32
  61. package/build/angular/primitives/shared.js.map +1 -1
  62. package/build/angular/render/index.js +40 -3
  63. package/build/angular/render/index.js.map +1 -1
  64. package/build/angular/renderer/index.d.ts +80 -0
  65. package/build/angular/renderer/index.js +324 -114
  66. package/build/angular/renderer/index.js.map +1 -1
  67. package/build/angular/runtime-matching.d.ts +9 -0
  68. package/build/angular/runtime-matching.js +82 -0
  69. package/build/angular/runtime-matching.js.map +1 -0
  70. package/build/angular/services/color-scheme.service.js +4 -4
  71. package/build/angular/services/color-scheme.service.js.map +1 -1
  72. package/build/angular/services/window-dimensions.service.js +4 -4
  73. package/build/angular/services/window-dimensions.service.js.map +1 -1
  74. package/build/angular/style-host.d.ts +11 -0
  75. package/build/angular/style-host.js +86 -0
  76. package/build/angular/style-host.js.map +1 -0
  77. package/package.json +6 -6
  78. package/src/callback-host.ts +229 -0
  79. package/src/change-detection-flush.ts +62 -9
  80. package/src/components/flat-list/index.ts +2 -1
  81. package/src/components/keyboard-avoiding-view/index.ts +8 -0
  82. package/src/components/pressable-props.ts +8 -0
  83. package/src/components/touchable-props.ts +4 -0
  84. package/src/components/virtualized-list/index.ts +98 -45
  85. package/src/components.ts +0 -2
  86. package/src/diagnostics.ts +18 -0
  87. package/src/element-props.ts +1 -5
  88. package/src/elements.ts +159 -38
  89. package/src/index.ts +47 -14
  90. package/src/primitives/index.ts +14 -70
  91. package/src/primitives/shared.ts +21 -6
  92. package/src/render/index.ts +56 -2
  93. package/src/renderer/index.ts +378 -104
  94. package/src/runtime-matching.ts +80 -0
  95. package/src/style-host.ts +94 -0
  96. package/tsconfig.angular.base.json +11 -0
package/README.md CHANGED
@@ -26,9 +26,9 @@ npm install @symbiote-native/angular react-native @angular/core
26
26
  ```
27
27
 
28
28
  `react-native` and `@angular/core` (**>=20**, for stable zoneless change detection) stay your app's
29
- own top-level dependencies. There's no `create-symbiote` scaffolder yet, so the AOT pipeline
30
- (`ngc --watch` alongside Metro — see [Run it](#run-it)) and the Metro config come from
31
- [`examples/angular`](../../examples/angular) rather than a generator.
29
+ own top-level dependencies. `npx @symbiote-native/cli new` wires the AOT pipeline (`ngc --watch`
30
+ alongside Metro — see [Run it](#run-it)) and the Metro config for a new app; wiring them into an
31
+ existing one still follows [`examples/angular`](../../examples/angular) rather than a generator.
32
32
 
33
33
  ---
34
34
 
@@ -89,7 +89,8 @@ reference canaries, standalone components, zoneless change detection.
89
89
  ## Parity — and the one gap
90
90
 
91
91
  Angular reaches the same 21+ primitives, runtime modules, `Animated` on both drivers, gestures,
92
- accessibility, and the `VirtualizedList` family as React and Vue. That parity is **structural, not
92
+ accessibility, and the `VirtualizedList` family as React and Vue, verified on-device on iOS and
93
+ Android. That parity is **structural, not
93
94
  hand-copied**: the component logic (state machines + render functions) is written **once** in
94
95
  `@symbiote-native/components`, and Angular supplies only its lifecycle (`Renderer2` + zoneless change
95
96
  detection + the descriptor→`createElement` bridge).
@@ -37,15 +37,7 @@ const PRIMITIVE_SELECTORS = new Set([
37
37
  'horizontal-scroll-content',
38
38
  'text-input',
39
39
  'text-input-multiline',
40
- // The COMPONENT path's pair. Only a lowering transform may emit the plain tags above — those
41
- // carry the engine's TextInput machine, and this adapter's own component runs that state in
42
- // its template. See `core/components/src/component-names/shared.ts`.
43
- 'text-input-managed',
44
- 'text-input-multiline-managed',
45
40
  'switch',
46
- // The component path's spelling — same native view, a tag the engine's Switch behavior does
47
- // not carry. See `core/components/src/component-names/shared.ts`.
48
- 'switch-managed',
49
41
  // The centering RCTView RN wraps the spinner in — the tag an app writes — plus the native
50
42
  // spinner the ActivityIndicator behavior builds under it.
51
43
  'activity-indicator',
@@ -0,0 +1,25 @@
1
+ import { type OnChanges, type OnDestroy, type SimpleChanges } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * THE SAME NAMES AS A LITERAL, and the duplication is forced rather than chosen.
5
+ *
6
+ * ngtsc STATICALLY EVALUATES a decorator's metadata, and a selector built with `.map().join()` is not
7
+ * something it can evaluate — it reports the class as `Component imports must be standalone
8
+ * components, directives, pipes, or must be NgModules`, which reads as a missing `standalone: true`
9
+ * and is not that. Thirty-seven of this adapter's compile-time guards went red on it at once.
10
+ *
11
+ * So the selector is written out, and `callback-host-selector.test.ts` holds the two in step.
12
+ */
13
+ export declare const CALLBACK_ATTRIBUTE_SELECTOR: string;
14
+ /** Exported for the guard alone — the decorator below must spell its own list for ngtsc. */
15
+ export declare const CALLBACK_INPUT_NAMES: readonly string[];
16
+ export declare class SymbioteCallbackHost implements OnChanges, OnDestroy {
17
+ private readonly renderer;
18
+ private readonly host;
19
+ private readonly detector;
20
+ constructor();
21
+ ngOnChanges(changes: SimpleChanges): void;
22
+ ngOnDestroy(): void;
23
+ static ɵfac: i0.ɵɵFactoryDeclaration<SymbioteCallbackHost, never>;
24
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SymbioteCallbackHost, "[onAccessibilityAction],[onAccessibilityEscape],[onAccessibilityTap],[onBlur],[onContentSizeChange],[onEndEditing],[onError],[onFocus],[onHoverIn],[onHoverOut],[onKeyPress],[onLayout],[onLoad],[onLoadEnd],[onLoadStart],[onLongPress],[onMagicTap],[onMomentumScrollBegin],[onMomentumScrollEnd],[onMoveShouldSetResponder],[onMoveShouldSetResponderCapture],[onPartialLoad],[onPress],[onPressIn],[onPressMove],[onPressOut],[onProgress],[onRefresh],[onResponderEnd],[onResponderGrant],[onResponderMove],[onResponderReject],[onResponderRelease],[onResponderStart],[onResponderTerminate],[onResponderTerminationRequest],[onScrollBeginDrag],[onScrollEndDrag],[onScrollToTop],[onSelectionChange],[onStartShouldSetResponder],[onStartShouldSetResponderCapture],[onSubmitEditing],[onValueChange]", never, { "onAccessibilityAction": { "alias": "onAccessibilityAction"; "required": false; }; "onAccessibilityEscape": { "alias": "onAccessibilityEscape"; "required": false; }; "onAccessibilityTap": { "alias": "onAccessibilityTap"; "required": false; }; "onBlur": { "alias": "onBlur"; "required": false; }; "onContentSizeChange": { "alias": "onContentSizeChange"; "required": false; }; "onEndEditing": { "alias": "onEndEditing"; "required": false; }; "onError": { "alias": "onError"; "required": false; }; "onFocus": { "alias": "onFocus"; "required": false; }; "onHoverIn": { "alias": "onHoverIn"; "required": false; }; "onHoverOut": { "alias": "onHoverOut"; "required": false; }; "onKeyPress": { "alias": "onKeyPress"; "required": false; }; "onLayout": { "alias": "onLayout"; "required": false; }; "onLoad": { "alias": "onLoad"; "required": false; }; "onLoadEnd": { "alias": "onLoadEnd"; "required": false; }; "onLoadStart": { "alias": "onLoadStart"; "required": false; }; "onLongPress": { "alias": "onLongPress"; "required": false; }; "onMagicTap": { "alias": "onMagicTap"; "required": false; }; "onMomentumScrollBegin": { "alias": "onMomentumScrollBegin"; "required": false; }; "onMomentumScrollEnd": { "alias": "onMomentumScrollEnd"; "required": false; }; "onMoveShouldSetResponder": { "alias": "onMoveShouldSetResponder"; "required": false; }; "onMoveShouldSetResponderCapture": { "alias": "onMoveShouldSetResponderCapture"; "required": false; }; "onPartialLoad": { "alias": "onPartialLoad"; "required": false; }; "onPress": { "alias": "onPress"; "required": false; }; "onPressIn": { "alias": "onPressIn"; "required": false; }; "onPressMove": { "alias": "onPressMove"; "required": false; }; "onPressOut": { "alias": "onPressOut"; "required": false; }; "onProgress": { "alias": "onProgress"; "required": false; }; "onRefresh": { "alias": "onRefresh"; "required": false; }; "onResponderEnd": { "alias": "onResponderEnd"; "required": false; }; "onResponderGrant": { "alias": "onResponderGrant"; "required": false; }; "onResponderMove": { "alias": "onResponderMove"; "required": false; }; "onResponderReject": { "alias": "onResponderReject"; "required": false; }; "onResponderRelease": { "alias": "onResponderRelease"; "required": false; }; "onResponderStart": { "alias": "onResponderStart"; "required": false; }; "onResponderTerminate": { "alias": "onResponderTerminate"; "required": false; }; "onResponderTerminationRequest": { "alias": "onResponderTerminationRequest"; "required": false; }; "onScrollBeginDrag": { "alias": "onScrollBeginDrag"; "required": false; }; "onScrollEndDrag": { "alias": "onScrollEndDrag"; "required": false; }; "onScrollToTop": { "alias": "onScrollToTop"; "required": false; }; "onSelectionChange": { "alias": "onSelectionChange"; "required": false; }; "onStartShouldSetResponder": { "alias": "onStartShouldSetResponder"; "required": false; }; "onStartShouldSetResponderCapture": { "alias": "onStartShouldSetResponderCapture"; "required": false; }; "onSubmitEditing": { "alias": "onSubmitEditing"; "required": false; }; "onValueChange": { "alias": "onValueChange"; "required": false; }; }, {}, never, never, true, never>;
25
+ }
@@ -0,0 +1,211 @@
1
+ // The directive that owns a `ChangeDetectorRef` for the elements that actually need one.
2
+ //
3
+ // An `on*` PROP is forwarded to the engine node, and the ENGINE calls it — Angular is told nothing,
4
+ // so a plain field mutation inside the app's handler dirties no view. `createCallbackWrapper` fixes
5
+ // that with `markForCheck`, and until 2026-09-18 the ref it marked came from `SymbioteElement`
6
+ // injecting one on EVERY element, because any element MAY carry a callback.
7
+ //
8
+ // Almost none does. Priced on JavaScriptCore, an injection is ~1.0-2.4 us per element — 10-24 ms
9
+ // over the ten thousand of a thousand-row screen, all of it to build `ViewRef`s nothing reads
10
+ // (`core/engine/cpp/tests/js/angular-directive-cost.itest.ts`, the `ChangeDetectorRef` row).
11
+ //
12
+ // MATCHING IS THE CHEAPER QUESTION THAN INJECTING, and that had to be measured rather than assumed,
13
+ // because the selector below is dozens of alternatives and Angular tries a component's whole
14
+ // directive list against every element it creates. The ladder's `cb-probe` arm carries this exact
15
+ // string WITHOUT matching anything — the state of every element on the benchmark row — so the two
16
+ // halves are priced against each other in one process, rotated, with a resolution bar:
17
+ //
18
+ // carrying the selector 4.3 5.9 -2.0 5.7 -1.5 ms inside the bar in four of five
19
+ // the ChangeDetectorRef 9.7 23.5 13.6 31.3 23.8 ms
20
+ // the CALLBACK HOST -3.9 -9.3 -25.5 -25.6 -25.3 ms all five negative, three outside
21
+ //
22
+ // WHAT IS NOT VERIFIED, said plainly: the ladder's arm carries ONE element directive beside this one,
23
+ // where a real screen carries two dozen, and the `ng-elements` bench arm could not confirm the change
24
+ // at app scale — the machine it was measured on swung its untouched CONTROL from 229 to 566 ms, so
25
+ // nothing quoted off it would have carried a verdict either way. The controlled A/B is the evidence
26
+ // here; the bench-arm confirmation is owed.
27
+ //
28
+ // IT HOLDS NO INPUTS, which is what keeps the change off the public surface. The `on*` props stay
29
+ // declared on `SymbioteElement` and its subclasses, so every type and every forward is unchanged;
30
+ // this directive only needs to BE on the element, and a missing name in its selector costs a change
31
+ // detection mark rather than a prop. `callback-host-selector.test.ts` derives the list from the
32
+ // directives' own declared inputs so it cannot go stale quietly.
33
+ //
34
+ // A LEAF, like `change-detection-flush` and `renderer/value-change`: importing `elements.ts` from
35
+ // here would close a cycle, since that file is what puts this directive in `SYMBIOTE_ELEMENTS`.
36
+ import { ChangeDetectorRef, Directive, ElementRef, Renderer2, inject, } from '@angular/core';
37
+ import { registerViewMarker, unregisterViewMarker, } from './change-detection-flush';
38
+ import * as i0 from "@angular/core";
39
+ /**
40
+ * Every `on*` input any element directive declares, except the one that fires per FRAME.
41
+ *
42
+ * `onScroll` is absent deliberately and not by oversight: with sticky headers RN pins
43
+ * `scrollEventThrottle` to 1, and `markForCheck` walks to the root, so wrapping it cost the canary
44
+ * ~37fps. `isWrappableCallback` excludes it, and the guard test asserts this string does too.
45
+ */
46
+ const CALLBACK_INPUTS = [
47
+ 'onAccessibilityAction',
48
+ 'onAccessibilityEscape',
49
+ 'onAccessibilityTap',
50
+ 'onBlur',
51
+ 'onChangeText',
52
+ 'onContentSizeChange',
53
+ 'onEndEditing',
54
+ 'onError',
55
+ 'onFocus',
56
+ 'onHoverIn',
57
+ 'onHoverOut',
58
+ 'onKeyPress',
59
+ 'onLayout',
60
+ 'onLoad',
61
+ 'onLoadEnd',
62
+ 'onLoadStart',
63
+ 'onLongPress',
64
+ 'onMagicTap',
65
+ 'onMomentumScrollBegin',
66
+ 'onMomentumScrollEnd',
67
+ 'onMoveShouldSetResponder',
68
+ 'onMoveShouldSetResponderCapture',
69
+ 'onPartialLoad',
70
+ 'onPress',
71
+ 'onPressIn',
72
+ 'onPressMove',
73
+ 'onPressOut',
74
+ 'onProgress',
75
+ 'onRefresh',
76
+ 'onResponderEnd',
77
+ 'onResponderGrant',
78
+ 'onResponderMove',
79
+ 'onResponderReject',
80
+ 'onResponderRelease',
81
+ 'onResponderStart',
82
+ 'onResponderTerminate',
83
+ 'onResponderTerminationRequest',
84
+ 'onScrollBeginDrag',
85
+ 'onScrollEndDrag',
86
+ 'onScrollToTop',
87
+ 'onSelectionChange',
88
+ 'onStartShouldSetResponder',
89
+ 'onStartShouldSetResponderCapture',
90
+ 'onSubmitEditing',
91
+ 'onValueChange',
92
+ ];
93
+ /**
94
+ * THE SAME NAMES AS A LITERAL, and the duplication is forced rather than chosen.
95
+ *
96
+ * ngtsc STATICALLY EVALUATES a decorator's metadata, and a selector built with `.map().join()` is not
97
+ * something it can evaluate — it reports the class as `Component imports must be standalone
98
+ * components, directives, pipes, or must be NgModules`, which reads as a missing `standalone: true`
99
+ * and is not that. Thirty-seven of this adapter's compile-time guards went red on it at once.
100
+ *
101
+ * So the selector is written out, and `callback-host-selector.test.ts` holds the two in step.
102
+ */
103
+ export const CALLBACK_ATTRIBUTE_SELECTOR = '[onAccessibilityAction],[onAccessibilityEscape],[onAccessibilityTap],[onBlur],' +
104
+ '[onChangeText],[onContentSizeChange],[onEndEditing],[onError],[onFocus],[onHoverIn],[onHoverOut],' +
105
+ '[onKeyPress],[onLayout],[onLoad],[onLoadEnd],[onLoadStart],[onLongPress],[onMagicTap],' +
106
+ '[onMomentumScrollBegin],[onMomentumScrollEnd],[onMoveShouldSetResponder],' +
107
+ '[onMoveShouldSetResponderCapture],[onPartialLoad],[onPress],[onPressIn],[onPressMove],' +
108
+ '[onPressOut],[onProgress],[onRefresh],[onResponderEnd],[onResponderGrant],[onResponderMove],' +
109
+ '[onResponderReject],[onResponderRelease],[onResponderStart],[onResponderTerminate],' +
110
+ '[onResponderTerminationRequest],[onScrollBeginDrag],[onScrollEndDrag],[onScrollToTop],' +
111
+ '[onSelectionChange],[onStartShouldSetResponder],[onStartShouldSetResponderCapture],' +
112
+ '[onSubmitEditing],[onValueChange]';
113
+ /** Exported for the guard alone — the decorator below must spell its own list for ngtsc. */
114
+ export const CALLBACK_INPUT_NAMES = CALLBACK_INPUTS;
115
+ export class SymbioteCallbackHost {
116
+ constructor() {
117
+ this.renderer = inject(Renderer2);
118
+ this.host = inject(ElementRef);
119
+ this.detector = inject(ChangeDetectorRef);
120
+ const node = this.host.nativeElement;
121
+ if (typeof node === 'object' && node !== null)
122
+ registerViewMarker(node, this.detector);
123
+ }
124
+ ngOnChanges(changes) {
125
+ // The renderer wraps the value on its way through, so this forwards it raw — same shape as the
126
+ // tag directives' own forward, and the only place a withheld tag's callback can now come from.
127
+ for (const name of Object.keys(changes))
128
+ this.renderer.setProperty(this.host.nativeElement, name, changes[name]?.currentValue);
129
+ }
130
+ ngOnDestroy() {
131
+ const node = this.host.nativeElement;
132
+ if (typeof node === 'object' && node !== null)
133
+ unregisterViewMarker(node);
134
+ }
135
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SymbioteCallbackHost, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
136
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: SymbioteCallbackHost, isStandalone: true, selector: "[onAccessibilityAction],[onAccessibilityEscape],[onAccessibilityTap],[onBlur],[onContentSizeChange],[onEndEditing],[onError],[onFocus],[onHoverIn],[onHoverOut],[onKeyPress],[onLayout],[onLoad],[onLoadEnd],[onLoadStart],[onLongPress],[onMagicTap],[onMomentumScrollBegin],[onMomentumScrollEnd],[onMoveShouldSetResponder],[onMoveShouldSetResponderCapture],[onPartialLoad],[onPress],[onPressIn],[onPressMove],[onPressOut],[onProgress],[onRefresh],[onResponderEnd],[onResponderGrant],[onResponderMove],[onResponderReject],[onResponderRelease],[onResponderStart],[onResponderTerminate],[onResponderTerminationRequest],[onScrollBeginDrag],[onScrollEndDrag],[onScrollToTop],[onSelectionChange],[onStartShouldSetResponder],[onStartShouldSetResponderCapture],[onSubmitEditing],[onValueChange]", inputs: { onAccessibilityAction: "onAccessibilityAction", onAccessibilityEscape: "onAccessibilityEscape", onAccessibilityTap: "onAccessibilityTap", onBlur: "onBlur", onContentSizeChange: "onContentSizeChange", onEndEditing: "onEndEditing", onError: "onError", onFocus: "onFocus", onHoverIn: "onHoverIn", onHoverOut: "onHoverOut", onKeyPress: "onKeyPress", onLayout: "onLayout", onLoad: "onLoad", onLoadEnd: "onLoadEnd", onLoadStart: "onLoadStart", onLongPress: "onLongPress", onMagicTap: "onMagicTap", onMomentumScrollBegin: "onMomentumScrollBegin", onMomentumScrollEnd: "onMomentumScrollEnd", onMoveShouldSetResponder: "onMoveShouldSetResponder", onMoveShouldSetResponderCapture: "onMoveShouldSetResponderCapture", onPartialLoad: "onPartialLoad", onPress: "onPress", onPressIn: "onPressIn", onPressMove: "onPressMove", onPressOut: "onPressOut", onProgress: "onProgress", onRefresh: "onRefresh", onResponderEnd: "onResponderEnd", onResponderGrant: "onResponderGrant", onResponderMove: "onResponderMove", onResponderReject: "onResponderReject", onResponderRelease: "onResponderRelease", onResponderStart: "onResponderStart", onResponderTerminate: "onResponderTerminate", onResponderTerminationRequest: "onResponderTerminationRequest", onScrollBeginDrag: "onScrollBeginDrag", onScrollEndDrag: "onScrollEndDrag", onScrollToTop: "onScrollToTop", onSelectionChange: "onSelectionChange", onStartShouldSetResponder: "onStartShouldSetResponder", onStartShouldSetResponderCapture: "onStartShouldSetResponderCapture", onSubmitEditing: "onSubmitEditing", onValueChange: "onValueChange" }, usesOnChanges: true, ngImport: i0 }); }
137
+ }
138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SymbioteCallbackHost, decorators: [{
139
+ type: Directive,
140
+ args: [{
141
+ selector: '[onAccessibilityAction],[onAccessibilityEscape],[onAccessibilityTap],[onBlur],' +
142
+ '[onContentSizeChange],[onEndEditing],[onError],[onFocus],[onHoverIn],[onHoverOut],' +
143
+ '[onKeyPress],[onLayout],[onLoad],[onLoadEnd],[onLoadStart],[onLongPress],[onMagicTap],' +
144
+ '[onMomentumScrollBegin],[onMomentumScrollEnd],[onMoveShouldSetResponder],' +
145
+ '[onMoveShouldSetResponderCapture],[onPartialLoad],[onPress],[onPressIn],[onPressMove],' +
146
+ '[onPressOut],[onProgress],[onRefresh],[onResponderEnd],[onResponderGrant],[onResponderMove],' +
147
+ '[onResponderReject],[onResponderRelease],[onResponderStart],[onResponderTerminate],' +
148
+ '[onResponderTerminationRequest],[onScrollBeginDrag],[onScrollEndDrag],[onScrollToTop],' +
149
+ '[onSelectionChange],[onStartShouldSetResponder],[onStartShouldSetResponderCapture],' +
150
+ '[onSubmitEditing],[onValueChange]',
151
+ // DECLARED, and this is not an optimisation — it is what makes withholding the tag directives
152
+ // possible at all. `ɵɵproperty` on an element no directive claimed ends in `setDomProperty`, which
153
+ // runs `validateAgainstEventProperties` and THROWS NG0306 on any name beginning with `on`
154
+ // (upstream `sanitization/sanitization.ts:267`). So an `on*` prop is not merely unwrapped without a
155
+ // claiming input — it is a hard runtime error, and the first attempt at this change hit it on the
156
+ // first test that binds one.
157
+ //
158
+ // The TYPES still come from `SymbioteElement` and its subclasses, which ngtsc goes on matching from
159
+ // the decorator's selector however empty the runtime array is. Angular requires an expression to
160
+ // satisfy EVERY directive that claims the input, so a permissive declaration here cannot loosen the
161
+ // exact one there.
162
+ inputs: [
163
+ 'onAccessibilityAction',
164
+ 'onAccessibilityEscape',
165
+ 'onAccessibilityTap',
166
+ 'onBlur',
167
+ 'onContentSizeChange',
168
+ 'onEndEditing',
169
+ 'onError',
170
+ 'onFocus',
171
+ 'onHoverIn',
172
+ 'onHoverOut',
173
+ 'onKeyPress',
174
+ 'onLayout',
175
+ 'onLoad',
176
+ 'onLoadEnd',
177
+ 'onLoadStart',
178
+ 'onLongPress',
179
+ 'onMagicTap',
180
+ 'onMomentumScrollBegin',
181
+ 'onMomentumScrollEnd',
182
+ 'onMoveShouldSetResponder',
183
+ 'onMoveShouldSetResponderCapture',
184
+ 'onPartialLoad',
185
+ 'onPress',
186
+ 'onPressIn',
187
+ 'onPressMove',
188
+ 'onPressOut',
189
+ 'onProgress',
190
+ 'onRefresh',
191
+ 'onResponderEnd',
192
+ 'onResponderGrant',
193
+ 'onResponderMove',
194
+ 'onResponderReject',
195
+ 'onResponderRelease',
196
+ 'onResponderStart',
197
+ 'onResponderTerminate',
198
+ 'onResponderTerminationRequest',
199
+ 'onScrollBeginDrag',
200
+ 'onScrollEndDrag',
201
+ 'onScrollToTop',
202
+ 'onSelectionChange',
203
+ 'onStartShouldSetResponder',
204
+ 'onStartShouldSetResponderCapture',
205
+ 'onSubmitEditing',
206
+ 'onValueChange',
207
+ ],
208
+ standalone: true,
209
+ }]
210
+ }], ctorParameters: () => [] });
211
+ //# sourceMappingURL=callback-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-host.js","sourceRoot":"","sources":["../../src/callback-host.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,16 @@
1
1
  interface IViewMarker {
2
2
  markForCheck(): void;
3
3
  }
4
- type ICallbackWrapper = (key: string, value: unknown) => unknown;
4
+ export type ICallbackWrapper = (key: string, value: unknown) => unknown;
5
+ /**
6
+ * Is this prop write one the wrapper would touch at all?
7
+ *
8
+ * Exported so a CALLER can ask before building a wrapper it may never need — an element directive is
9
+ * constructed per TAG, and a screen's tags overwhelmingly carry no `on*` function prop. Keeping the
10
+ * question here rather than copying the two tests at the call site is what stops the predicate and
11
+ * the wrapper drifting apart.
12
+ */
13
+ export declare function isWrappableCallback(key: string, value: unknown): boolean;
5
14
  /**
6
15
  * Makes an app callback handed to the engine as a PROP re-enter Angular's update loop.
7
16
  *
@@ -24,7 +33,10 @@ type ICallbackWrapper = (key: string, value: unknown) => unknown;
24
33
  * ONE wrapper per original handler, for the caller's lifetime. A fresh closure per push would make
25
34
  * the engine store a new listener every time and would defeat every downstream identity check.
26
35
  */
27
- export declare function createCallbackWrapper(view: IViewMarker, node: unknown): ICallbackWrapper;
36
+ export declare function createCallbackWrapper(node: unknown): ICallbackWrapper;
37
+ export declare function registerViewMarker(node: object, marker: IViewMarker): void;
38
+ export declare function unregisterViewMarker(node: object): void;
39
+ export declare function markViewFor(node: unknown): void;
28
40
  export declare function registerViewFlush(node: object, flush: () => void): void;
29
41
  export declare function unregisterViewFlush(node: object): void;
30
42
  export declare function flushViewFor(node: unknown): void;
@@ -51,6 +51,19 @@ const ON_PREFIX = /^on[A-Z]/;
51
51
  // typically an Animated.event touching no Angular state. Drag and momentum begin/end stay wrapped;
52
52
  // once per gesture is not a hot path.
53
53
  const PER_FRAME_CALLBACK = 'onScroll';
54
+ /**
55
+ * Is this prop write one the wrapper would touch at all?
56
+ *
57
+ * Exported so a CALLER can ask before building a wrapper it may never need — an element directive is
58
+ * constructed per TAG, and a screen's tags overwhelmingly carry no `on*` function prop. Keeping the
59
+ * question here rather than copying the two tests at the call site is what stops the predicate and
60
+ * the wrapper drifting apart.
61
+ */
62
+ export function isWrappableCallback(key, value) {
63
+ return (ON_PREFIX.test(key) &&
64
+ typeof value === 'function' &&
65
+ key !== PER_FRAME_CALLBACK);
66
+ }
54
67
  /**
55
68
  * Makes an app callback handed to the engine as a PROP re-enter Angular's update loop.
56
69
  *
@@ -73,13 +86,18 @@ const PER_FRAME_CALLBACK = 'onScroll';
73
86
  * ONE wrapper per original handler, for the caller's lifetime. A fresh closure per push would make
74
87
  * the engine store a new listener every time and would defeat every downstream identity check.
75
88
  */
76
- export function createCallbackWrapper(view, node) {
77
- const wrappers = new WeakMap();
89
+ export function createCallbackWrapper(node) {
90
+ // Allocated on the FIRST wrap, not at construction. The map is per-CALLER, so an eager one is one
91
+ // `WeakMap` per directive instance — 7 000 on a 1 000-row create of the benchmark row, none of
92
+ // which is ever read, because that row carries no `on*` prop.
93
+ let wrappers;
78
94
  return (key, value) => {
79
- if (!ON_PREFIX.test(key) || typeof value !== 'function')
80
- return value;
81
- if (key === PER_FRAME_CALLBACK)
95
+ // The second test is redundant at RUNTIME and load-bearing for the TYPE: `isWrappableCallback`
96
+ // answers a boolean, which narrows nothing, and the alternative is a cast. Keeping the `typeof`
97
+ // here is what lets `value` be a `Function` below without one.
98
+ if (!isWrappableCallback(key, value) || typeof value !== 'function')
82
99
  return value;
100
+ wrappers ??= new WeakMap();
83
101
  const cached = wrappers.get(value);
84
102
  if (cached !== undefined)
85
103
  return cached;
@@ -88,7 +106,7 @@ export function createCallbackWrapper(view, node) {
88
106
  // `undefined` as the receiver preserves the previous unbound call.
89
107
  const wrapper = (...args) => {
90
108
  const result = Reflect.apply(value, undefined, args);
91
- view.markForCheck();
109
+ markViewFor(node);
92
110
  flushViewFor(node);
93
111
  return result;
94
112
  };
@@ -96,6 +114,36 @@ export function createCallbackWrapper(view, node) {
96
114
  return wrapper;
97
115
  };
98
116
  }
117
+ /**
118
+ * The MARKER half, keyed on the node rather than injected.
119
+ *
120
+ * `markForCheck` needs the view holding the binding, and until 2026-09-18 the only way to have one
121
+ * was for `SymbioteElement` to inject a `ChangeDetectorRef` — on EVERY element, so that its lazy
122
+ * `on*` wrapper could reach one on the few that carry a callback. An injection is ~1-2.4 us per
123
+ * element on JavaScriptCore (`core/engine/cpp/tests/js/angular-directive-cost.itest.ts`), so a
124
+ * thousand-row screen paid for ten thousand `ViewRef`s to serve none.
125
+ *
126
+ * `SymbioteCallbackHost` registers here instead, and it MATCHES on the callback attributes rather
127
+ * than being injected — so an element with no `on*` binding neither instantiates it nor pays for it.
128
+ * Measured as the whole trade, selector included: -3.9 / -9.3 / -25.5 ms over ten thousand elements.
129
+ *
130
+ * Separate from `viewFlushes` deliberately. They come from different directives, on overlapping but
131
+ * different sets of tags, and they are not the same instruction: this one SCHEDULES and reaches every
132
+ * ancestor, that one re-checks ONE view synchronously. Collapsing them into a single entry would make
133
+ * whichever directive registered last decide which of the two an element gets.
134
+ */
135
+ const viewMarkers = new WeakMap();
136
+ export function registerViewMarker(node, marker) {
137
+ viewMarkers.set(node, marker);
138
+ }
139
+ export function unregisterViewMarker(node) {
140
+ viewMarkers.delete(node);
141
+ }
142
+ export function markViewFor(node) {
143
+ if (typeof node !== 'object' || node === null)
144
+ return;
145
+ viewMarkers.get(node)?.markForCheck();
146
+ }
99
147
  export function registerViewFlush(node, flush) {
100
148
  viewFlushes.set(node, flush);
101
149
  }
@@ -1 +1 @@
1
- {"version":3,"file":"change-detection-flush.js","sourceRoot":"","sources":["../../src/change-detection-flush.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,kGAAkG;AAClG,kCAAkC;AAClC,EAAE;AACF,sEAAsE;AACtE,kFAAkF;AAClF,6FAA6F;AAC7F,iGAAiG;AACjG,oGAAoG;AACpG,6DAA6D;AAC7D,EAAE;AACF,+FAA+F;AAC/F,iFAAiF;AACjF,sFAAsF;AACtF,EAAE;AACF,mGAAmG;AACnG,2FAA2F;AAC3F,qGAAqG;AACrG,0DAA0D;AAC1D,EAAE;AACF,gGAAgG;AAChG,4FAA4F;AAC5F,kGAAkG;AAClG,mGAAmG;AACnG,oGAAoG;AACpG,iGAAiG;AACjG,6FAA6F;AAC7F,oGAAoG;AACpG,oGAAoG;AACpG,kFAAkF;AAClF,yFAAyF;AACzF,gGAAgG;AAChG,iEAAiE;AACjE,EAAE;AACF,6FAA6F;AAC7F,oGAAoG;AACpG,4FAA4F;AAC5F,kGAAkG;AAClG,oBAAoB;AACpB,EAAE;AACF,6FAA6F;AAC7F,yEAAyE;AACzE,mCAAmC;AACnC,MAAM,WAAW,GAAG,IAAI,OAAO,EAAsB,CAAC;AAEtD,MAAM,SAAS,GAAG,UAAU,CAAC;AAE7B,2FAA2F;AAC3F,oGAAoG;AACpG,gGAAgG;AAChG,mGAAmG;AACnG,oGAAoG;AACpG,mGAAmG;AACnG,sCAAsC;AACtC,MAAM,kBAAkB,GAAG,UAAU,CAAC;AAQtC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAiB,EACjB,IAAa;IAEb,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA2C,CAAC;IACxE,OAAO,CAAC,GAAW,EAAE,KAAc,EAAW,EAAE;QAC9C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK,UAAU;YAAE,OAAO,KAAK,CAAC;QACtE,IAAI,GAAG,KAAK,kBAAkB;YAAE,OAAO,KAAK,CAAC;QAC7C,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACxC,gGAAgG;QAChG,iGAAiG;QACjG,mEAAmE;QACnE,MAAM,OAAO,GAAG,CAAC,GAAG,IAAe,EAAW,EAAE;YAC9C,MAAM,MAAM,GAAY,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YAC9D,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,CAAC;YACnB,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;QACF,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAiB;IAC/D,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO;IACtD,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"change-detection-flush.js","sourceRoot":"","sources":["../../src/change-detection-flush.ts"],"names":[],"mappings":"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"}
@@ -68,77 +68,42 @@ function asItem(value) {
68
68
  }
69
69
  export { VListEmptyDirective, VListFooterDirective, VListHeaderDirective, VListItemDirective, VListSeparatorDirective, } from '../virtualized-list';
70
70
  export class FlatList {
71
- // The list's edge/viewability/failure events as real Angular events: `(endReached)="…"`, not
72
- // `[onEndReached]="…"` re-emitted straight from the inner VirtualizedList's own @Output()s (see
73
- // the template's `(endReached)="endReached.emit($event)"` style forwarding above).
74
- endReached = new EventEmitter();
75
- startReached = new EventEmitter();
76
- refresh = new EventEmitter();
77
- viewableItemsChanged = new EventEmitter();
78
- scrollToIndexFailed = new EventEmitter();
79
- data;
80
- // RN's FlatList spreads its props onto the VirtualizedList it renders, so `testID` lands on the
81
- // scroll view. Angular has no spread, and this one was simply missing: `<FlatList testID="x">`
82
- // compiled (a static attribute on a component tag is never checked), reached the non-painting
83
- // anchor host, and committed nowhere — so the list was untargetable by a test or an e2e selector
84
- // while looking perfectly healthy on screen. VirtualizedList has always declared it.
85
- testID;
86
- numColumns;
87
- columnWrapperStyle;
88
- keyExtractor;
89
- getItemLayout;
90
- horizontal;
91
- inverted;
92
- extraData;
93
- onEndReachedThreshold;
94
- onStartReachedThreshold;
95
- refreshing;
96
- progressViewOffset;
97
- viewabilityConfig;
98
- viewabilityConfigCallbackPairs;
99
- initialNumToRender;
100
- initialScrollIndex;
101
- maxToRenderPerBatch;
102
- updateCellsBatchingPeriod;
103
- windowSize;
104
- stickyHeaderIndices;
105
- maintainVisibleContentPosition;
106
- onScroll;
107
- onScrollBeginDrag;
108
- onScrollEndDrag;
109
- onMomentumScrollBegin;
110
- onMomentumScrollEnd;
111
- scrollEventThrottle;
112
- keyboardShouldPersistTaps;
113
- keyboardDismissMode;
114
- style;
115
- contentContainerStyle;
116
- // The app's cell + slot templates, captured for the multi-column re-stamp path. In the
117
- // single-column path they are ALSO projected through <ng-content> to the inner list and these
118
- // captures stay unused (the inner list's own @ContentChild resolves them across the projection).
119
- itemDir;
120
- headerDir;
121
- footerDir;
122
- emptyDir;
123
- separatorDir;
124
- // The composed inner list (whichever numColumns branch rendered). Its instance IS the
125
- // IVirtualizedListHandle, so FlatList's handle delegates straight to it.
126
- listRef;
127
- // This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
128
- // onto (see anchorHostStyle's doc comment) — NOT `listRef` above, which targets the real inner
129
- // `<VirtualizedList>` one level down (itself its own separate anchor host).
130
- elementRef = inject(ElementRef);
131
- // --- derived view state (recomputed in ngOnChanges, stable identity across CD) ---
132
- rows = [];
133
- // Angular's [style]="…" binding compiles to the built-in ɵɵstyleMap instruction (NOT a
134
- // regular @Input() property write), which only understands a flat object — an array (RN's
135
- // own `style={[a, b]}` composition idiom) crashes deep inside Angular's styling engine. So
136
- // every style value this component stamps onto a template `[style]=` binding is flattened
137
- // via the engine's own flattenStyle first, here at the source rather than at each call site.
138
- rowStyle = flattenStyle([{ flexDirection: 'row' }]);
139
- rowViewabilityPairs;
140
- columnCellStyle = { flex: 1 };
141
- resolvedStyle = undefined;
71
+ constructor() {
72
+ // The list's edge/viewability/failure events as real Angular events: `(endReached)="…"`, not
73
+ // `[onEndReached]=""` re-emitted straight from the inner VirtualizedList's own @Output()s (see
74
+ // the template's `(endReached)="endReached.emit($event)"` style forwarding above).
75
+ this.endReached = new EventEmitter();
76
+ this.startReached = new EventEmitter();
77
+ this.refresh = new EventEmitter();
78
+ this.viewableItemsChanged = new EventEmitter();
79
+ this.scrollToIndexFailed = new EventEmitter();
80
+ // This component's OWN host the non-painting anchor `class="..."` at the use site resolves
81
+ // onto (see anchorHostStyle's doc comment) NOT `listRef` above, which targets the real inner
82
+ // `<VirtualizedList>` one level down (itself its own separate anchor host).
83
+ this.elementRef = inject(ElementRef);
84
+ // --- derived view state (recomputed in ngOnChanges, stable identity across CD) ---
85
+ this.rows = [];
86
+ // Angular's [style]="…" binding compiles to the built-in ɵɵstyleMap instruction (NOT a
87
+ // regular @Input() property write), which only understands a flat object — an array (RN's
88
+ // own `style={[a, b]}` composition idiom) crashes deep inside Angular's styling engine. So
89
+ // every style value this component stamps onto a template `[style]=` binding is flattened
90
+ // via the engine's own flattenStyle first, here at the source rather than at each call site.
91
+ this.rowStyle = flattenStyle([{ flexDirection: 'row' }]);
92
+ this.columnCellStyle = { flex: 1 };
93
+ this.resolvedStyle = undefined;
94
+ // ---- single-column data adaptation (plain array -> getItem/getItemCount) ----
95
+ this.getFlatItem = (_data, index) => this.data[index];
96
+ this.getFlatCount = (_data) => this.data.length;
97
+ // ---- multi-column data adaptation (the virtualized stream is rows) ----
98
+ this.getRow = (_data, index) => this.rows[index];
99
+ this.getRowCount = (_data) => this.rows.length;
100
+ this.rowKey = (row, _index) => rowKeyExtractor(row, this.keyExtractor);
101
+ // Viewability over rows expands back to per-item tokens, so the caller sees item-level
102
+ // visibility, not row-level (shared expandRowViewability), matching Vue.
103
+ this.rowViewableItemsChanged = (info) => {
104
+ this.viewableItemsChanged.emit(expandRowViewability(info, this.keyExtractor));
105
+ };
106
+ }
142
107
  get columns() {
143
108
  return this.numColumns ?? SINGLE_COLUMN;
144
109
  }
@@ -176,18 +141,6 @@ export class FlatList {
176
141
  this.rowViewabilityPairs = this.buildRowViewabilityPairs();
177
142
  }
178
143
  }
179
- // ---- single-column data adaptation (plain array -> getItem/getItemCount) ----
180
- getFlatItem = (_data, index) => this.data[index];
181
- getFlatCount = (_data) => this.data.length;
182
- // ---- multi-column data adaptation (the virtualized stream is rows) ----
183
- getRow = (_data, index) => this.rows[index];
184
- getRowCount = (_data) => this.rows.length;
185
- rowKey = (row, _index) => rowKeyExtractor(row);
186
- // Viewability over rows expands back to per-item tokens, so the caller sees item-level
187
- // visibility, not row-level (shared expandRowViewability), matching Vue.
188
- rowViewableItemsChanged = (info) => {
189
- this.viewableItemsChanged.emit(expandRowViewability(info, this.keyExtractor));
190
- };
191
144
  buildRowViewabilityPairs() {
192
145
  return this.viewabilityConfigCallbackPairs?.map(pair => ({
193
146
  viewabilityConfig: pair.viewabilityConfig,
@@ -270,8 +223,8 @@ export class FlatList {
270
223
  recordInteraction() {
271
224
  this.listRef?.recordInteraction();
272
225
  }
273
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FlatList, deps: [], target: i0.ɵɵFactoryTarget.Component });
274
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FlatList, isStandalone: true, selector: "FlatList", inputs: { data: "data", testID: "testID", numColumns: "numColumns", columnWrapperStyle: "columnWrapperStyle", keyExtractor: "keyExtractor", getItemLayout: "getItemLayout", horizontal: "horizontal", inverted: "inverted", extraData: "extraData", onEndReachedThreshold: "onEndReachedThreshold", onStartReachedThreshold: "onStartReachedThreshold", refreshing: "refreshing", progressViewOffset: "progressViewOffset", viewabilityConfig: "viewabilityConfig", viewabilityConfigCallbackPairs: "viewabilityConfigCallbackPairs", initialNumToRender: "initialNumToRender", initialScrollIndex: "initialScrollIndex", maxToRenderPerBatch: "maxToRenderPerBatch", updateCellsBatchingPeriod: "updateCellsBatchingPeriod", windowSize: "windowSize", stickyHeaderIndices: "stickyHeaderIndices", maintainVisibleContentPosition: "maintainVisibleContentPosition", onScroll: "onScroll", onScrollBeginDrag: "onScrollBeginDrag", onScrollEndDrag: "onScrollEndDrag", onMomentumScrollBegin: "onMomentumScrollBegin", onMomentumScrollEnd: "onMomentumScrollEnd", scrollEventThrottle: "scrollEventThrottle", keyboardShouldPersistTaps: "keyboardShouldPersistTaps", keyboardDismissMode: "keyboardDismissMode", style: "style", contentContainerStyle: "contentContainerStyle" }, outputs: { endReached: "endReached", startReached: "startReached", refresh: "refresh", viewableItemsChanged: "viewableItemsChanged", scrollToIndexFailed: "scrollToIndexFailed" }, queries: [{ propertyName: "itemDir", first: true, predicate: VListItemDirective, descendants: true }, { propertyName: "headerDir", first: true, predicate: VListHeaderDirective, descendants: true }, { propertyName: "footerDir", first: true, predicate: VListFooterDirective, descendants: true }, { propertyName: "emptyDir", first: true, predicate: VListEmptyDirective, descendants: true }, { propertyName: "separatorDir", first: true, predicate: VListSeparatorDirective, descendants: true }], viewQueries: [{ propertyName: "listRef", first: true, predicate: VirtualizedList, descendants: true }], usesOnChanges: true, hostDirectives: [{ directive: i1.SymbioteStyleInputDirective, inputs: ["style", "style"] }], ngImport: i0, template: `
226
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FlatList, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
227
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FlatList, isStandalone: true, selector: "FlatList", inputs: { data: "data", testID: "testID", numColumns: "numColumns", columnWrapperStyle: "columnWrapperStyle", keyExtractor: "keyExtractor", getItemLayout: "getItemLayout", horizontal: "horizontal", inverted: "inverted", extraData: "extraData", onEndReachedThreshold: "onEndReachedThreshold", onStartReachedThreshold: "onStartReachedThreshold", refreshing: "refreshing", progressViewOffset: "progressViewOffset", viewabilityConfig: "viewabilityConfig", viewabilityConfigCallbackPairs: "viewabilityConfigCallbackPairs", initialNumToRender: "initialNumToRender", initialScrollIndex: "initialScrollIndex", maxToRenderPerBatch: "maxToRenderPerBatch", updateCellsBatchingPeriod: "updateCellsBatchingPeriod", windowSize: "windowSize", stickyHeaderIndices: "stickyHeaderIndices", maintainVisibleContentPosition: "maintainVisibleContentPosition", onScroll: "onScroll", onScrollBeginDrag: "onScrollBeginDrag", onScrollEndDrag: "onScrollEndDrag", onMomentumScrollBegin: "onMomentumScrollBegin", onMomentumScrollEnd: "onMomentumScrollEnd", scrollEventThrottle: "scrollEventThrottle", keyboardShouldPersistTaps: "keyboardShouldPersistTaps", keyboardDismissMode: "keyboardDismissMode", style: "style", contentContainerStyle: "contentContainerStyle" }, outputs: { endReached: "endReached", startReached: "startReached", refresh: "refresh", viewableItemsChanged: "viewableItemsChanged", scrollToIndexFailed: "scrollToIndexFailed" }, queries: [{ propertyName: "itemDir", first: true, predicate: VListItemDirective, descendants: true }, { propertyName: "headerDir", first: true, predicate: VListHeaderDirective, descendants: true }, { propertyName: "footerDir", first: true, predicate: VListFooterDirective, descendants: true }, { propertyName: "emptyDir", first: true, predicate: VListEmptyDirective, descendants: true }, { propertyName: "separatorDir", first: true, predicate: VListSeparatorDirective, descendants: true }], viewQueries: [{ propertyName: "listRef", first: true, predicate: VirtualizedList, descendants: true }], usesOnChanges: true, hostDirectives: [{ directive: i1.SymbioteStyleInputDirective, inputs: ["style", "style"] }], ngImport: i0, template: `
275
228
  @if (isMultiColumn) {
276
229
  <VirtualizedList
277
230
  [data]="rows"
@@ -440,7 +393,7 @@ export class FlatList {
440
393
  }
441
394
  </VirtualizedList>
442
395
  }
443
- `, isInline: true, dependencies: [{ kind: "component", type: VirtualizedList, selector: "VirtualizedList", inputs: ["data", "getItem", "getItemCount", "keyExtractor", "getItemLayout", "horizontal", "inverted", "extraData", "onEndReachedThreshold", "onStartReachedThreshold", "refreshing", "progressViewOffset", "refreshRequested", "viewabilityConfig", "viewabilityConfigCallbackPairs", "initialNumToRender", "initialScrollIndex", "maxToRenderPerBatch", "updateCellsBatchingPeriod", "windowSize", "stickyHeaderIndices", "maintainVisibleContentPosition", "onScroll", "onScrollBeginDrag", "onScrollEndDrag", "onMomentumScrollBegin", "onMomentumScrollEnd", "scrollEventThrottle", "keyboardShouldPersistTaps", "keyboardDismissMode", "style", "contentContainerStyle", "testID", "nativeID", "accessible", "accessibilityLabel", "accessibilityHint", "accessibilityRole", "accessibilityState", "accessibilityValue", "accessibilityActions", "accessibilityLabelledBy", "importantForAccessibility", "accessibilityLiveRegion", "screenReaderFocusable", "accessibilityViewIsModal", "accessibilityElementsHidden", "accessibilityIgnoresInvertColors", "accessibilityLanguage", "accessibilityRespondsToUserInteraction", "accessibilityShowsLargeContentViewer", "accessibilityLargeContentTitle", "role", "ariaLabel", "ariaLabelledBy", "ariaLive", "ariaHidden", "ariaBusy", "ariaChecked", "ariaDisabled", "ariaExpanded", "ariaSelected", "ariaModal", "ariaValueMax", "ariaValueMin", "ariaValueNow", "ariaValueText"], outputs: ["endReached", "startReached", "refresh", "viewableItemsChanged", "scrollToIndexFailed", "accessibilityAction", "accessibilityTap", "magicTap", "accessibilityEscape"] }, { kind: "directive", type: VListItemDirective, selector: "[vListItem]" }, { kind: "directive", type: VListHeaderDirective, selector: "[vListHeader]" }, { kind: "directive", type: VListFooterDirective, selector: "[vListFooter]" }, { kind: "directive", type: VListEmptyDirective, selector: "[vListEmpty]" }, { kind: "directive", type: VListSeparatorDirective, selector: "[vListSeparator]" }, { kind: "directive", type: VListOutletDirective, selector: "[vListOutlet]", inputs: ["vListOutlet", "vListOutletContext"] }, { kind: "component", type: ViewHost, selector: "view" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
396
+ `, isInline: true, dependencies: [{ kind: "component", type: VirtualizedList, selector: "VirtualizedList", inputs: ["data", "getItem", "getItemCount", "keyExtractor", "getItemLayout", "horizontal", "inverted", "extraData", "onEndReachedThreshold", "onStartReachedThreshold", "refreshing", "progressViewOffset", "refreshRequested", "viewabilityConfig", "viewabilityConfigCallbackPairs", "initialNumToRender", "initialScrollIndex", "maxToRenderPerBatch", "updateCellsBatchingPeriod", "windowSize", "stickyHeaderIndices", "maintainVisibleContentPosition", "onScroll", "onScrollBeginDrag", "onScrollEndDrag", "onMomentumScrollBegin", "onMomentumScrollEnd", "scrollEventThrottle", "keyboardShouldPersistTaps", "keyboardDismissMode", "style", "contentContainerStyle", "testID", "nativeID", "accessible", "accessibilityLabel", "accessibilityHint", "accessibilityRole", "accessibilityState", "accessibilityValue", "accessibilityActions", "accessibilityLabelledBy", "importantForAccessibility", "accessibilityLiveRegion", "screenReaderFocusable", "accessibilityViewIsModal", "accessibilityElementsHidden", "accessibilityIgnoresInvertColors", "accessibilityLanguage", "accessibilityRespondsToUserInteraction", "accessibilityShowsLargeContentViewer", "accessibilityLargeContentTitle", "role", "ariaLabel", "ariaLabelledBy", "ariaLive", "ariaHidden", "ariaBusy", "ariaChecked", "ariaDisabled", "ariaExpanded", "ariaSelected", "ariaModal", "ariaValueMax", "ariaValueMin", "ariaValueNow", "ariaValueText"], outputs: ["endReached", "startReached", "refresh", "viewableItemsChanged", "scrollToIndexFailed", "accessibilityAction", "accessibilityTap", "magicTap", "accessibilityEscape"] }, { kind: "directive", type: VListItemDirective, selector: "[vListItem]" }, { kind: "directive", type: VListHeaderDirective, selector: "[vListHeader]" }, { kind: "directive", type: VListFooterDirective, selector: "[vListFooter]" }, { kind: "directive", type: VListEmptyDirective, selector: "[vListEmpty]" }, { kind: "directive", type: VListSeparatorDirective, selector: "[vListSeparator]" }, { kind: "directive", type: VListOutletDirective, selector: "[vListOutlet]", inputs: ["vListOutlet", "vListOutletContext"] }, { kind: "component", type: ViewHost, selector: "view" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
444
397
  }
445
398
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FlatList, decorators: [{
446
399
  type: Component,