@symbiote-native/angular 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/babel-lower-host-primitives.cjs +636 -0
- package/babel-register-composed.cjs +21 -3
- package/build/angular/anchor-host-registry.js +3 -1
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/bootstrap.js +1 -1
- package/build/angular/bootstrap.js.map +1 -1
- package/build/angular/components/activity-indicator/index.android.js +7 -2
- package/build/angular/components/activity-indicator/index.android.js.map +1 -1
- package/build/angular/components/activity-indicator/index.ios.js +3 -1
- package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
- package/build/angular/components/activity-indicator/shared.d.ts +1 -1
- package/build/angular/components/activity-indicator/shared.js +1 -1
- package/build/angular/components/activity-indicator/shared.js.map +1 -1
- package/build/angular/components/button.d.ts +3 -0
- package/build/angular/components/button.js +22 -5
- package/build/angular/components/button.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +1 -1
- package/build/angular/components/flat-list/index.js +47 -14
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image/index.android.js +4 -10
- package/build/angular/components/image/index.android.js.map +1 -1
- package/build/angular/components/image/index.ios.js +4 -10
- package/build/angular/components/image/index.ios.js.map +1 -1
- package/build/angular/components/image/shared.d.ts +4 -4
- package/build/angular/components/image/shared.js +51 -33
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/image-background.d.ts +3 -0
- package/build/angular/components/image-background.js +32 -9
- package/build/angular/components/image-background.js.map +1 -1
- package/build/angular/components/input-accessory-view/index.d.ts +2 -1
- package/build/angular/components/input-accessory-view/index.js +18 -16
- package/build/angular/components/input-accessory-view/index.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +3 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +56 -19
- 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 +21 -10
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable/index.d.ts +10 -5
- package/build/angular/components/pressable/index.js +61 -16
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control/index.d.ts +2 -1
- package/build/angular/components/refresh-control/index.js +13 -9
- package/build/angular/components/refresh-control/index.js.map +1 -1
- package/build/angular/components/safe-area-view/index.d.ts +3 -1
- package/build/angular/components/safe-area-view/index.js +22 -20
- package/build/angular/components/safe-area-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/index.android.js +44 -14
- package/build/angular/components/scroll-view/index.android.js.map +1 -1
- package/build/angular/components/scroll-view/index.ios.js +13 -5
- package/build/angular/components/scroll-view/index.ios.js.map +1 -1
- package/build/angular/components/scroll-view/projection.d.ts +32 -0
- package/build/angular/components/scroll-view/projection.js +212 -47
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.d.ts +2 -0
- package/build/angular/components/scroll-view/shared.js +48 -11
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.js +7 -3
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +9 -1
- package/build/angular/components/section-list/index.js +72 -18
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/switch/index.android.js +23 -9
- package/build/angular/components/switch/index.android.js.map +1 -1
- package/build/angular/components/switch/index.ios.js +23 -9
- package/build/angular/components/switch/index.ios.js.map +1 -1
- package/build/angular/components/switch/shared.d.ts +1 -1
- package/build/angular/components/switch/shared.js +5 -2
- package/build/angular/components/switch/shared.js.map +1 -1
- package/build/angular/components/text-input.d.ts +8 -2
- package/build/angular/components/text-input.js +79 -34
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +36 -7
- package/build/angular/components/touchable/index.js +280 -72
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +2 -1
- package/build/angular/components/touchable-native-feedback/index.js +19 -17
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +4 -0
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +8 -2
- package/build/angular/components/virtualized-list/index.js +207 -61
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +21 -1
- package/build/angular/components/virtualized-section-list/index.js +100 -22
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +2 -2
- package/build/angular/components.js +1 -1
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.d.ts +11 -2
- package/build/angular/create-portal/index.js +42 -12
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +3 -1
- package/build/angular/descriptor-to-angular/index.js.map +1 -1
- package/build/angular/diagnostics.d.ts +33 -0
- package/build/angular/diagnostics.js +82 -0
- package/build/angular/diagnostics.js.map +1 -0
- package/build/angular/gate-demand.d.ts +37 -0
- package/build/angular/gate-demand.js +84 -0
- package/build/angular/gate-demand.js.map +1 -0
- package/build/angular/host-instance/index.js +1 -1
- package/build/angular/host-instance/index.js.map +1 -1
- package/build/angular/index.d.ts +6 -2
- package/build/angular/index.js +16 -1
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +1 -0
- package/build/angular/modules/animated/create-animated-component.js +41 -18
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/app-registry/index.js.map +1 -1
- package/build/angular/modules/status-bar/index.js +1 -1
- package/build/angular/modules/status-bar/index.js.map +1 -1
- package/build/angular/primitives/index.d.ts +31 -2
- package/build/angular/primitives/index.js +81 -3
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +9 -1
- package/build/angular/primitives/shared.js +49 -3
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/register.d.ts +1 -0
- package/build/angular/register.js +35 -0
- package/build/angular/register.js.map +1 -0
- package/build/angular/render/index.js +51 -6
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.d.ts +1 -0
- package/build/angular/renderer/index.js +157 -21
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/services/color-scheme.service.js.map +1 -1
- package/build/angular/services/window-dimensions.service.js +1 -1
- package/build/angular/services/window-dimensions.service.js.map +1 -1
- package/build/angular/state-style.d.ts +9 -0
- package/build/angular/state-style.js +39 -0
- package/build/angular/state-style.js.map +1 -0
- package/metro-transformer.cjs +69 -0
- package/package.json +27 -8
- package/src/anchor-host-registry.ts +3 -1
- package/src/bootstrap.ts +4 -1
- package/src/components/activity-indicator/index.android.ts +7 -2
- package/src/components/activity-indicator/index.ios.ts +3 -1
- package/src/components/activity-indicator/shared.ts +32 -13
- package/src/components/button.ts +36 -6
- package/src/components/flat-list/index.ts +91 -29
- package/src/components/image/index.android.ts +9 -6
- package/src/components/image/index.ios.ts +9 -6
- package/src/components/image/shared.ts +110 -45
- package/src/components/image-background.ts +47 -10
- package/src/components/input-accessory-view/index.ts +41 -18
- package/src/components/keyboard-avoiding-view/index.ts +76 -22
- package/src/components/modal/index.ts +39 -12
- package/src/components/pressable/index.ts +121 -31
- package/src/components/refresh-control/index.ts +36 -13
- package/src/components/safe-area-view/index.ts +44 -19
- package/src/components/scroll-view/index.android.ts +31 -9
- package/src/components/scroll-view/index.ios.ts +17 -4
- package/src/components/scroll-view/projection.ts +290 -63
- package/src/components/scroll-view/shared.ts +124 -30
- package/src/components/scroll-view/sticky-header.ts +25 -8
- package/src/components/section-list/index.ts +79 -19
- package/src/components/switch/index.android.ts +18 -7
- package/src/components/switch/index.ios.ts +18 -7
- package/src/components/switch/shared.ts +15 -4
- package/src/components/text-input.ts +119 -31
- package/src/components/touchable/index.ts +339 -72
- package/src/components/touchable-native-feedback/index.ts +50 -18
- package/src/components/virtualized-list/directives.ts +19 -5
- package/src/components/virtualized-list/index.ts +292 -72
- package/src/components/virtualized-section-list/directives.ts +6 -3
- package/src/components/virtualized-section-list/index.ts +164 -34
- package/src/components.ts +6 -1
- package/src/create-portal/index.ts +47 -11
- package/src/create-tunnel/index.ts +3 -1
- package/src/descriptor-to-angular/index.ts +29 -8
- package/src/diagnostics.ts +138 -0
- package/src/gate-demand.ts +109 -0
- package/src/host-instance/index.ts +9 -3
- package/src/index.ts +33 -2
- package/src/modules/animated/create-animated-component.ts +67 -19
- package/src/modules/app-registry/index.ts +3 -1
- package/src/modules/status-bar/index.ts +13 -4
- package/src/primitives/index.ts +73 -2
- package/src/primitives/shared.ts +70 -9
- package/src/register.ts +43 -0
- package/src/render/index.ts +59 -7
- package/src/renderer/index.ts +209 -37
- package/src/services/color-scheme.service.ts +3 -1
- package/src/services/window-dimensions.service.ts +11 -4
- package/src/state-style.ts +39 -0
|
@@ -46,7 +46,8 @@ export interface IVSectionContext<ItemT> {
|
|
|
46
46
|
// `<ng-template vSectionItem>` — one section item row. The static guard types the `let-` bindings.
|
|
47
47
|
@Directive({ selector: '[vSectionItem]', standalone: true })
|
|
48
48
|
export class VSectionItemDirective<ItemT = unknown> {
|
|
49
|
-
readonly templateRef =
|
|
49
|
+
readonly templateRef =
|
|
50
|
+
inject<TemplateRef<IVSectionItemContext<ItemT>>>(TemplateRef);
|
|
50
51
|
|
|
51
52
|
static ngTemplateContextGuard<T>(
|
|
52
53
|
_dir: VSectionItemDirective<T>,
|
|
@@ -59,7 +60,8 @@ export class VSectionItemDirective<ItemT = unknown> {
|
|
|
59
60
|
// `<ng-template vSectionHeader>` — rendered above each section's items (RN renderSectionHeader).
|
|
60
61
|
@Directive({ selector: '[vSectionHeader]', standalone: true })
|
|
61
62
|
export class VSectionHeaderDirective<ItemT = unknown> {
|
|
62
|
-
readonly templateRef =
|
|
63
|
+
readonly templateRef =
|
|
64
|
+
inject<TemplateRef<IVSectionContext<ItemT>>>(TemplateRef);
|
|
63
65
|
|
|
64
66
|
static ngTemplateContextGuard<T>(
|
|
65
67
|
_dir: VSectionHeaderDirective<T>,
|
|
@@ -72,7 +74,8 @@ export class VSectionHeaderDirective<ItemT = unknown> {
|
|
|
72
74
|
// `<ng-template vSectionFooter>` — rendered below each section's items (RN renderSectionFooter).
|
|
73
75
|
@Directive({ selector: '[vSectionFooter]', standalone: true })
|
|
74
76
|
export class VSectionFooterDirective<ItemT = unknown> {
|
|
75
|
-
readonly templateRef =
|
|
77
|
+
readonly templateRef =
|
|
78
|
+
inject<TemplateRef<IVSectionContext<ItemT>>>(TemplateRef);
|
|
76
79
|
|
|
77
80
|
static ngTemplateContextGuard<T>(
|
|
78
81
|
_dir: VSectionFooterDirective<T>,
|
|
@@ -64,7 +64,10 @@ import {
|
|
|
64
64
|
type IVListSeparatorContext,
|
|
65
65
|
} from '../virtualized-list';
|
|
66
66
|
import { VListOutletDirective } from '../virtualized-list/directives';
|
|
67
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
stableAnchorStyle,
|
|
69
|
+
SymbioteStyleInputDirective,
|
|
70
|
+
} from '../../primitives';
|
|
68
71
|
import {
|
|
69
72
|
VSectionFooterDirective,
|
|
70
73
|
VSectionHeaderDirective,
|
|
@@ -85,6 +88,13 @@ export {
|
|
|
85
88
|
VSectionItemDirective,
|
|
86
89
|
VSectionSeparatorDirective,
|
|
87
90
|
} from './directives';
|
|
91
|
+
import {
|
|
92
|
+
gateWanted,
|
|
93
|
+
injectGateDemandAbove,
|
|
94
|
+
provideGateDemand,
|
|
95
|
+
type IGateDemand,
|
|
96
|
+
type IGatedAccessibilityEvent,
|
|
97
|
+
} from '../../gate-demand';
|
|
88
98
|
export type { IVSectionContext, IVSectionItemContext } from './directives';
|
|
89
99
|
|
|
90
100
|
// The Angular VirtualizedSectionList prop surface. Mirrors React/Vue's IVirtualizedSectionListProps
|
|
@@ -92,9 +102,20 @@ export type { IVSectionContext, IVSectionItemContext } from './directives';
|
|
|
92
102
|
// those are the per-adapter children/render fields and become `<ng-template>` directives in Angular.
|
|
93
103
|
// Everything agnostic is the SAME surface as the
|
|
94
104
|
// React/Vue adapters, including the a11y/aria prop family forwarded through VirtualizedList.
|
|
95
|
-
export interface IVirtualizedSectionListProps<ItemT>
|
|
105
|
+
export interface IVirtualizedSectionListProps<ItemT>
|
|
106
|
+
extends IAccessibilityProps, IAriaProps {
|
|
96
107
|
sections: ReadonlyArray<ISection<ItemT>>;
|
|
97
108
|
keyExtractor?: (item: ItemT, index: number) => string;
|
|
109
|
+
// Fixed-layout fast path, FLAT like RN's: the SECTIONS array plus a flat entry index, where every
|
|
110
|
+
// section contributes two rows beyond its items (header, footer) and the caller accounts for them.
|
|
111
|
+
// A `({ section, index })` form would be our invention, not parity - `VirtualizedSectionList.js`
|
|
112
|
+
// has no getItemLayout code at all, the prop rides through `passThroughProps`; that shape is the
|
|
113
|
+
// community react-native-section-list-get-item-layout, layered on top. Without it a fast scroll
|
|
114
|
+
// outruns measurement and leaves blank windows.
|
|
115
|
+
getItemLayout?: (
|
|
116
|
+
data: ReadonlyArray<ISection<ItemT>> | null,
|
|
117
|
+
index: number,
|
|
118
|
+
) => { length: number; offset: number; index: number };
|
|
98
119
|
// Stick each section header to the top as the next section scrolls up. Routed to the inner
|
|
99
120
|
// VirtualizedList's stickyHeaderIndices. Defaults to `Platform.OS === 'ios'`; Android does not
|
|
100
121
|
// stick by default. Pass true/false to override.
|
|
@@ -146,7 +167,10 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
|
|
|
146
167
|
@Component({
|
|
147
168
|
selector: 'VirtualizedSectionList',
|
|
148
169
|
standalone: true,
|
|
149
|
-
|
|
170
|
+
viewProviders: [provideGateDemand(() => VirtualizedSectionList)],
|
|
171
|
+
hostDirectives: [
|
|
172
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
173
|
+
],
|
|
150
174
|
imports: [
|
|
151
175
|
VirtualizedList,
|
|
152
176
|
VListItemDirective,
|
|
@@ -163,6 +187,7 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
|
|
|
163
187
|
[getItem]="getEntry"
|
|
164
188
|
[getItemCount]="getEntryCount"
|
|
165
189
|
[keyExtractor]="entryKeyExtractor"
|
|
190
|
+
[getItemLayout]="entryItemLayout"
|
|
166
191
|
[stickyHeaderIndices]="stickyHeaderIndices"
|
|
167
192
|
[extraData]="extraData"
|
|
168
193
|
[inverted]="inverted"
|
|
@@ -207,8 +232,12 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
|
|
|
207
232
|
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
208
233
|
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
209
234
|
[accessibilityLanguage]="accessibilityLanguage"
|
|
210
|
-
[accessibilityRespondsToUserInteraction]="
|
|
211
|
-
|
|
235
|
+
[accessibilityRespondsToUserInteraction]="
|
|
236
|
+
accessibilityRespondsToUserInteraction
|
|
237
|
+
"
|
|
238
|
+
[accessibilityShowsLargeContentViewer]="
|
|
239
|
+
accessibilityShowsLargeContentViewer
|
|
240
|
+
"
|
|
212
241
|
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
213
242
|
(accessibilityAction)="resolvedOnAccessibilityAction?.($event)"
|
|
214
243
|
(accessibilityTap)="resolvedOnAccessibilityTap?.($event)"
|
|
@@ -246,7 +275,9 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
|
|
|
246
275
|
></ng-container>
|
|
247
276
|
}
|
|
248
277
|
@case ('section-separator') {
|
|
249
|
-
<ng-container
|
|
278
|
+
<ng-container
|
|
279
|
+
[vListOutlet]="sectionSeparatorDir?.templateRef"
|
|
280
|
+
></ng-container>
|
|
250
281
|
}
|
|
251
282
|
@case ('item') {
|
|
252
283
|
<ng-container
|
|
@@ -260,12 +291,16 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
|
|
|
260
291
|
<!-- list-level slots: forward the app's VirtualizedList directives to the inner list. -->
|
|
261
292
|
@if (listHeaderDir !== undefined) {
|
|
262
293
|
<ng-template vListHeader>
|
|
263
|
-
<ng-container
|
|
294
|
+
<ng-container
|
|
295
|
+
[vListOutlet]="listHeaderDir.templateRef"
|
|
296
|
+
></ng-container>
|
|
264
297
|
</ng-template>
|
|
265
298
|
}
|
|
266
299
|
@if (listFooterDir !== undefined) {
|
|
267
300
|
<ng-template vListFooter>
|
|
268
|
-
<ng-container
|
|
301
|
+
<ng-container
|
|
302
|
+
[vListOutlet]="listFooterDir.templateRef"
|
|
303
|
+
></ng-container>
|
|
269
304
|
</ng-template>
|
|
270
305
|
}
|
|
271
306
|
@if (listEmptyDir !== undefined) {
|
|
@@ -283,7 +318,9 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
|
|
|
283
318
|
>
|
|
284
319
|
<ng-container
|
|
285
320
|
[vListOutlet]="itemSeparatorDir.templateRef"
|
|
286
|
-
[vListOutletContext]="
|
|
321
|
+
[vListOutletContext]="
|
|
322
|
+
itemSeparatorContextOf(highlighted, leadingItem, trailingItem)
|
|
323
|
+
"
|
|
287
324
|
></ng-container>
|
|
288
325
|
</ng-template>
|
|
289
326
|
}
|
|
@@ -291,15 +328,26 @@ export type IVirtualizedSectionListInputs<ItemT> = Omit<
|
|
|
291
328
|
`,
|
|
292
329
|
})
|
|
293
330
|
export class VirtualizedSectionList<ItemT = unknown>
|
|
294
|
-
implements
|
|
331
|
+
implements
|
|
332
|
+
IVirtualizedSectionListInputs<ItemT>,
|
|
333
|
+
IVirtualizedSectionListHandle,
|
|
334
|
+
DoCheck
|
|
295
335
|
{
|
|
296
336
|
@Input({ required: true }) sections!: ReadonlyArray<ISection<ItemT>>;
|
|
297
337
|
@Input() keyExtractor?: (item: ItemT, index: number) => string;
|
|
338
|
+
@Input() getItemLayout?: (
|
|
339
|
+
data: ReadonlyArray<ISection<ItemT>> | null,
|
|
340
|
+
index: number,
|
|
341
|
+
) => { length: number; offset: number; index: number };
|
|
298
342
|
@Input() stickySectionHeadersEnabled?: boolean;
|
|
299
343
|
@Input() extraData?: unknown;
|
|
300
|
-
@Output() readonly endReached = new EventEmitter<{
|
|
344
|
+
@Output() readonly endReached = new EventEmitter<{
|
|
345
|
+
distanceFromEnd: number;
|
|
346
|
+
}>();
|
|
301
347
|
@Input() onEndReachedThreshold?: number;
|
|
302
|
-
@Output() readonly startReached = new EventEmitter<{
|
|
348
|
+
@Output() readonly startReached = new EventEmitter<{
|
|
349
|
+
distanceFromStart: number;
|
|
350
|
+
}>();
|
|
303
351
|
@Input() onStartReachedThreshold?: number;
|
|
304
352
|
@Output() readonly refresh = new EventEmitter<void>();
|
|
305
353
|
@Input() refreshing?: boolean | null;
|
|
@@ -360,8 +408,10 @@ export class VirtualizedSectionList<ItemT = unknown>
|
|
|
360
408
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
361
409
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
362
410
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
363
|
-
@Input()
|
|
364
|
-
|
|
411
|
+
@Input()
|
|
412
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
413
|
+
@Input()
|
|
414
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
365
415
|
@Input() screenReaderFocusable?: boolean;
|
|
366
416
|
@Input() accessibilityViewIsModal?: boolean;
|
|
367
417
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -374,6 +424,16 @@ export class VirtualizedSectionList<ItemT = unknown>
|
|
|
374
424
|
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
375
425
|
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
376
426
|
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
427
|
+
|
|
428
|
+
// This wrapper binds the four gated accessibility events on the component it renders, which
|
|
429
|
+
// Angular forces to be unconditional and which would light that component's gates on every
|
|
430
|
+
// instance. It answers for them instead — see `gate-demand.ts`.
|
|
431
|
+
private readonly gateDemandAbove = injectGateDemandAbove();
|
|
432
|
+
|
|
433
|
+
wantsGate(name: IGatedAccessibilityEvent): boolean {
|
|
434
|
+
return gateWanted(this.gateDemandAbove, name, this[name]);
|
|
435
|
+
}
|
|
436
|
+
|
|
377
437
|
@Input() role?: IAriaProps['role'];
|
|
378
438
|
@Input() ariaLabel?: string;
|
|
379
439
|
@Input() ariaLabelledBy?: string;
|
|
@@ -393,20 +453,27 @@ export class VirtualizedSectionList<ItemT = unknown>
|
|
|
393
453
|
// The section cell templates the app authors (the Angular twin of renderItem /
|
|
394
454
|
// renderSectionHeader / renderSectionFooter / SectionSeparatorComponent), captured from projected
|
|
395
455
|
// <ng-template> content and stamped by the synthesized vListItem dispatch.
|
|
396
|
-
@ContentChild(VSectionItemDirective)
|
|
397
|
-
|
|
398
|
-
@ContentChild(
|
|
399
|
-
|
|
456
|
+
@ContentChild(VSectionItemDirective)
|
|
457
|
+
sectionItemDir?: VSectionItemDirective<ItemT>;
|
|
458
|
+
@ContentChild(VSectionHeaderDirective)
|
|
459
|
+
sectionHeaderDir?: VSectionHeaderDirective<ItemT>;
|
|
460
|
+
@ContentChild(VSectionFooterDirective)
|
|
461
|
+
sectionFooterDir?: VSectionFooterDirective<ItemT>;
|
|
462
|
+
@ContentChild(VSectionSeparatorDirective)
|
|
463
|
+
sectionSeparatorDir?: VSectionSeparatorDirective;
|
|
400
464
|
|
|
401
465
|
// list-level slots reuse VirtualizedList's own directives; VSL forwards them to the inner list.
|
|
402
466
|
@ContentChild(VListHeaderDirective) listHeaderDir?: VListHeaderDirective;
|
|
403
467
|
@ContentChild(VListFooterDirective) listFooterDir?: VListFooterDirective;
|
|
404
468
|
@ContentChild(VListEmptyDirective) listEmptyDir?: VListEmptyDirective;
|
|
405
|
-
@ContentChild(VListSeparatorDirective)
|
|
469
|
+
@ContentChild(VListSeparatorDirective)
|
|
470
|
+
itemSeparatorDir?: VListSeparatorDirective<ItemT>;
|
|
406
471
|
|
|
407
472
|
// The composed inner VirtualizedList. Its instance IS an IVirtualizedListHandle, so the section
|
|
408
473
|
// handle delegates straight to it. Available from ngAfterViewInit; reads lazily, no-ops pre-commit.
|
|
409
|
-
@ViewChild(VirtualizedList) private list?: VirtualizedList<
|
|
474
|
+
@ViewChild(VirtualizedList) private list?: VirtualizedList<
|
|
475
|
+
ISectionEntry<ItemT>
|
|
476
|
+
>;
|
|
410
477
|
|
|
411
478
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
412
479
|
// onto (see anchorHostStyle's doc comment) — NOT `list` above, which targets the real inner
|
|
@@ -439,7 +506,10 @@ export class VirtualizedSectionList<ItemT = unknown>
|
|
|
439
506
|
this.lastHasSectionSeparator = hasSectionSeparator;
|
|
440
507
|
|
|
441
508
|
const sections = this.sections ?? [];
|
|
442
|
-
const { entries, headerIndices } = flattenSections(
|
|
509
|
+
const { entries, headerIndices } = flattenSections(
|
|
510
|
+
sections,
|
|
511
|
+
hasSectionSeparator,
|
|
512
|
+
);
|
|
443
513
|
this.flatEntries = entries;
|
|
444
514
|
this.headerIndices = headerIndices;
|
|
445
515
|
|
|
@@ -458,42 +528,95 @@ export class VirtualizedSectionList<ItemT = unknown>
|
|
|
458
528
|
|
|
459
529
|
// The derived flat-stream accessors handed to the inner VirtualizedList. Stable arrow fields so
|
|
460
530
|
// the bindings keep a constant identity; they read the live flatEntries.
|
|
461
|
-
getEntry = (_source: unknown, index: number): ISectionEntry<ItemT> =>
|
|
531
|
+
getEntry = (_source: unknown, index: number): ISectionEntry<ItemT> =>
|
|
532
|
+
this.flatEntries[index];
|
|
462
533
|
getEntryCount = (): number => this.flatEntries.length;
|
|
463
534
|
entryKeyExtractor = (entry: ISectionEntry<ItemT>, index: number): string =>
|
|
464
535
|
sectionEntryKey(entry, index, this.keyExtractor);
|
|
465
536
|
|
|
537
|
+
// Hand the callback `sections`, not the entries: RN's inner VirtualizedList gets
|
|
538
|
+
// `data={this.props.sections}` (VirtualizedSectionList.js:216) while ours streams the FLATTENED
|
|
539
|
+
// entries, so the same user code would otherwise see a different argument here than on RN.
|
|
540
|
+
//
|
|
541
|
+
// UPSTREAM-DIVERGENCE(react-native): the flat INDEX matches RN's (two rows per section, header
|
|
542
|
+
// and footer) only while the vSectionSeparator template is unset. With it, flattenSections emits
|
|
543
|
+
// an extra 'section-separator' row per boundary that RN renders inside the neighbouring cell, so
|
|
544
|
+
// indices shift by one per boundary from the second section on. Deliberate - that row is how this
|
|
545
|
+
// adapter paints the separator; a caller combining the two must account for it.
|
|
546
|
+
//
|
|
547
|
+
// Cached on the input's own identity, like resolvedStyle above: the inner VirtualizedList folds
|
|
548
|
+
// getItemLayout into its ngDoCheck dedup array, so a wrapper allocated per getter read would
|
|
549
|
+
// re-run its whole recompute every CD.
|
|
550
|
+
private cachedEntryItemLayout?: (
|
|
551
|
+
data: unknown,
|
|
552
|
+
index: number,
|
|
553
|
+
) => { length: number; offset: number; index: number };
|
|
554
|
+
private lastGetItemLayout?: IVirtualizedSectionListProps<ItemT>['getItemLayout'];
|
|
555
|
+
get entryItemLayout():
|
|
556
|
+
| ((
|
|
557
|
+
data: unknown,
|
|
558
|
+
index: number,
|
|
559
|
+
) => { length: number; offset: number; index: number })
|
|
560
|
+
| undefined {
|
|
561
|
+
if (this.getItemLayout !== this.lastGetItemLayout) {
|
|
562
|
+
const getItemLayout = this.getItemLayout;
|
|
563
|
+
this.lastGetItemLayout = getItemLayout;
|
|
564
|
+
this.cachedEntryItemLayout =
|
|
565
|
+
getItemLayout === undefined
|
|
566
|
+
? undefined
|
|
567
|
+
: (
|
|
568
|
+
_entries: unknown,
|
|
569
|
+
index: number,
|
|
570
|
+
): { length: number; offset: number; index: number } =>
|
|
571
|
+
getItemLayout(this.sections, index);
|
|
572
|
+
}
|
|
573
|
+
return this.cachedEntryItemLayout;
|
|
574
|
+
}
|
|
575
|
+
|
|
466
576
|
// Adapts each @Output() into the plain callback VirtualizedList's own @Input() still wants,
|
|
467
577
|
// gated on `.observed` so an unlistened event forwards `undefined` — the same "nobody cares"
|
|
468
578
|
// contract the old @Input() callback passthrough had (e.g. onRefresh presence gates whether a
|
|
469
579
|
// RefreshControl is built downstream; see the Vue twin's `listens('onRefresh')` gate).
|
|
470
|
-
get resolvedOnEndReached():
|
|
471
|
-
|
|
580
|
+
get resolvedOnEndReached():
|
|
581
|
+
((info: { distanceFromEnd: number }) => void) | undefined {
|
|
582
|
+
return this.endReached.observed
|
|
583
|
+
? info => this.endReached.emit(info)
|
|
584
|
+
: undefined;
|
|
472
585
|
}
|
|
473
586
|
|
|
474
|
-
get resolvedOnStartReached():
|
|
475
|
-
|
|
587
|
+
get resolvedOnStartReached():
|
|
588
|
+
((info: { distanceFromStart: number }) => void) | undefined {
|
|
589
|
+
return this.startReached.observed
|
|
590
|
+
? info => this.startReached.emit(info)
|
|
591
|
+
: undefined;
|
|
476
592
|
}
|
|
477
593
|
|
|
478
594
|
get resolvedOnRefresh(): (() => void) | undefined {
|
|
479
595
|
return this.refresh.observed ? () => this.refresh.emit() : undefined;
|
|
480
596
|
}
|
|
481
597
|
|
|
482
|
-
get resolvedOnAccessibilityAction():
|
|
598
|
+
get resolvedOnAccessibilityAction():
|
|
599
|
+
((event: ISymbioteEvent) => void) | undefined {
|
|
483
600
|
return this.accessibilityAction.observed
|
|
484
601
|
? event => this.accessibilityAction.emit(event)
|
|
485
602
|
: undefined;
|
|
486
603
|
}
|
|
487
604
|
|
|
488
|
-
get resolvedOnAccessibilityTap():
|
|
489
|
-
|
|
605
|
+
get resolvedOnAccessibilityTap():
|
|
606
|
+
((event: ISymbioteEvent) => void) | undefined {
|
|
607
|
+
return this.accessibilityTap.observed
|
|
608
|
+
? event => this.accessibilityTap.emit(event)
|
|
609
|
+
: undefined;
|
|
490
610
|
}
|
|
491
611
|
|
|
492
612
|
get resolvedOnMagicTap(): ((event: ISymbioteEvent) => void) | undefined {
|
|
493
|
-
return this.magicTap.observed
|
|
613
|
+
return this.magicTap.observed
|
|
614
|
+
? event => this.magicTap.emit(event)
|
|
615
|
+
: undefined;
|
|
494
616
|
}
|
|
495
617
|
|
|
496
|
-
get resolvedOnAccessibilityEscape():
|
|
618
|
+
get resolvedOnAccessibilityEscape():
|
|
619
|
+
((event: ISymbioteEvent) => void) | undefined {
|
|
497
620
|
return this.accessibilityEscape.observed
|
|
498
621
|
? event => this.accessibilityEscape.emit(event)
|
|
499
622
|
: undefined;
|
|
@@ -516,7 +639,10 @@ export class VirtualizedSectionList<ItemT = unknown>
|
|
|
516
639
|
return { $implicit: value.section, section: value.section };
|
|
517
640
|
}
|
|
518
641
|
|
|
519
|
-
itemContextOf(
|
|
642
|
+
itemContextOf(
|
|
643
|
+
value: unknown,
|
|
644
|
+
separators: ISeparators,
|
|
645
|
+
): IVSectionItemContext<ItemT> | undefined {
|
|
520
646
|
if (!this.isEntry(value) || value.kind !== 'item') return undefined;
|
|
521
647
|
return {
|
|
522
648
|
$implicit: value.item,
|
|
@@ -539,8 +665,12 @@ export class VirtualizedSectionList<ItemT = unknown>
|
|
|
539
665
|
return {
|
|
540
666
|
$implicit: highlighted === true,
|
|
541
667
|
highlighted: highlighted === true,
|
|
542
|
-
leadingItem: this.isEntry(leadingItem)
|
|
543
|
-
|
|
668
|
+
leadingItem: this.isEntry(leadingItem)
|
|
669
|
+
? unwrapEntryItem(leadingItem)
|
|
670
|
+
: undefined,
|
|
671
|
+
trailingItem: this.isEntry(trailingItem)
|
|
672
|
+
? unwrapEntryItem(trailingItem)
|
|
673
|
+
: undefined,
|
|
544
674
|
};
|
|
545
675
|
}
|
|
546
676
|
|
package/src/components.ts
CHANGED
|
@@ -112,7 +112,10 @@ export type {
|
|
|
112
112
|
IVSectionItemContext,
|
|
113
113
|
} from './components/virtualized-section-list';
|
|
114
114
|
export { SectionList } from './components/section-list';
|
|
115
|
-
export type {
|
|
115
|
+
export type {
|
|
116
|
+
ISectionListHandle,
|
|
117
|
+
ISectionListProps,
|
|
118
|
+
} from './components/section-list';
|
|
116
119
|
|
|
117
120
|
// Re-export primitive hosts so composed components can import them from the public barrel too.
|
|
118
121
|
export {
|
|
@@ -127,6 +130,8 @@ export {
|
|
|
127
130
|
HorizontalScrollContentView,
|
|
128
131
|
TextInputHost,
|
|
129
132
|
MultilineTextInputHost,
|
|
133
|
+
ManagedTextInputHost,
|
|
134
|
+
ManagedMultilineTextInputHost,
|
|
130
135
|
SwitchHost,
|
|
131
136
|
ActivityIndicatorHost,
|
|
132
137
|
SafeAreaViewHost,
|
|
@@ -20,16 +20,23 @@
|
|
|
20
20
|
//
|
|
21
21
|
// The safe, fully-public-API mechanism: create the embedded view DIRECTLY inside a
|
|
22
22
|
// ViewContainerRef anchored at the destination, so there is nothing to move at all. That
|
|
23
|
-
// ViewContainerRef comes from `PortalOutletDirective`, a
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// `
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
23
|
+
// ViewContainerRef comes from `PortalOutletDirective`, exported to a template variable with the
|
|
24
|
+
// same idiom `#form="ngForm"` uses. This also replaces the `isSymbioteNode` runtime guard
|
|
25
|
+
// React/Vue need (there `to` is an arbitrary JS value until validated): here `to` is typed as
|
|
26
|
+
// `PortalOutletDirective` — the only way to construct one is Angular's own template compiler
|
|
27
|
+
// resolving a template reference variable, so `strictTemplates` rejects anything else at compile
|
|
28
|
+
// time and there is nothing left to guard at runtime.
|
|
29
|
+
//
|
|
30
|
+
// THE MARKER GOES ON AN `<ng-container>` INSIDE THE TARGET, never on the target element. A
|
|
31
|
+
// ViewContainerRef anchors AT its host and `createEmbeddedView` inserts after that anchor, so
|
|
32
|
+
// `<View portalOutlet>` delivers a SIBLING where React's `createPortal(node, host)` and Vue's
|
|
33
|
+
// `<Teleport to>` deliver a child. Device-reported 2026-09-02: a card portaled into an absolutely
|
|
34
|
+
// positioned overlay laid out in the scroll flow, since the overlay cannot centre a node that is
|
|
35
|
+
// not in it.
|
|
30
36
|
|
|
31
37
|
import {
|
|
32
38
|
Directive,
|
|
39
|
+
ElementRef,
|
|
33
40
|
inject,
|
|
34
41
|
Input,
|
|
35
42
|
TemplateRef,
|
|
@@ -38,13 +45,40 @@ import {
|
|
|
38
45
|
type OnChanges,
|
|
39
46
|
type OnDestroy,
|
|
40
47
|
} from '@angular/core';
|
|
48
|
+
import { isAnchor, isSymbioteNode } from '@symbiote-native/engine';
|
|
41
49
|
|
|
42
50
|
/** Marks the destination for `*portal` — place it on whichever already-mounted host should
|
|
43
|
-
* paint the portaled content
|
|
44
|
-
* export it to a template variable and pass that
|
|
45
|
-
|
|
51
|
+
* paint the portaled content — on an `<ng-container>` INSIDE that host, never on the host
|
|
52
|
+
* element itself (see the file header) — then export it to a template variable and pass that
|
|
53
|
+
* variable as `*portal`'s target:
|
|
54
|
+
*
|
|
55
|
+
* ```html
|
|
56
|
+
* <View class="overlay-host">
|
|
57
|
+
* <ng-container portalOutlet #overlayHost="portalOutlet"></ng-container>
|
|
58
|
+
* </View>
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
@Directive({
|
|
62
|
+
selector: '[portalOutlet]',
|
|
63
|
+
standalone: true,
|
|
64
|
+
exportAs: 'portalOutlet',
|
|
65
|
+
})
|
|
46
66
|
export class PortalOutletDirective {
|
|
47
67
|
readonly viewContainerRef = inject(ViewContainerRef);
|
|
68
|
+
|
|
69
|
+
constructor() {
|
|
70
|
+
// The wrong placement commits a valid tree that simply differs from React's, so nothing
|
|
71
|
+
// downstream can catch it. It has to fail where it is written.
|
|
72
|
+
const host: unknown = inject(ElementRef).nativeElement;
|
|
73
|
+
if (!isSymbioteNode(host) || !isAnchor(host)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'portalOutlet must sit on an <ng-container> inside the target host, not on the host ' +
|
|
76
|
+
'element: a ViewContainerRef anchored at an element inserts AFTER it, so the ported ' +
|
|
77
|
+
'content would become the host\'s sibling. Write <View class="…">' +
|
|
78
|
+
'<ng-container portalOutlet #x="portalOutlet"></ng-container></View>.',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
48
82
|
}
|
|
49
83
|
|
|
50
84
|
/** `*portal="overlayHost"` — renders the host element into whichever `PortalOutletDirective`
|
|
@@ -59,7 +93,9 @@ export class PortalDirective implements OnChanges, OnDestroy {
|
|
|
59
93
|
|
|
60
94
|
ngOnChanges(): void {
|
|
61
95
|
this.viewRef?.destroy();
|
|
62
|
-
this.viewRef = this.portal.viewContainerRef.createEmbeddedView(
|
|
96
|
+
this.viewRef = this.portal.viewContainerRef.createEmbeddedView(
|
|
97
|
+
this.templateRef,
|
|
98
|
+
);
|
|
63
99
|
}
|
|
64
100
|
|
|
65
101
|
ngOnDestroy(): void {
|
|
@@ -109,7 +109,9 @@ export class TunnelOut implements OnInit, OnDestroy {
|
|
|
109
109
|
private stopEffect: (() => void) | undefined;
|
|
110
110
|
|
|
111
111
|
ngOnInit(): void {
|
|
112
|
-
const ref = effect(() => this.sync(this.tunnel.entries()), {
|
|
112
|
+
const ref = effect(() => this.sync(this.tunnel.entries()), {
|
|
113
|
+
injector: this.injector,
|
|
114
|
+
});
|
|
113
115
|
this.stopEffect = () => ref.destroy();
|
|
114
116
|
}
|
|
115
117
|
|
|
@@ -15,7 +15,10 @@ import {
|
|
|
15
15
|
type OnDestroy,
|
|
16
16
|
type SimpleChanges,
|
|
17
17
|
} from '@angular/core';
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
IDescriptor,
|
|
20
|
+
IDescriptorChild,
|
|
21
|
+
} from '@symbiote-native/components';
|
|
19
22
|
|
|
20
23
|
type IRenderedChild = IRenderedElement | IRenderedText;
|
|
21
24
|
|
|
@@ -43,7 +46,8 @@ export class DescriptorOutlet implements OnChanges, OnDestroy {
|
|
|
43
46
|
@Input({ required: true }) node!: IDescriptor;
|
|
44
47
|
|
|
45
48
|
private readonly renderer = inject(Renderer2);
|
|
46
|
-
private readonly hostNode =
|
|
49
|
+
private readonly hostNode =
|
|
50
|
+
inject<ElementRef<unknown>>(ElementRef).nativeElement;
|
|
47
51
|
private rendered: IRenderedElement | undefined;
|
|
48
52
|
|
|
49
53
|
ngOnChanges(changes: SimpleChanges): void {
|
|
@@ -57,7 +61,10 @@ export class DescriptorOutlet implements OnChanges, OnDestroy {
|
|
|
57
61
|
this.rendered = undefined;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
private patchRoot(
|
|
64
|
+
private patchRoot(
|
|
65
|
+
rendered: IRenderedElement | undefined,
|
|
66
|
+
next: IDescriptor,
|
|
67
|
+
): IRenderedElement {
|
|
61
68
|
if (rendered === undefined) {
|
|
62
69
|
const created = this.createElement(next);
|
|
63
70
|
this.renderer.appendChild(this.hostNode, created.node);
|
|
@@ -67,7 +74,9 @@ export class DescriptorOutlet implements OnChanges, OnDestroy {
|
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
private createChild(child: IDescriptorChild): IRenderedChild {
|
|
70
|
-
return typeof child === 'string'
|
|
77
|
+
return typeof child === 'string'
|
|
78
|
+
? this.createText(child)
|
|
79
|
+
: this.createElement(child);
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
private createText(value: string): IRenderedText {
|
|
@@ -127,12 +136,19 @@ export class DescriptorOutlet implements OnChanges, OnDestroy {
|
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
this.patchProps(rendered, next.props);
|
|
130
|
-
rendered.children = this.patchChildren(
|
|
139
|
+
rendered.children = this.patchChildren(
|
|
140
|
+
rendered.node,
|
|
141
|
+
rendered.children,
|
|
142
|
+
next.children,
|
|
143
|
+
);
|
|
131
144
|
rendered.descriptor = next;
|
|
132
145
|
return rendered;
|
|
133
146
|
}
|
|
134
147
|
|
|
135
|
-
private patchProps(
|
|
148
|
+
private patchProps(
|
|
149
|
+
rendered: IRenderedElement,
|
|
150
|
+
nextProps: Record<string, unknown>,
|
|
151
|
+
): void {
|
|
136
152
|
const prevProps = rendered.props;
|
|
137
153
|
for (const key of Object.keys(prevProps)) {
|
|
138
154
|
if (!(key in nextProps)) {
|
|
@@ -173,13 +189,18 @@ export class DescriptorOutlet implements OnChanges, OnDestroy {
|
|
|
173
189
|
|
|
174
190
|
for (let i = common; i < renderedChildren.length; i++) {
|
|
175
191
|
const rendered = renderedChildren[i];
|
|
176
|
-
if (rendered !== undefined)
|
|
192
|
+
if (rendered !== undefined)
|
|
193
|
+
this.renderer.removeChild(parent, rendered.node);
|
|
177
194
|
}
|
|
178
195
|
|
|
179
196
|
return nextRendered;
|
|
180
197
|
}
|
|
181
198
|
|
|
182
|
-
private replaceChild(
|
|
199
|
+
private replaceChild(
|
|
200
|
+
parent: unknown,
|
|
201
|
+
oldChild: unknown,
|
|
202
|
+
newChild: unknown,
|
|
203
|
+
): void {
|
|
183
204
|
this.renderer.insertBefore(parent, newChild, oldChild);
|
|
184
205
|
this.renderer.removeChild(parent, oldChild);
|
|
185
206
|
}
|