@symbiote-native/angular 1.0.0 → 2.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.
- package/README.md +13 -10
- package/babel-register-composed.cjs +41 -19
- package/bin/symbiote-angular-dev.cjs +16 -4
- package/build/angular/anchor-host-registry.js +1 -17
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/change-detection-flush.d.ts +31 -0
- package/build/angular/change-detection-flush.js +110 -0
- package/build/angular/change-detection-flush.js.map +1 -0
- package/build/angular/components/activity-indicator-props.d.ts +2 -0
- package/build/angular/components/activity-indicator-props.js +2 -0
- package/build/angular/components/activity-indicator-props.js.map +1 -0
- package/build/angular/components/button-props.d.ts +2 -0
- package/build/angular/components/button-props.js +2 -0
- package/build/angular/components/button-props.js.map +1 -0
- package/build/angular/components/flat-list/index.d.ts +2 -1
- package/build/angular/components/flat-list/index.js +22 -10
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image-background-props.d.ts +6 -0
- package/build/angular/components/image-background-props.js +2 -0
- package/build/angular/components/image-background-props.js.map +1 -0
- package/build/angular/components/{image/shared.d.ts → image-shared.d.ts} +1 -1
- package/build/angular/components/{image/shared.js → image-shared.js} +7 -2
- package/build/angular/components/image-shared.js.map +1 -0
- package/build/angular/components/input-accessory-view-props.d.ts +7 -0
- package/build/angular/components/input-accessory-view-props.js +9 -0
- package/build/angular/components/input-accessory-view-props.js.map +1 -0
- package/build/angular/components/keyboard-avoiding-view/index.js +11 -17
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +1 -0
- package/build/angular/components/modal/index.js +19 -12
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable-props.d.ts +32 -0
- package/build/angular/components/pressable-props.js +11 -0
- package/build/angular/components/pressable-props.js.map +1 -0
- package/build/angular/components/refresh-control-props.d.ts +16 -0
- package/build/angular/components/refresh-control-props.js +14 -0
- package/build/angular/components/refresh-control-props.js.map +1 -0
- package/build/angular/components/safe-area-view-props.d.ts +7 -0
- package/build/angular/components/safe-area-view-props.js +11 -0
- package/build/angular/components/safe-area-view-props.js.map +1 -0
- package/build/angular/components/scroll-view-props.d.ts +76 -0
- package/build/angular/components/scroll-view-props.js +18 -0
- package/build/angular/components/scroll-view-props.js.map +1 -0
- package/build/angular/components/text-input-props.d.ts +45 -0
- package/build/angular/components/text-input-props.js +18 -0
- package/build/angular/components/text-input-props.js.map +1 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +6 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js +2 -0
- package/build/angular/components/touchable-native-feedback/touchable-native-feedback-props.js.map +1 -0
- package/build/angular/components/touchable-props.d.ts +14 -0
- package/build/angular/components/touchable-props.js +13 -0
- package/build/angular/components/touchable-props.js.map +1 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +3 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js +2 -0
- package/build/angular/components/touchable-without-feedback/touchable-without-feedback-props.js.map +1 -0
- package/build/angular/components/virtualized-list/index.d.ts +5 -1
- package/build/angular/components/virtualized-list/index.js +366 -280
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components.d.ts +20 -26
- package/build/angular/components.js +12 -14
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.d.ts +2 -2
- package/build/angular/create-portal/index.js +5 -5
- package/build/angular/create-tunnel/index.js +1 -1
- package/build/angular/element-props.d.ts +53 -0
- package/build/angular/element-props.js +2 -0
- package/build/angular/element-props.js.map +1 -0
- package/build/angular/elements.d.ts +528 -0
- package/build/angular/elements.js +1444 -0
- package/build/angular/elements.js.map +1 -0
- package/build/angular/host-instance/index.js +1 -1
- package/build/angular/index.d.ts +4 -2
- package/build/angular/index.js +15 -1
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +5 -6
- package/build/angular/modules/animated/create-animated-component.js +60 -73
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/animated/index.js +1 -1
- package/build/angular/modules/animated/index.js.map +1 -1
- package/build/angular/modules/image/index.d.ts +3 -0
- package/build/angular/modules/image/index.js +16 -0
- package/build/angular/modules/image/index.js.map +1 -0
- package/build/angular/primitives/index.d.ts +18 -18
- package/build/angular/primitives/index.js +40 -36
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +2 -3
- package/build/angular/primitives/shared.js +8 -53
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/register.d.ts +1 -1
- package/build/angular/register.js +12 -31
- package/build/angular/register.js.map +1 -1
- package/build/angular/render/index.js +3 -3
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.js +113 -67
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/renderer/value-change.d.ts +2 -0
- package/build/angular/renderer/value-change.js +9 -0
- package/build/angular/renderer/value-change.js.map +1 -0
- package/metro-config.cjs +54 -20
- package/metro-css-parser.cjs +2 -1
- package/package.json +5 -14
- package/src/anchor-host-registry.ts +1 -17
- package/src/change-detection-flush.ts +120 -0
- package/src/components/activity-indicator-props.ts +21 -0
- package/src/components/button-props.ts +26 -0
- package/src/components/flat-list/index.ts +12 -4
- package/src/components/image-background-props.ts +24 -0
- package/src/components/{image/shared.ts → image-shared.ts} +6 -1
- package/src/components/input-accessory-view-props.ts +21 -0
- package/src/components/keyboard-avoiding-view/index.ts +6 -9
- package/src/components/modal/index.ts +15 -7
- package/src/components/pressable-props.ts +73 -0
- package/src/components/refresh-control-props.ts +43 -0
- package/src/components/safe-area-view-props.ts +28 -0
- package/src/components/scroll-view-props.ts +109 -0
- package/src/components/text-input-props.ts +72 -0
- package/src/components/touchable-native-feedback/touchable-native-feedback-props.ts +23 -0
- package/src/components/touchable-props.ts +30 -0
- package/src/components/touchable-without-feedback/touchable-without-feedback-props.ts +21 -0
- package/src/components/virtualized-list/index.ts +239 -156
- package/src/components.ts +84 -35
- package/src/create-portal/index.ts +5 -5
- package/src/create-tunnel/index.ts +1 -1
- package/src/diagnostics.ts +1 -1
- package/src/element-props.ts +90 -0
- package/src/elements.ts +919 -0
- package/src/host-instance/index.ts +1 -1
- package/src/index.ts +52 -15
- package/src/modules/animated/create-animated-component.ts +50 -55
- package/src/modules/animated/index.ts +1 -1
- package/src/modules/image/index.ts +18 -0
- package/src/primitives/index.ts +22 -18
- package/src/primitives/shared.ts +11 -44
- package/src/register.ts +12 -40
- package/src/render/index.ts +3 -3
- package/src/renderer/index.ts +122 -73
- package/src/renderer/value-change.ts +8 -0
- package/babel-lower-host-primitives.cjs +0 -636
- package/build/angular/components/activity-indicator/index.android.d.ts +0 -13
- package/build/angular/components/activity-indicator/index.android.js +0 -29
- package/build/angular/components/activity-indicator/index.android.js.map +0 -1
- package/build/angular/components/activity-indicator/index.d.ts +0 -1
- package/build/angular/components/activity-indicator/index.ios.d.ts +0 -9
- package/build/angular/components/activity-indicator/index.ios.js +0 -26
- package/build/angular/components/activity-indicator/index.ios.js.map +0 -1
- package/build/angular/components/activity-indicator/index.js +0 -4
- package/build/angular/components/activity-indicator/index.js.map +0 -1
- package/build/angular/components/activity-indicator/shared.d.ts +0 -67
- package/build/angular/components/activity-indicator/shared.js +0 -260
- package/build/angular/components/activity-indicator/shared.js.map +0 -1
- package/build/angular/components/button.d.ts +0 -68
- package/build/angular/components/button.js +0 -360
- package/build/angular/components/button.js.map +0 -1
- package/build/angular/components/image/index.android.d.ts +0 -15
- package/build/angular/components/image/index.android.js +0 -83
- package/build/angular/components/image/index.android.js.map +0 -1
- package/build/angular/components/image/index.d.ts +0 -1
- package/build/angular/components/image/index.ios.d.ts +0 -15
- package/build/angular/components/image/index.ios.js +0 -83
- package/build/angular/components/image/index.ios.js.map +0 -1
- package/build/angular/components/image/index.js +0 -3
- package/build/angular/components/image/index.js.map +0 -1
- package/build/angular/components/image/shared.js.map +0 -1
- package/build/angular/components/image-background.d.ts +0 -85
- package/build/angular/components/image-background.js +0 -443
- package/build/angular/components/image-background.js.map +0 -1
- package/build/angular/components/input-accessory-view/index.d.ts +0 -66
- package/build/angular/components/input-accessory-view/index.js +0 -287
- package/build/angular/components/input-accessory-view/index.js.map +0 -1
- package/build/angular/components/pressable/index.d.ts +0 -134
- package/build/angular/components/pressable/index.js +0 -514
- package/build/angular/components/pressable/index.js.map +0 -1
- package/build/angular/components/refresh-control/index.d.ts +0 -91
- package/build/angular/components/refresh-control/index.js +0 -382
- package/build/angular/components/refresh-control/index.js.map +0 -1
- package/build/angular/components/safe-area-view/index.d.ts +0 -64
- package/build/angular/components/safe-area-view/index.js +0 -287
- package/build/angular/components/safe-area-view/index.js.map +0 -1
- package/build/angular/components/scroll-view/index.android.d.ts +0 -20
- package/build/angular/components/scroll-view/index.android.js +0 -262
- package/build/angular/components/scroll-view/index.android.js.map +0 -1
- package/build/angular/components/scroll-view/index.d.ts +0 -3
- package/build/angular/components/scroll-view/index.ios.d.ts +0 -8
- package/build/angular/components/scroll-view/index.ios.js +0 -92
- package/build/angular/components/scroll-view/index.ios.js.map +0 -1
- package/build/angular/components/scroll-view/index.js +0 -6
- package/build/angular/components/scroll-view/index.js.map +0 -1
- package/build/angular/components/scroll-view/projection.d.ts +0 -67
- package/build/angular/components/scroll-view/projection.js +0 -560
- package/build/angular/components/scroll-view/projection.js.map +0 -1
- package/build/angular/components/scroll-view/shared.d.ts +0 -241
- package/build/angular/components/scroll-view/shared.js +0 -848
- package/build/angular/components/scroll-view/shared.js.map +0 -1
- package/build/angular/components/scroll-view/sticky-header.d.ts +0 -40
- package/build/angular/components/scroll-view/sticky-header.js +0 -224
- package/build/angular/components/scroll-view/sticky-header.js.map +0 -1
- package/build/angular/components/switch/index.android.d.ts +0 -24
- package/build/angular/components/switch/index.android.js +0 -112
- package/build/angular/components/switch/index.android.js.map +0 -1
- package/build/angular/components/switch/index.d.ts +0 -1
- package/build/angular/components/switch/index.ios.d.ts +0 -23
- package/build/angular/components/switch/index.ios.js +0 -111
- package/build/angular/components/switch/index.ios.js.map +0 -1
- package/build/angular/components/switch/index.js +0 -4
- package/build/angular/components/switch/index.js.map +0 -1
- package/build/angular/components/switch/shared.d.ts +0 -49
- package/build/angular/components/switch/shared.js +0 -191
- package/build/angular/components/switch/shared.js.map +0 -1
- package/build/angular/components/text-input.d.ts +0 -167
- package/build/angular/components/text-input.js +0 -684
- package/build/angular/components/text-input.js.map +0 -1
- package/build/angular/components/touchable/index.d.ts +0 -283
- package/build/angular/components/touchable/index.js +0 -1453
- package/build/angular/components/touchable/index.js.map +0 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +0 -87
- package/build/angular/components/touchable-native-feedback/index.js +0 -409
- package/build/angular/components/touchable-native-feedback/index.js.map +0 -1
- package/build/angular/state-style.d.ts +0 -9
- package/build/angular/state-style.js +0 -39
- package/build/angular/state-style.js.map +0 -1
- package/metro-transformer.cjs +0 -69
- package/src/components/activity-indicator/index.android.ts +0 -23
- package/src/components/activity-indicator/index.ios.ts +0 -21
- package/src/components/activity-indicator/index.ts +0 -4
- package/src/components/activity-indicator/shared.ts +0 -225
- package/src/components/button.ts +0 -237
- package/src/components/image/index.android.ts +0 -95
- package/src/components/image/index.ios.ts +0 -95
- package/src/components/image/index.ts +0 -3
- package/src/components/image-background.ts +0 -308
- package/src/components/input-accessory-view/index.ts +0 -246
- package/src/components/pressable/index.ts +0 -522
- package/src/components/refresh-control/index.ts +0 -349
- package/src/components/safe-area-view/index.ts +0 -247
- package/src/components/scroll-view/index.android.ts +0 -205
- package/src/components/scroll-view/index.ios.ts +0 -81
- package/src/components/scroll-view/index.ts +0 -7
- package/src/components/scroll-view/projection.ts +0 -713
- package/src/components/scroll-view/shared.ts +0 -1093
- package/src/components/scroll-view/sticky-header.ts +0 -267
- package/src/components/switch/index.android.ts +0 -113
- package/src/components/switch/index.ios.ts +0 -112
- package/src/components/switch/index.ts +0 -4
- package/src/components/switch/shared.ts +0 -284
- package/src/components/text-input.ts +0 -669
- package/src/components/touchable/index.ts +0 -956
- package/src/components/touchable-native-feedback/index.ts +0 -327
- package/src/state-style.ts +0 -39
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// in ngAfterViewChecked. OnPush + ChangeDetectorRef.markForCheck() drives re-render off the native
|
|
10
10
|
// scroll/layout/measure callbacks (they fire outside Angular's own event bindings, so they must
|
|
11
11
|
// mark the view dirty). This is the Angular twin of the React useReducer/useEffect and Vue
|
|
12
|
-
// ref/computed/watch over the same shared functions. It
|
|
13
|
-
// as the React/Vue lists drive their
|
|
12
|
+
// ref/computed/watch over the same shared functions. It writes the `scroll-view`/
|
|
13
|
+
// `horizontal-scroll-view` TAG directly, exactly as the React/Vue lists drive their scroll tag.
|
|
14
14
|
//
|
|
15
15
|
// Per-item rendering is TEMPLATES, not a callback: React/Vue `renderItem: (info) => element` does
|
|
16
16
|
// not translate to Angular. The app supplies the cell via `<ng-template vListItem>` (and the
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// shape differs, since a callback prop returning an element has no Angular equivalent.
|
|
23
23
|
//
|
|
24
24
|
// Lists have no Descriptor render fn — the cell content is the framework's own children.
|
|
25
|
-
// Cells/spacers are plain
|
|
25
|
+
// Cells/spacers are plain view host elements.
|
|
26
26
|
|
|
27
27
|
import {
|
|
28
28
|
CUSTOM_ELEMENTS_SCHEMA,
|
|
@@ -80,20 +80,21 @@ import {
|
|
|
80
80
|
type IViewabilityConfigCallbackPair,
|
|
81
81
|
type IViewableItemsChangedInfo,
|
|
82
82
|
type IVirtualizedListHandle,
|
|
83
|
+
buildScrollViewHandle,
|
|
83
84
|
} from '@symbiote-native/components';
|
|
84
85
|
import {
|
|
85
86
|
dlog,
|
|
86
87
|
flattenStyle,
|
|
88
|
+
isSymbioteNode,
|
|
87
89
|
type IStyleProp,
|
|
88
90
|
type ISymbioteEvent,
|
|
89
91
|
type ISymbioteNode,
|
|
90
92
|
type IViewStyle,
|
|
91
93
|
} from '@symbiote-native/engine';
|
|
92
94
|
import { countAngular } from '../../diagnostics';
|
|
93
|
-
import { ScrollView } from '../scroll-view';
|
|
94
|
-
import { RefreshControl } from '../refresh-control';
|
|
95
95
|
import {
|
|
96
96
|
anchorHostStyle,
|
|
97
|
+
SymbioteHostPropsDirective,
|
|
97
98
|
SymbioteStyleInputDirective,
|
|
98
99
|
ViewHost,
|
|
99
100
|
} from '../../primitives';
|
|
@@ -130,8 +131,6 @@ export {
|
|
|
130
131
|
import {
|
|
131
132
|
gateWanted,
|
|
132
133
|
injectGateDemandAbove,
|
|
133
|
-
provideGateDemand,
|
|
134
|
-
type IGateDemand,
|
|
135
134
|
type IGatedAccessibilityEvent,
|
|
136
135
|
} from '../../gate-demand';
|
|
137
136
|
export type { IVListItemContext, IVListSeparatorContext } from './directives';
|
|
@@ -217,145 +216,162 @@ interface IWindowCell<ItemT> {
|
|
|
217
216
|
separatorContext?: IVListSeparatorContext<ItemT>;
|
|
218
217
|
}
|
|
219
218
|
|
|
219
|
+
// The scroll body is identical on both axes; only the outer tag differs (the axis comes from the
|
|
220
|
+
// TAG, never a prop — `<horizontal-scroll-view>` vs `<scroll-view>`, matching every other adapter,
|
|
221
|
+
// `.claude/rules/host-primitive-tier.md`). No shared-outlet primitive exists to de-duplicate this
|
|
222
|
+
// (this package's own NgTemplateOutlet twin is keyed on a per-cell context, not a static block, and
|
|
223
|
+
// ngc's partial evaluator needs the decorator's `template` to be one literal), so the body is
|
|
224
|
+
// duplicated across the two branches — the same shape Svelte's own `{#if horizontal}` takes.
|
|
220
225
|
@Component({
|
|
221
226
|
selector: 'VirtualizedList',
|
|
222
227
|
standalone: true,
|
|
223
|
-
viewProviders: [provideGateDemand(() => VirtualizedList)],
|
|
224
228
|
hostDirectives: [
|
|
225
229
|
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
226
230
|
],
|
|
227
231
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
228
|
-
imports: [
|
|
232
|
+
imports: [SymbioteHostPropsDirective, VListOutletDirective, ViewHost],
|
|
229
233
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
230
234
|
template: `
|
|
231
|
-
|
|
232
|
-
[
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
[accessibilityLabel]="foldedAccessibility().accessibilityLabel"
|
|
239
|
-
[accessibilityHint]="foldedAccessibility().accessibilityHint"
|
|
240
|
-
[accessibilityRole]="foldedAccessibility().accessibilityRole"
|
|
241
|
-
[accessibilityState]="foldedAccessibility().accessibilityState"
|
|
242
|
-
[accessibilityValue]="foldedAccessibility().accessibilityValue"
|
|
243
|
-
[accessibilityActions]="foldedAccessibility().accessibilityActions"
|
|
244
|
-
[accessibilityLabelledBy]="foldedAccessibility().accessibilityLabelledBy"
|
|
245
|
-
[importantForAccessibility]="
|
|
246
|
-
foldedAccessibility().importantForAccessibility
|
|
247
|
-
"
|
|
248
|
-
[accessibilityLiveRegion]="foldedAccessibility().accessibilityLiveRegion"
|
|
249
|
-
[screenReaderFocusable]="foldedAccessibility().screenReaderFocusable"
|
|
250
|
-
[accessibilityViewIsModal]="
|
|
251
|
-
foldedAccessibility().accessibilityViewIsModal
|
|
252
|
-
"
|
|
253
|
-
[accessibilityElementsHidden]="
|
|
254
|
-
foldedAccessibility().accessibilityElementsHidden
|
|
255
|
-
"
|
|
256
|
-
[accessibilityIgnoresInvertColors]="
|
|
257
|
-
foldedAccessibility().accessibilityIgnoresInvertColors
|
|
258
|
-
"
|
|
259
|
-
[accessibilityLanguage]="foldedAccessibility().accessibilityLanguage"
|
|
260
|
-
[accessibilityRespondsToUserInteraction]="
|
|
261
|
-
foldedAccessibility().accessibilityRespondsToUserInteraction
|
|
262
|
-
"
|
|
263
|
-
[accessibilityShowsLargeContentViewer]="
|
|
264
|
-
foldedAccessibility().accessibilityShowsLargeContentViewer
|
|
265
|
-
"
|
|
266
|
-
[accessibilityLargeContentTitle]="
|
|
267
|
-
foldedAccessibility().accessibilityLargeContentTitle
|
|
268
|
-
"
|
|
269
|
-
(accessibilityAction)="accessibilityActionTick($event)"
|
|
270
|
-
(accessibilityTap)="accessibilityTapTick($event)"
|
|
271
|
-
(magicTap)="magicTapTick($event)"
|
|
272
|
-
(accessibilityEscape)="accessibilityEscapeTick($event)"
|
|
273
|
-
[onScroll]="onScrollTick"
|
|
274
|
-
(layout)="onLayoutTick($event)"
|
|
275
|
-
[onScrollBeginDrag]="onScrollBeginDrag"
|
|
276
|
-
[onScrollEndDrag]="onScrollEndDrag"
|
|
277
|
-
[onMomentumScrollBegin]="onMomentumScrollBegin"
|
|
278
|
-
[onMomentumScrollEnd]="onMomentumScrollEnd"
|
|
279
|
-
[scrollEventThrottle]="scrollEventThrottle"
|
|
280
|
-
[keyboardShouldPersistTaps]="keyboardShouldPersistTaps"
|
|
281
|
-
[keyboardDismissMode]="keyboardDismissMode"
|
|
282
|
-
[contentOffset]="commandedOffset"
|
|
283
|
-
[stickyHeaderIndices]="renderedStickyIndices"
|
|
284
|
-
[maintainVisibleContentPosition]="resolvedMaintainVisibleContentPosition"
|
|
285
|
-
>
|
|
286
|
-
@if (shouldRenderRefreshControl) {
|
|
287
|
-
<RefreshControl
|
|
288
|
-
[refreshing]="refreshing ?? false"
|
|
289
|
-
(refresh)="handleRefresh()"
|
|
290
|
-
[progressViewOffset]="progressViewOffset"
|
|
291
|
-
/>
|
|
292
|
-
}
|
|
235
|
+
@if (isHorizontal) {
|
|
236
|
+
<horizontal-scroll-view #scrollHost [symbioteHostProps]="scrollViewBag()">
|
|
237
|
+
@if (shouldRenderRefreshControl) {
|
|
238
|
+
<refresh-control
|
|
239
|
+
[symbioteHostProps]="refreshControlBag()"
|
|
240
|
+
></refresh-control>
|
|
241
|
+
}
|
|
293
242
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
243
|
+
@if (headerDir !== undefined) {
|
|
244
|
+
<view>
|
|
245
|
+
<ng-container [vListOutlet]="headerDir.templateRef"></ng-container>
|
|
246
|
+
</view>
|
|
247
|
+
}
|
|
299
248
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
249
|
+
@if (itemCount === 0) {
|
|
250
|
+
@if (emptyDir !== undefined) {
|
|
251
|
+
<view>
|
|
252
|
+
<ng-container [vListOutlet]="emptyDir.templateRef"></ng-container>
|
|
253
|
+
</view>
|
|
254
|
+
}
|
|
255
|
+
} @else {
|
|
256
|
+
@if (leadingSpacerStyle !== null) {
|
|
257
|
+
<view [style]="leadingSpacerStyle"></view>
|
|
258
|
+
}
|
|
259
|
+
@if (forcedStickyCell !== null) {
|
|
260
|
+
<view
|
|
261
|
+
(layout)="handleCellLayout(forcedStickyCell.measure, $event)"
|
|
262
|
+
[style]="cellStyle"
|
|
263
|
+
>
|
|
264
|
+
<ng-container
|
|
265
|
+
[vListOutlet]="itemDir?.templateRef"
|
|
266
|
+
[vListOutletContext]="forcedStickyCell.context"
|
|
267
|
+
></ng-container>
|
|
268
|
+
</view>
|
|
269
|
+
}
|
|
270
|
+
@if (gapSpacerStyle !== null) {
|
|
271
|
+
<view [style]="gapSpacerStyle"></view>
|
|
272
|
+
}
|
|
273
|
+
@for (cell of windowCells; track cell.key) {
|
|
274
|
+
<view
|
|
275
|
+
(layout)="handleCellLayout(cell.measure, $event)"
|
|
276
|
+
[style]="cellStyle"
|
|
277
|
+
>
|
|
278
|
+
<ng-container
|
|
279
|
+
[vListOutlet]="itemDir?.templateRef"
|
|
280
|
+
[vListOutletContext]="cell.context"
|
|
281
|
+
></ng-container>
|
|
282
|
+
@if (cell.separatorContext !== undefined) {
|
|
283
|
+
<ng-container
|
|
284
|
+
[vListOutlet]="separatorDir?.templateRef"
|
|
285
|
+
[vListOutletContext]="cell.separatorContext"
|
|
286
|
+
></ng-container>
|
|
287
|
+
}
|
|
288
|
+
</view>
|
|
289
|
+
}
|
|
290
|
+
@if (trailingSpacerStyle !== null) {
|
|
291
|
+
<view [style]="trailingSpacerStyle"></view>
|
|
292
|
+
}
|
|
305
293
|
}
|
|
306
|
-
|
|
307
|
-
@if (
|
|
308
|
-
<
|
|
294
|
+
|
|
295
|
+
@if (footerDir !== undefined) {
|
|
296
|
+
<view>
|
|
297
|
+
<ng-container [vListOutlet]="footerDir.templateRef"></ng-container>
|
|
298
|
+
</view>
|
|
309
299
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
[vListOutletContext]="forcedStickyCell.context"
|
|
318
|
-
></ng-container>
|
|
319
|
-
</symbiote-view>
|
|
300
|
+
</horizontal-scroll-view>
|
|
301
|
+
} @else {
|
|
302
|
+
<scroll-view #scrollHost [symbioteHostProps]="scrollViewBag()">
|
|
303
|
+
@if (shouldRenderRefreshControl) {
|
|
304
|
+
<refresh-control
|
|
305
|
+
[symbioteHostProps]="refreshControlBag()"
|
|
306
|
+
></refresh-control>
|
|
320
307
|
}
|
|
321
|
-
|
|
322
|
-
|
|
308
|
+
|
|
309
|
+
@if (headerDir !== undefined) {
|
|
310
|
+
<view>
|
|
311
|
+
<ng-container [vListOutlet]="headerDir.templateRef"></ng-container>
|
|
312
|
+
</view>
|
|
323
313
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
@
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
[
|
|
339
|
-
|
|
340
|
-
@if (cell.separatorContext !== undefined) {
|
|
314
|
+
|
|
315
|
+
@if (itemCount === 0) {
|
|
316
|
+
@if (emptyDir !== undefined) {
|
|
317
|
+
<view>
|
|
318
|
+
<ng-container [vListOutlet]="emptyDir.templateRef"></ng-container>
|
|
319
|
+
</view>
|
|
320
|
+
}
|
|
321
|
+
} @else {
|
|
322
|
+
@if (leadingSpacerStyle !== null) {
|
|
323
|
+
<view [style]="leadingSpacerStyle"></view>
|
|
324
|
+
}
|
|
325
|
+
@if (forcedStickyCell !== null) {
|
|
326
|
+
<view
|
|
327
|
+
(layout)="handleCellLayout(forcedStickyCell.measure, $event)"
|
|
328
|
+
[style]="cellStyle"
|
|
329
|
+
>
|
|
341
330
|
<ng-container
|
|
342
|
-
[vListOutlet]="
|
|
343
|
-
[vListOutletContext]="
|
|
331
|
+
[vListOutlet]="itemDir?.templateRef"
|
|
332
|
+
[vListOutletContext]="forcedStickyCell.context"
|
|
344
333
|
></ng-container>
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
334
|
+
</view>
|
|
335
|
+
}
|
|
336
|
+
@if (gapSpacerStyle !== null) {
|
|
337
|
+
<view [style]="gapSpacerStyle"></view>
|
|
338
|
+
}
|
|
339
|
+
<!-- The separator sits INSIDE the measuring view, as RN's own cell renderer places it
|
|
340
|
+
(VirtualizedListCellRenderer.js:218-221). As a sibling it is an extra flex child, so
|
|
341
|
+
the chrome between two cells is gap + separator + gap while a spacer collapsing that
|
|
342
|
+
region replaces it with a single gap — every cell below the leading spacer then lands
|
|
343
|
+
short by (separator + gap) and the content jumps by that much whenever the window's
|
|
344
|
+
first index moves. Measured at exactly 17px on device 2026-08-19; see
|
|
345
|
+
.claude/rules/list-geometry-feedback-loop.md. -->
|
|
346
|
+
@for (cell of windowCells; track cell.key) {
|
|
347
|
+
<view
|
|
348
|
+
(layout)="handleCellLayout(cell.measure, $event)"
|
|
349
|
+
[style]="cellStyle"
|
|
350
|
+
>
|
|
351
|
+
<ng-container
|
|
352
|
+
[vListOutlet]="itemDir?.templateRef"
|
|
353
|
+
[vListOutletContext]="cell.context"
|
|
354
|
+
></ng-container>
|
|
355
|
+
@if (cell.separatorContext !== undefined) {
|
|
356
|
+
<ng-container
|
|
357
|
+
[vListOutlet]="separatorDir?.templateRef"
|
|
358
|
+
[vListOutletContext]="cell.separatorContext"
|
|
359
|
+
></ng-container>
|
|
360
|
+
}
|
|
361
|
+
</view>
|
|
362
|
+
}
|
|
363
|
+
@if (trailingSpacerStyle !== null) {
|
|
364
|
+
<view [style]="trailingSpacerStyle"></view>
|
|
365
|
+
}
|
|
350
366
|
}
|
|
351
|
-
}
|
|
352
367
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
368
|
+
@if (footerDir !== undefined) {
|
|
369
|
+
<view>
|
|
370
|
+
<ng-container [vListOutlet]="footerDir.templateRef"></ng-container>
|
|
371
|
+
</view>
|
|
372
|
+
}
|
|
373
|
+
</scroll-view>
|
|
374
|
+
}
|
|
359
375
|
`,
|
|
360
376
|
})
|
|
361
377
|
export class VirtualizedList<ItemT = unknown>
|
|
@@ -369,7 +385,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
369
385
|
{
|
|
370
386
|
// The list's edge/viewability/failure events as real Angular events: `(endReached)="…"`, not
|
|
371
387
|
// `[onEndReached]="…"`. See handleRefresh/accessibility*Tick below for how the still-callback-
|
|
372
|
-
// shaped
|
|
388
|
+
// shaped scroll-view/refresh-control bag keys are fed from these.
|
|
373
389
|
@Output() readonly endReached = new EventEmitter<{
|
|
374
390
|
distanceFromEnd: number;
|
|
375
391
|
}>();
|
|
@@ -493,10 +509,24 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
493
509
|
@ContentChild(VListSeparatorDirective)
|
|
494
510
|
separatorDir?: VListSeparatorDirective<ItemT>;
|
|
495
511
|
|
|
496
|
-
// The
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
|
|
512
|
+
// The inner scroll TAG's own engine node — a template ref on a bare intrinsic hands back the
|
|
513
|
+
// host node directly (`isSymbioteNode(elementRef.nativeElement)`, matching `anchorHostStyle`'s
|
|
514
|
+
// own reader below), so there is no wrapper component left to read a handle off. Present from
|
|
515
|
+
// whichever branch (`scroll-view` / `horizontal-scroll-view`) is currently mounted, once the
|
|
516
|
+
// template has run at least one CD pass.
|
|
517
|
+
@ViewChild('scrollHost', { read: ElementRef })
|
|
518
|
+
private scrollHostRef?: ElementRef<unknown>;
|
|
519
|
+
|
|
520
|
+
private get scrollNode(): ISymbioteNode | null {
|
|
521
|
+
const node = this.scrollHostRef?.nativeElement;
|
|
522
|
+
return isSymbioteNode(node) ? node : null;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// `buildScrollViewHandle` reads the node lazily through the getter above and no-ops before
|
|
526
|
+
// commit — the same function every other adapter feeds from its own host-instance accessor.
|
|
527
|
+
private readonly scrollHandle: IScrollViewHandle = buildScrollViewHandle(
|
|
528
|
+
() => this.scrollNode,
|
|
529
|
+
);
|
|
500
530
|
|
|
501
531
|
// --- template-bound view state, assembled in ngDoCheck (recomputeView) ---
|
|
502
532
|
itemCount = EMPTY_OFFSET;
|
|
@@ -578,13 +608,13 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
578
608
|
|
|
579
609
|
private readonly cdr = inject(ChangeDetectorRef);
|
|
580
610
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
581
|
-
// onto (see anchorHostStyle's doc comment) — NOT `
|
|
582
|
-
// inner `<
|
|
611
|
+
// onto (see anchorHostStyle's doc comment) — NOT `scrollHostRef` above, which targets the real
|
|
612
|
+
// inner `<scroll-view>`/`<horizontal-scroll-view>` tag one level down.
|
|
583
613
|
private readonly elementRef = inject(ElementRef);
|
|
584
614
|
|
|
585
|
-
// Bridges the non-reactive @Input fields into the reactive graph so
|
|
586
|
-
// memoize a bag derived from them (
|
|
587
|
-
//
|
|
615
|
+
// Bridges the non-reactive @Input fields into the reactive graph so `foldedAccessibility` below
|
|
616
|
+
// can memoize a bag derived from them (read its own comment for why signal inputs are not an
|
|
617
|
+
// option while this package's unit suite runs on JIT).
|
|
588
618
|
//
|
|
589
619
|
// ONLY safe for a bag whose every dependency is an @Input. This list's window machinery
|
|
590
620
|
// (listState, renderVersion, windowCells, the measured metrics) is driven from scroll/layout
|
|
@@ -593,12 +623,13 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
593
623
|
// instead of widening this one.
|
|
594
624
|
private readonly inputsRevision = signal(0);
|
|
595
625
|
|
|
596
|
-
// MEASURED: the template
|
|
597
|
-
// not cache a getter across binding expressions - as a getter it
|
|
598
|
-
// refresh, and every consumer saw a fresh reference, so all 20
|
|
599
|
-
//
|
|
600
|
-
// input actually changes. Every dependency below is an @Input, which is what makes
|
|
601
|
-
// inputsRevision a complete dependency set here
|
|
626
|
+
// MEASURED (while ScrollView was still a wrapper): the template fed 20 separate inputs off this
|
|
627
|
+
// one bag, and Angular does not cache a getter across binding expressions - as a getter it
|
|
628
|
+
// rebuilt the object 20 times per refresh, and every consumer saw a fresh reference, so all 20
|
|
629
|
+
// reported "changed" and wrote through. A computed evaluates once and hands back the SAME object
|
|
630
|
+
// until an input actually changes. Every dependency below is an @Input, which is what makes
|
|
631
|
+
// inputsRevision a complete dependency set here — `scrollViewBag()` reuses this result rather
|
|
632
|
+
// than re-running the fold.
|
|
602
633
|
readonly foldedAccessibility = computed<
|
|
603
634
|
IAccessibilityProps & IAriaProps & Record<string, unknown>
|
|
604
635
|
>(() => {
|
|
@@ -648,6 +679,57 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
648
679
|
return this.refreshRequested ?? this.refresh.observed;
|
|
649
680
|
}
|
|
650
681
|
|
|
682
|
+
// The flat bag fed to the scroll tag via `[symbioteHostProps]`. A plain method, not `computed()`:
|
|
683
|
+
// most of these fields (resolvedStyle, renderedStickyIndices, commandedOffset, …) are recomputed
|
|
684
|
+
// in ngDoCheck/dispatch as ordinary fields, not signals, so a computed() memoized only against
|
|
685
|
+
// `inputsRevision` would go stale the moment one of them changed outside an @Input() write. The
|
|
686
|
+
// directive itself diffs per key before writing to the engine (`primitives/shared.ts`), so a
|
|
687
|
+
// fresh object every CD pass costs nothing beyond the literal allocation.
|
|
688
|
+
scrollViewBag(): Record<string, unknown> {
|
|
689
|
+
return {
|
|
690
|
+
...this.foldedAccessibility(),
|
|
691
|
+
style: this.resolvedStyle,
|
|
692
|
+
contentContainerStyle: this.resolvedContentContainerStyle,
|
|
693
|
+
onScroll: this.onScrollTick,
|
|
694
|
+
onLayout: this.onLayoutTick,
|
|
695
|
+
onScrollBeginDrag: this.onScrollBeginDrag,
|
|
696
|
+
onScrollEndDrag: this.onScrollEndDrag,
|
|
697
|
+
onMomentumScrollBegin: this.onMomentumScrollBegin,
|
|
698
|
+
onMomentumScrollEnd: this.onMomentumScrollEnd,
|
|
699
|
+
scrollEventThrottle: this.scrollEventThrottle,
|
|
700
|
+
keyboardShouldPersistTaps: this.keyboardShouldPersistTaps,
|
|
701
|
+
keyboardDismissMode: this.keyboardDismissMode,
|
|
702
|
+
contentOffset: this.commandedOffset,
|
|
703
|
+
stickyHeaderIndices: this.renderedStickyIndices,
|
|
704
|
+
maintainVisibleContentPosition:
|
|
705
|
+
this.resolvedMaintainVisibleContentPosition,
|
|
706
|
+
// The four boolean-gated Fabric events (`.claude/rules/fabric-boolean-event-gates.md`): the
|
|
707
|
+
// TAG has no `.observed` of its own, so `wantsGate` (own subscriber, or the demand from
|
|
708
|
+
// whatever wraps this list) is what decides whether the flag reaches Fabric at all.
|
|
709
|
+
onAccessibilityAction: this.wantsGate('accessibilityAction')
|
|
710
|
+
? this.accessibilityActionTick
|
|
711
|
+
: undefined,
|
|
712
|
+
onAccessibilityTap: this.wantsGate('accessibilityTap')
|
|
713
|
+
? this.accessibilityTapTick
|
|
714
|
+
: undefined,
|
|
715
|
+
onMagicTap: this.wantsGate('magicTap') ? this.magicTapTick : undefined,
|
|
716
|
+
onAccessibilityEscape: this.wantsGate('accessibilityEscape')
|
|
717
|
+
? this.accessibilityEscapeTick
|
|
718
|
+
: undefined,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// The `refresh-control` tag's own bag — an ordinary first child, re-parented per platform by the
|
|
723
|
+
// scroll-view behavior (`../refresh-control-props.ts`). `onRefresh` is a plain listener prop, not
|
|
724
|
+
// a gated one: RefreshControl's `refresh` is unaffected by `fabric-boolean-event-gates.md`.
|
|
725
|
+
refreshControlBag(): Record<string, unknown> {
|
|
726
|
+
return {
|
|
727
|
+
refreshing: this.refreshing ?? false,
|
|
728
|
+
progressViewOffset: this.progressViewOffset,
|
|
729
|
+
onRefresh: this.handleRefresh,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
651
733
|
get isHorizontal(): boolean {
|
|
652
734
|
return this.horizontal === true;
|
|
653
735
|
}
|
|
@@ -675,9 +757,10 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
675
757
|
return this.onStartReachedThreshold ?? DEFAULT_START_REACHED_THRESHOLD;
|
|
676
758
|
}
|
|
677
759
|
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
760
|
+
// onScroll stays a plain callback bag key (an Animated.event(...) target must be able to flow
|
|
761
|
+
// through it, which an @Output() can't carry), so this is a stable arrow field read straight into
|
|
762
|
+
// scrollViewBag()'s `onScroll`. onLayout is unconditional too — this list always needs its own
|
|
763
|
+
// layout for windowing, regardless of whether an app subscribes to anything.
|
|
681
764
|
onScrollTick = (event: ISymbioteEvent): void => {
|
|
682
765
|
countAngular('scrollTicks');
|
|
683
766
|
const offset = readScrollOffset(event, this.isHorizontal);
|
|
@@ -697,9 +780,9 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
697
780
|
this.dispatch({ kind: 'layout', length });
|
|
698
781
|
};
|
|
699
782
|
|
|
700
|
-
// RefreshControl's refresh and
|
|
701
|
-
//
|
|
702
|
-
//
|
|
783
|
+
// RefreshControl's refresh and the scroll tag's accessibility events reach here as plain
|
|
784
|
+
// listener-prop values in scrollViewBag()/refreshControlBag() — these fields just adapt
|
|
785
|
+
// VirtualizedList's own @Output() into a plain re-emit callback.
|
|
703
786
|
handleRefresh = (): void => {
|
|
704
787
|
this.refresh.emit();
|
|
705
788
|
};
|
|
@@ -1263,23 +1346,23 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
1263
1346
|
}
|
|
1264
1347
|
|
|
1265
1348
|
flashScrollIndicators(): void {
|
|
1266
|
-
this.
|
|
1349
|
+
this.scrollHandle.flashScrollIndicators();
|
|
1267
1350
|
}
|
|
1268
1351
|
|
|
1269
1352
|
getNativeScrollRef(): IScrollViewHandle | null {
|
|
1270
|
-
return this.
|
|
1353
|
+
return this.scrollNode !== null ? this.scrollHandle : null;
|
|
1271
1354
|
}
|
|
1272
1355
|
|
|
1273
1356
|
getScrollableNode(): IScrollViewHandle | null {
|
|
1274
|
-
return this.
|
|
1357
|
+
return this.scrollNode !== null ? this.scrollHandle : null;
|
|
1275
1358
|
}
|
|
1276
1359
|
|
|
1277
1360
|
getScrollResponder(): IScrollViewHandle | null {
|
|
1278
|
-
return this.
|
|
1361
|
+
return this.scrollNode !== null ? this.scrollHandle : null;
|
|
1279
1362
|
}
|
|
1280
1363
|
|
|
1281
1364
|
getScrollNode(): ISymbioteNode | null {
|
|
1282
|
-
return this.
|
|
1365
|
+
return this.scrollHandle.getScrollNode();
|
|
1283
1366
|
}
|
|
1284
1367
|
|
|
1285
1368
|
recordInteraction(): void {
|
|
@@ -1291,11 +1374,11 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
1291
1374
|
const target = this.isHorizontal
|
|
1292
1375
|
? { x: clamped, y: EMPTY_OFFSET }
|
|
1293
1376
|
: { x: EMPTY_OFFSET, y: clamped };
|
|
1294
|
-
if (this.
|
|
1377
|
+
if (this.scrollNode !== null) {
|
|
1295
1378
|
dlog(
|
|
1296
1379
|
`Angular VirtualizedList scrollTo offset=${clamped} animated=${animated} (horizontal=${this.isHorizontal})`,
|
|
1297
1380
|
);
|
|
1298
|
-
this.
|
|
1381
|
+
this.scrollHandle.scrollTo({ x: target.x, y: target.y, animated });
|
|
1299
1382
|
return;
|
|
1300
1383
|
}
|
|
1301
1384
|
dlog(`Angular VirtualizedList scrollTo offset=${clamped} pending-ref`);
|