@ng-native/components 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/content-container.d.ts +25 -0
  2. package/dist/content-container.js +35 -0
  3. package/dist/content-container.js.map +1 -0
  4. package/dist/gestures-web.d.ts +12 -0
  5. package/dist/gestures-web.js +32 -0
  6. package/dist/gestures-web.js.map +1 -0
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/keyboard-avoiding-view.d.ts +6 -1
  10. package/dist/keyboard-avoiding-view.js +23 -6
  11. package/dist/keyboard-avoiding-view.js.map +1 -1
  12. package/dist/pressable.d.ts +33 -4
  13. package/dist/pressable.js +55 -3
  14. package/dist/pressable.js.map +1 -1
  15. package/dist/reanimated-web.d.ts +26 -0
  16. package/dist/reanimated-web.js +59 -0
  17. package/dist/reanimated-web.js.map +1 -0
  18. package/dist/refresh-control.d.ts +4 -1
  19. package/dist/refresh-control.js +4 -1
  20. package/dist/refresh-control.js.map +1 -1
  21. package/dist/scroll-view.d.ts +17 -1
  22. package/dist/scroll-view.js +59 -6
  23. package/dist/scroll-view.js.map +1 -1
  24. package/dist/section-list.d.ts +14 -2
  25. package/dist/section-list.js +20 -4
  26. package/dist/section-list.js.map +1 -1
  27. package/dist/sticky-headers.d.ts +6 -0
  28. package/dist/sticky-headers.js +7 -2
  29. package/dist/sticky-headers.js.map +1 -1
  30. package/dist/switch.d.ts +11 -1
  31. package/dist/switch.js +16 -3
  32. package/dist/switch.js.map +1 -1
  33. package/dist/text.d.ts +10 -1
  34. package/dist/text.js +15 -1
  35. package/dist/text.js.map +1 -1
  36. package/dist/touchable-opacity.d.ts +2 -1
  37. package/dist/touchable-opacity.js +3 -0
  38. package/dist/touchable-opacity.js.map +1 -1
  39. package/dist/view-base.d.ts +28 -18
  40. package/dist/view-base.js +37 -22
  41. package/dist/view-base.js.map +1 -1
  42. package/dist/virtual-list.d.ts +49 -5
  43. package/dist/virtual-list.js +129 -42
  44. package/dist/virtual-list.js.map +1 -1
  45. package/dist/zero-size-warning.d.ts +30 -0
  46. package/dist/zero-size-warning.js +58 -0
  47. package/dist/zero-size-warning.js.map +1 -0
  48. package/package.json +7 -4
@@ -1,4 +1,6 @@
1
1
  import { TemplateRef } from '@angular/core';
2
+ import type { KeyboardShouldPersistTaps } from './keyboard-taps.ts';
3
+ import { type VirtualListPadding } from './virtual-list.ts';
2
4
  import * as i0 from "@angular/core";
3
5
  /** A section as RN's SectionList takes it: its rows in `data`, plus whatever else it carries. */
4
6
  export interface SectionListSection<T> {
@@ -132,8 +134,14 @@ type Height<A extends unknown[]> = number | ((...args: A) => number);
132
134
  * It defaults to on for iOS and off for Android, as RN's does. `listHeader` and `listFooter`
133
135
  * content and a `<refresh-control>` pass through to the list.
134
136
  *
137
+ * `contentPadding` and `keyboardShouldPersistTaps` pass through to the list as `<virtual-list>`
138
+ * takes them.
139
+ *
135
140
  * ponytail: no `horizontal` or `inverted`, no viewability events, and no `highlighted` on a
136
- * separator. The host is a plain view with the list filling it, where RN's host is the scroll view.
141
+ * separator. No `keyExtractor` or `maintainVisibleContentPosition` either: rows are fixed height,
142
+ * so there is no measured height for a key to carry across an insert. The host is a plain view
143
+ * with the list filling it, where RN's host is the scroll view, so scroll-view props are not
144
+ * taken.
137
145
  */
138
146
  export declare class SectionList<T, S extends SectionListSection<T> = SectionListSection<T>> {
139
147
  readonly sections: import("@angular/core").InputSignal<readonly S[]>;
@@ -145,6 +153,10 @@ export declare class SectionList<T, S extends SectionListSection<T> = SectionLis
145
153
  readonly stickySectionHeadersEnabled: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
146
154
  readonly overscan: import("@angular/core").InputSignalWithTransform<number, unknown>;
147
155
  readonly endReachedThreshold: import("@angular/core").InputSignalWithTransform<number, unknown>;
156
+ /** Space around the rows, inside the scrolling content. See `<virtual-list>`. */
157
+ readonly contentPadding: import("@angular/core").InputSignal<VirtualListPadding>;
158
+ /** What a tap in the list does while a text input has the keyboard up. See `ScrollView`. */
159
+ readonly keyboardShouldPersistTaps: import("@angular/core").InputSignal<KeyboardShouldPersistTaps>;
148
160
  /** The end of the last section is within `endReachedThreshold`. See `<virtual-list>`. */
149
161
  readonly endReached: import("@angular/core").OutputEmitterRef<{
150
162
  distanceFromEnd: number;
@@ -178,6 +190,6 @@ export declare class SectionList<T, S extends SectionListSection<T> = SectionLis
178
190
  animated?: boolean;
179
191
  }): void;
180
192
  static ɵfac: i0.ɵɵFactoryDeclaration<SectionList<any, any>, never>;
181
- static ɵcmp: i0.ɵɵComponentDeclaration<SectionList<any, any>, "section-list", ["sectionList"], { "sections": { "alias": "sections"; "required": true; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": true; "isSignal": true; }; "sectionHeaderHeight": { "alias": "sectionHeaderHeight"; "required": false; "isSignal": true; }; "sectionFooterHeight": { "alias": "sectionFooterHeight"; "required": false; "isSignal": true; }; "stickySectionHeadersEnabled": { "alias": "stickySectionHeadersEnabled"; "required": false; "isSignal": true; }; "overscan": { "alias": "overscan"; "required": false; "isSignal": true; }; "endReachedThreshold": { "alias": "endReachedThreshold"; "required": false; "isSignal": true; }; }, { "endReached": "endReached"; }, ["header", "footer", "item", "separator", "edge"], ["refresh-control", "[listHeader]", "*", "[listFooter]"], true, never>;
193
+ static ɵcmp: i0.ɵɵComponentDeclaration<SectionList<any, any>, "section-list", ["sectionList"], { "sections": { "alias": "sections"; "required": true; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": true; "isSignal": true; }; "sectionHeaderHeight": { "alias": "sectionHeaderHeight"; "required": false; "isSignal": true; }; "sectionFooterHeight": { "alias": "sectionFooterHeight"; "required": false; "isSignal": true; }; "stickySectionHeadersEnabled": { "alias": "stickySectionHeadersEnabled"; "required": false; "isSignal": true; }; "overscan": { "alias": "overscan"; "required": false; "isSignal": true; }; "endReachedThreshold": { "alias": "endReachedThreshold"; "required": false; "isSignal": true; }; "contentPadding": { "alias": "contentPadding"; "required": false; "isSignal": true; }; "keyboardShouldPersistTaps": { "alias": "keyboardShouldPersistTaps"; "required": false; "isSignal": true; }; }, { "endReached": "endReached"; }, ["header", "footer", "item", "separator", "edge"], ["refresh-control", "[listHeader]", "*", "[listFooter]"], true, never>;
182
194
  }
183
195
  export {};
@@ -140,8 +140,14 @@ const measure = (height, ...args) => typeof height === 'number' ? height : heigh
140
140
  * It defaults to on for iOS and off for Android, as RN's does. `listHeader` and `listFooter`
141
141
  * content and a `<refresh-control>` pass through to the list.
142
142
  *
143
+ * `contentPadding` and `keyboardShouldPersistTaps` pass through to the list as `<virtual-list>`
144
+ * takes them.
145
+ *
143
146
  * ponytail: no `horizontal` or `inverted`, no viewability events, and no `highlighted` on a
144
- * separator. The host is a plain view with the list filling it, where RN's host is the scroll view.
147
+ * separator. No `keyExtractor` or `maintainVisibleContentPosition` either: rows are fixed height,
148
+ * so there is no measured height for a key to carry across an insert. The host is a plain view
149
+ * with the list filling it, where RN's host is the scroll view, so scroll-view props are not
150
+ * taken.
145
151
  */
146
152
  export class SectionList {
147
153
  sections = input.required(/* @ts-ignore */
@@ -157,6 +163,12 @@ export class SectionList {
157
163
  stickySectionHeadersEnabled = input(undefined, { ...(ngDevMode ? { debugName: "stickySectionHeadersEnabled" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
158
164
  overscan = input(4, { ...(ngDevMode ? { debugName: "overscan" } : /* istanbul ignore next */ {}), transform: numberAttribute });
159
165
  endReachedThreshold = input(2, { ...(ngDevMode ? { debugName: "endReachedThreshold" } : /* istanbul ignore next */ {}), transform: numberAttribute });
166
+ /** Space around the rows, inside the scrolling content. See `<virtual-list>`. */
167
+ contentPadding = input(0, /* @ts-ignore */
168
+ ...(ngDevMode ? [{ debugName: "contentPadding" }] : /* istanbul ignore next */ []));
169
+ /** What a tap in the list does while a text input has the keyboard up. See `ScrollView`. */
170
+ keyboardShouldPersistTaps = input('never', /* @ts-ignore */
171
+ ...(ngDevMode ? [{ debugName: "keyboardShouldPersistTaps" }] : /* istanbul ignore next */ []));
160
172
  /** The end of the last section is within `endReachedThreshold`. See `<virtual-list>`. */
161
173
  endReached = output();
162
174
  header = contentChild(SectionHeader, /* @ts-ignore */
@@ -216,7 +228,7 @@ export class SectionList {
216
228
  this.list().scrollToIndex({ index, viewOffset, animated: options.animated });
217
229
  }
218
230
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SectionList, deps: [], target: i0.ɵɵFactoryTarget.Component });
219
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: SectionList, isStandalone: true, selector: "section-list", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: true, transformFunction: null }, sectionHeaderHeight: { classPropertyName: "sectionHeaderHeight", publicName: "sectionHeaderHeight", isSignal: true, isRequired: false, transformFunction: null }, sectionFooterHeight: { classPropertyName: "sectionFooterHeight", publicName: "sectionFooterHeight", isSignal: true, isRequired: false, transformFunction: null }, stickySectionHeadersEnabled: { classPropertyName: "stickySectionHeadersEnabled", publicName: "stickySectionHeadersEnabled", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, endReachedThreshold: { classPropertyName: "endReachedThreshold", publicName: "endReachedThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { endReached: "endReached" }, queries: [{ propertyName: "header", first: true, predicate: SectionHeader, descendants: true, isSignal: true }, { propertyName: "footer", first: true, predicate: SectionFooter, descendants: true, isSignal: true }, { propertyName: "item", first: true, predicate: SectionItem, descendants: true, isSignal: true }, { propertyName: "separator", first: true, predicate: SectionSeparator, descendants: true, isSignal: true }, { propertyName: "edge", first: true, predicate: SectionEdgeSeparator, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }], exportAs: ["sectionList"], ngImport: i0, template: `
231
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: SectionList, isStandalone: true, selector: "section-list", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: true, transformFunction: null }, sectionHeaderHeight: { classPropertyName: "sectionHeaderHeight", publicName: "sectionHeaderHeight", isSignal: true, isRequired: false, transformFunction: null }, sectionFooterHeight: { classPropertyName: "sectionFooterHeight", publicName: "sectionFooterHeight", isSignal: true, isRequired: false, transformFunction: null }, stickySectionHeadersEnabled: { classPropertyName: "stickySectionHeadersEnabled", publicName: "stickySectionHeadersEnabled", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, endReachedThreshold: { classPropertyName: "endReachedThreshold", publicName: "endReachedThreshold", isSignal: true, isRequired: false, transformFunction: null }, contentPadding: { classPropertyName: "contentPadding", publicName: "contentPadding", isSignal: true, isRequired: false, transformFunction: null }, keyboardShouldPersistTaps: { classPropertyName: "keyboardShouldPersistTaps", publicName: "keyboardShouldPersistTaps", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { endReached: "endReached" }, queries: [{ propertyName: "header", first: true, predicate: SectionHeader, descendants: true, isSignal: true }, { propertyName: "footer", first: true, predicate: SectionFooter, descendants: true, isSignal: true }, { propertyName: "item", first: true, predicate: SectionItem, descendants: true, isSignal: true }, { propertyName: "separator", first: true, predicate: SectionSeparator, descendants: true, isSignal: true }, { propertyName: "edge", first: true, predicate: SectionEdgeSeparator, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }], exportAs: ["sectionList"], ngImport: i0, template: `
220
232
  <virtual-list
221
233
  #list
222
234
  [items]="rows()"
@@ -224,6 +236,8 @@ export class SectionList {
224
236
  [stickyIndices]="stickyRows()"
225
237
  [overscan]="overscan()"
226
238
  [endReachedThreshold]="endReachedThreshold()"
239
+ [contentPadding]="contentPadding()"
240
+ [keyboardShouldPersistTaps]="keyboardShouldPersistTaps()"
227
241
  [style]="fill"
228
242
  (endReached)="endReached.emit($event)"
229
243
  >
@@ -279,7 +293,7 @@ export class SectionList {
279
293
  }
280
294
  <view listFooter><ng-content select="[listFooter]" /></view>
281
295
  </virtual-list>
282
- `, isInline: true, dependencies: [{ kind: "directive", type: TemplateSlot, selector: "[templateSlot]", inputs: ["templateSlot", "templateSlotContext"] }, { kind: "directive", type: View, selector: "view" }, { kind: "component", type: VirtualList, selector: "virtual-list", inputs: ["items", "itemHeight", "estimatedItemHeight", "keyExtractor", "itemType", "maintainVisibleContentPosition", "overscan", "endReachedThreshold", "horizontal", "inverted", "stickyIndices", "stickyHeader", "keyboardShouldPersistTaps", "itemVisiblePercentThreshold"], outputs: ["endReached", "viewableItemsChanged"], exportAs: ["virtualList"] }] });
296
+ `, isInline: true, dependencies: [{ kind: "directive", type: TemplateSlot, selector: "[templateSlot]", inputs: ["templateSlot", "templateSlotContext"] }, { kind: "directive", type: View, selector: "view" }, { kind: "component", type: VirtualList, selector: "virtual-list", inputs: ["items", "itemHeight", "estimatedItemHeight", "contentPadding", "rowGap", "keyExtractor", "itemType", "maintainVisibleContentPosition", "overscan", "endReachedThreshold", "horizontal", "inverted", "stickyIndices", "stickyHeader", "keyboardShouldPersistTaps", "itemVisiblePercentThreshold"], outputs: ["endReached", "viewableItemsChanged"], exportAs: ["virtualList"] }] });
283
297
  }
284
298
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SectionList, decorators: [{
285
299
  type: Component,
@@ -295,6 +309,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
295
309
  [stickyIndices]="stickyRows()"
296
310
  [overscan]="overscan()"
297
311
  [endReachedThreshold]="endReachedThreshold()"
312
+ [contentPadding]="contentPadding()"
313
+ [keyboardShouldPersistTaps]="keyboardShouldPersistTaps()"
298
314
  [style]="fill"
299
315
  (endReached)="endReached.emit($event)"
300
316
  >
@@ -352,5 +368,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
352
368
  </virtual-list>
353
369
  `,
354
370
  }]
355
- }], propDecorators: { sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: true }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: true }] }], sectionHeaderHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionHeaderHeight", required: false }] }], sectionFooterHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionFooterHeight", required: false }] }], stickySectionHeadersEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickySectionHeadersEnabled", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], endReachedThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "endReachedThreshold", required: false }] }], endReached: [{ type: i0.Output, args: ["endReached"] }], header: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionHeader), { isSignal: true }] }], footer: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionFooter), { isSignal: true }] }], item: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionItem), { isSignal: true }] }], separator: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionSeparator), { isSignal: true }] }], edge: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionEdgeSeparator), { isSignal: true }] }], list: [{ type: i0.ViewChild, args: ['list', { isSignal: true }] }] } });
371
+ }], propDecorators: { sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: true }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: true }] }], sectionHeaderHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionHeaderHeight", required: false }] }], sectionFooterHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionFooterHeight", required: false }] }], stickySectionHeadersEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickySectionHeadersEnabled", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], endReachedThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "endReachedThreshold", required: false }] }], contentPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentPadding", required: false }] }], keyboardShouldPersistTaps: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardShouldPersistTaps", required: false }] }], endReached: [{ type: i0.Output, args: ["endReached"] }], header: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionHeader), { isSignal: true }] }], footer: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionFooter), { isSignal: true }] }], item: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionItem), { isSignal: true }] }], separator: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionSeparator), { isSignal: true }] }], edge: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SectionEdgeSeparator), { isSignal: true }] }], list: [{ type: i0.ViewChild, args: ['list', { isSignal: true }] }] } });
356
372
  //# sourceMappingURL=section-list.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"section-list.js","sourceRoot":"","sources":["../src/section-list.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Directive,\n TemplateRef,\n computed,\n contentChild,\n inject,\n input,\n numberAttribute,\n output,\n viewChild,\n} from '@angular/core';\nimport { nativePlatform } from '@ng-native/fabric';\nimport { TemplateSlot } from './template-slot.ts';\nimport { optionalBoolean } from './transforms.ts';\nimport { View } from './view.ts';\nimport { VirtualList } from './virtual-list.ts';\n\n/** A section as RN's SectionList takes it: its rows in `data`, plus whatever else it carries. */\nexport interface SectionListSection<T> {\n readonly key?: string;\n readonly title?: string;\n readonly data: readonly T[];\n}\n\n/** What a section header or footer template is given. */\nexport interface SectionContext<S> {\n readonly $implicit: S;\n readonly section: S;\n readonly sectionIndex: number;\n}\n\n/** What an item template is given. `index` counts within the section, as `renderItem` does. */\nexport interface SectionItemContext<T, S> {\n readonly $implicit: T;\n readonly item: T;\n readonly index: number;\n readonly section: S;\n readonly sectionIndex: number;\n}\n\n/** What a separator template is given: the items either side of it, in the same section. */\nexport interface SectionSeparatorContext<T, S> {\n readonly $implicit: T;\n readonly leadingItem: T;\n readonly trailingItem: T;\n readonly section: S;\n}\n\n/**\n * What a section separator template is given: the section, and what is either side of the edge\n * it sits at. At a section's start `leadingItem` is undefined, and at its end `trailingItem` is.\n */\nexport interface SectionEdgeSeparatorContext<T, S> {\n readonly $implicit: S;\n readonly section: S;\n readonly leadingItem: T | undefined;\n readonly leadingSection: S | undefined;\n readonly trailingItem: T | undefined;\n readonly trailingSection: S | undefined;\n}\n\n/** `<ng-template sectionHeader let-section>`: RN's `renderSectionHeader`. */\n@Directive({ selector: 'ng-template[sectionHeader]' })\nexport class SectionHeader<S = unknown> {\n readonly template = inject<TemplateRef<SectionContext<S>>>(TemplateRef);\n\n static ngTemplateContextGuard<S>(\n _directive: SectionHeader<S>,\n context: unknown,\n ): context is SectionContext<S> {\n return true;\n }\n}\n\n/** `<ng-template sectionFooter let-section>`: RN's `renderSectionFooter`. */\n@Directive({ selector: 'ng-template[sectionFooter]' })\nexport class SectionFooter<S = unknown> {\n readonly template = inject<TemplateRef<SectionContext<S>>>(TemplateRef);\n\n static ngTemplateContextGuard<S>(\n _directive: SectionFooter<S>,\n context: unknown,\n ): context is SectionContext<S> {\n return true;\n }\n}\n\n/** `<ng-template sectionItem let-item let-index=\"index\">`: RN's `renderItem`. */\n@Directive({ selector: 'ng-template[sectionItem]' })\nexport class SectionItem<T = unknown, S = unknown> {\n readonly template = inject<TemplateRef<SectionItemContext<T, S>>>(TemplateRef);\n\n static ngTemplateContextGuard<T, S>(\n _directive: SectionItem<T, S>,\n context: unknown,\n ): context is SectionItemContext<T, S> {\n return true;\n }\n}\n\n/** `<ng-template sectionSeparator let-leading>`: RN's `ItemSeparatorComponent`. */\n@Directive({ selector: 'ng-template[sectionSeparator]' })\nexport class SectionSeparator<T = unknown, S = unknown> {\n readonly template = inject<TemplateRef<SectionSeparatorContext<T, S>>>(TemplateRef);\n\n static ngTemplateContextGuard<T, S>(\n _directive: SectionSeparator<T, S>,\n context: unknown,\n ): context is SectionSeparatorContext<T, S> {\n return true;\n }\n}\n\n/**\n * `<ng-template sectionEdgeSeparator let-section>`: RN's `SectionSeparatorComponent`, drawn at\n * both edges of a section that has items, between its header and first item and between its\n * last item and footer.\n */\n@Directive({ selector: 'ng-template[sectionEdgeSeparator]' })\nexport class SectionEdgeSeparator<T = unknown, S = unknown> {\n readonly template = inject<TemplateRef<SectionEdgeSeparatorContext<T, S>>>(TemplateRef);\n\n static ngTemplateContextGuard<T, S>(\n _directive: SectionEdgeSeparator<T, S>,\n context: unknown,\n ): context is SectionEdgeSeparatorContext<T, S> {\n return true;\n }\n}\n\n/** One row of the flattened list. */\nexport type SectionRow<T, S> =\n | {\n readonly kind: 'header' | 'footer';\n readonly context: SectionContext<S>;\n readonly separator: null;\n readonly leading: null;\n readonly trailing: null;\n }\n | {\n readonly kind: 'item';\n readonly context: SectionItemContext<T, S>;\n /** Null for a section's last item, where the section separator takes its place, as in RN. */\n readonly separator: SectionSeparatorContext<T, S> | null;\n /** The section separator before a section's first item, and null on every other. */\n readonly leading: SectionEdgeSeparatorContext<T, S> | null;\n /** The section separator after a section's last item, and null on every other. */\n readonly trailing: SectionEdgeSeparatorContext<T, S> | null;\n };\n\n/**\n * Flatten sections into the rows RN's `VirtualizedSectionList` gives its list: for each section a\n * header, its items, then a footer. The header and footer rows are there whether or not anything\n * is drawn in them, as in RN, which is what keeps `scrollToLocation`'s arithmetic the same.\n *\n * The section separators ride on the first and last item, as RN's do: `SectionSeparatorComponent`\n * is the first item's leading separator and the last item's trailing one, so an empty section has\n * none, and a last item has it in place of the item separator.\n */\nexport function flattenSections<T, S extends SectionListSection<T>>(\n sections: readonly S[],\n): SectionRow<T, S>[] {\n const rows: SectionRow<T, S>[] = [];\n const edges = { leading: null, trailing: null };\n sections.forEach((section, sectionIndex) => {\n const context = { $implicit: section, section, sectionIndex };\n rows.push({ kind: 'header', context, separator: null, ...edges });\n const data = section.data;\n const last = data.length - 1;\n const around = {\n $implicit: section,\n section,\n leadingSection: sections[sectionIndex - 1],\n trailingSection: sections[sectionIndex + 1],\n };\n data.forEach((item, index) => {\n const trailingItem = data[index + 1];\n rows.push({\n kind: 'item',\n context: { $implicit: item, item, index, section, sectionIndex },\n separator:\n index < last\n ? { $implicit: item, leadingItem: item, trailingItem: trailingItem!, section }\n : null,\n leading: index === 0 ? { ...around, leadingItem: undefined, trailingItem: item } : null,\n trailing: index === last ? { ...around, leadingItem: item, trailingItem: undefined } : null,\n });\n });\n rows.push({ kind: 'footer', context, separator: null, ...edges });\n });\n return rows;\n}\n\ntype Height<A extends unknown[]> = number | ((...args: A) => number);\n\nconst measure = <A extends unknown[]>(height: Height<A>, ...args: A): number =>\n typeof height === 'number' ? height : height(...args);\n\n/**\n * RN's SectionList: sections of items, each with a header and footer, over `<virtual-list>`.\n *\n * RN's own is a VirtualizedList over flattened rows, and so is this. Sections become one list of\n * header, item and footer rows, the rows are placed by `<virtual-list>`'s variable-height windowing,\n * and this component draws each from the caller's templates:\n *\n * ```html\n * <section-list [sections]=\"sections\" [itemHeight]=\"44\" [sectionHeaderHeight]=\"28\">\n * <ng-template sectionHeader let-section><text>{{ section.title }}</text></ng-template>\n * <ng-template sectionItem let-item><text>{{ item }}</text></ng-template>\n * <ng-template sectionSeparator><view [style]=\"line\"></view></ng-template>\n * <ng-template sectionEdgeSeparator><view [style]=\"rule\"></view></ng-template>\n * </section-list>\n * ```\n *\n * Rows are fixed height, as `getItemLayout` makes them in RN: `itemHeight`, `sectionHeaderHeight`\n * and `sectionFooterHeight` are numbers or functions. An item's height includes its separators,\n * which are drawn in the item's slot: the item separator at its bottom, only between items of the\n * same section, and the section separator at the top of a section's first item and the bottom of\n * its last, as RN's `SectionSeparatorComponent` is.\n *\n * `stickySectionHeadersEnabled` pins the current section's header until the next pushes it off.\n * It defaults to on for iOS and off for Android, as RN's does. `listHeader` and `listFooter`\n * content and a `<refresh-control>` pass through to the list.\n *\n * ponytail: no `horizontal` or `inverted`, no viewability events, and no `highlighted` on a\n * separator. The host is a plain view with the list filling it, where RN's host is the scroll view.\n */\n@Component({\n selector: 'section-list',\n exportAs: 'sectionList',\n imports: [TemplateSlot, View, VirtualList],\n template: `\n <virtual-list\n #list\n [items]=\"rows()\"\n [itemHeight]=\"rowHeight\"\n [stickyIndices]=\"stickyRows()\"\n [overscan]=\"overscan()\"\n [endReachedThreshold]=\"endReachedThreshold()\"\n [style]=\"fill\"\n (endReached)=\"endReached.emit($event)\"\n >\n <ng-content select=\"refresh-control\" ngProjectAs=\"refresh-control\" />\n <view listHeader><ng-content select=\"[listHeader]\" /></view>\n <ng-content />\n @for (row of list.window(); track row.index) {\n <view [style]=\"row.style\">\n @switch (row.item.kind) {\n @case ('header') {\n @if (header(); as header) {\n <ng-container\n [templateSlot]=\"header.template\"\n [templateSlotContext]=\"row.item.context\"\n />\n }\n }\n @case ('footer') {\n @if (footer(); as footer) {\n <ng-container\n [templateSlot]=\"footer.template\"\n [templateSlotContext]=\"row.item.context\"\n />\n }\n }\n @case ('item') {\n @if (row.item.leading; as start) {\n @if (edge(); as edge) {\n <ng-container [templateSlot]=\"edge.template\" [templateSlotContext]=\"start\" />\n }\n }\n <view [style]=\"cell\">\n @if (item(); as item) {\n <ng-container\n [templateSlot]=\"item.template\"\n [templateSlotContext]=\"row.item.context\"\n />\n }\n </view>\n @if (separator(); as separator) {\n @if (row.item.separator; as gap) {\n <ng-container [templateSlot]=\"separator.template\" [templateSlotContext]=\"gap\" />\n }\n }\n @if (row.item.trailing; as end) {\n @if (edge(); as edge) {\n <ng-container [templateSlot]=\"edge.template\" [templateSlotContext]=\"end\" />\n }\n }\n }\n }\n </view>\n }\n <view listFooter><ng-content select=\"[listFooter]\" /></view>\n </virtual-list>\n `,\n})\nexport class SectionList<T, S extends SectionListSection<T> = SectionListSection<T>> {\n readonly sections = input.required<readonly S[]>();\n /** Each item's height, including every separator drawn in its slot. */\n readonly itemHeight = input.required<Height<[item: T, index: number, section: S]>>();\n readonly sectionHeaderHeight = input<Height<[section: S]>>(0);\n readonly sectionFooterHeight = input<Height<[section: S]>>(0);\n /** Pin the current section's header. Defaults to true on iOS and false on Android, as RN. */\n readonly stickySectionHeadersEnabled = input(undefined, { transform: optionalBoolean });\n readonly overscan = input(4, { transform: numberAttribute });\n readonly endReachedThreshold = input(2, { transform: numberAttribute });\n\n /** The end of the last section is within `endReachedThreshold`. See `<virtual-list>`. */\n readonly endReached = output<{ distanceFromEnd: number }>();\n\n protected readonly header = contentChild<SectionHeader<S>>(SectionHeader);\n protected readonly footer = contentChild<SectionFooter<S>>(SectionFooter);\n protected readonly item = contentChild<SectionItem<T, S>>(SectionItem);\n protected readonly separator = contentChild<SectionSeparator<T, S>>(SectionSeparator);\n protected readonly edge = contentChild<SectionEdgeSeparator<T, S>>(SectionEdgeSeparator);\n private readonly list = viewChild.required<VirtualList<SectionRow<T, S>>>('list');\n\n protected readonly fill = { flex: 1 };\n /** The item fills its slot between its separators, as RN's cell draws them around it. */\n protected readonly cell = { flex: 1 };\n\n protected readonly rows = computed(() => flattenSections<T, S>(this.sections()));\n\n private readonly sticky = computed(\n () => this.stickySectionHeadersEnabled() ?? nativePlatform() === 'ios',\n );\n\n /** The header rows, pinned in turn, when headers stick and there is a header to draw. */\n protected readonly stickyRows = computed(() => {\n if (!this.sticky() || !this.header()) return [];\n const indices: number[] = [];\n this.rows().forEach((row, index) => {\n if (row.kind === 'header') indices.push(index);\n });\n return indices;\n });\n\n protected readonly rowHeight = (row: SectionRow<T, S>): number => {\n const { section } = row.context;\n if (row.kind === 'item') {\n return measure(this.itemHeight(), row.context.item, row.context.index, section);\n }\n return row.kind === 'header'\n ? measure(this.sectionHeaderHeight(), section)\n : measure(this.sectionFooterHeight(), section);\n };\n\n /**\n * Scroll to an item, RN's way: `itemIndex` 0 is the section's header and 1 its first item, and\n * with sticky headers the header's height is allowed for so the item is not hidden under it.\n */\n scrollToLocation(options: {\n sectionIndex: number;\n itemIndex: number;\n viewOffset?: number;\n animated?: boolean;\n }): void {\n const sections = this.sections();\n let index = options.itemIndex;\n for (let i = 0; i < options.sectionIndex; i++) index += sections[i]!.data.length + 2;\n let viewOffset = options.viewOffset ?? 0;\n const section = sections[options.sectionIndex];\n if (options.itemIndex > 0 && this.sticky() && section) {\n viewOffset += measure(this.sectionHeaderHeight(), section);\n }\n this.list().scrollToIndex({ index, viewOffset, animated: options.animated });\n }\n}\n"]}
1
+ {"version":3,"file":"section-list.js","sourceRoot":"","sources":["../src/section-list.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Directive,\n TemplateRef,\n computed,\n contentChild,\n inject,\n input,\n numberAttribute,\n output,\n viewChild,\n} from '@angular/core';\nimport { nativePlatform } from '@ng-native/fabric';\nimport { TemplateSlot } from './template-slot.ts';\nimport { optionalBoolean } from './transforms.ts';\nimport { View } from './view.ts';\nimport type { KeyboardShouldPersistTaps } from './keyboard-taps.ts';\nimport { VirtualList, type VirtualListPadding } from './virtual-list.ts';\n\n/** A section as RN's SectionList takes it: its rows in `data`, plus whatever else it carries. */\nexport interface SectionListSection<T> {\n readonly key?: string;\n readonly title?: string;\n readonly data: readonly T[];\n}\n\n/** What a section header or footer template is given. */\nexport interface SectionContext<S> {\n readonly $implicit: S;\n readonly section: S;\n readonly sectionIndex: number;\n}\n\n/** What an item template is given. `index` counts within the section, as `renderItem` does. */\nexport interface SectionItemContext<T, S> {\n readonly $implicit: T;\n readonly item: T;\n readonly index: number;\n readonly section: S;\n readonly sectionIndex: number;\n}\n\n/** What a separator template is given: the items either side of it, in the same section. */\nexport interface SectionSeparatorContext<T, S> {\n readonly $implicit: T;\n readonly leadingItem: T;\n readonly trailingItem: T;\n readonly section: S;\n}\n\n/**\n * What a section separator template is given: the section, and what is either side of the edge\n * it sits at. At a section's start `leadingItem` is undefined, and at its end `trailingItem` is.\n */\nexport interface SectionEdgeSeparatorContext<T, S> {\n readonly $implicit: S;\n readonly section: S;\n readonly leadingItem: T | undefined;\n readonly leadingSection: S | undefined;\n readonly trailingItem: T | undefined;\n readonly trailingSection: S | undefined;\n}\n\n/** `<ng-template sectionHeader let-section>`: RN's `renderSectionHeader`. */\n@Directive({ selector: 'ng-template[sectionHeader]' })\nexport class SectionHeader<S = unknown> {\n readonly template = inject<TemplateRef<SectionContext<S>>>(TemplateRef);\n\n static ngTemplateContextGuard<S>(\n _directive: SectionHeader<S>,\n context: unknown,\n ): context is SectionContext<S> {\n return true;\n }\n}\n\n/** `<ng-template sectionFooter let-section>`: RN's `renderSectionFooter`. */\n@Directive({ selector: 'ng-template[sectionFooter]' })\nexport class SectionFooter<S = unknown> {\n readonly template = inject<TemplateRef<SectionContext<S>>>(TemplateRef);\n\n static ngTemplateContextGuard<S>(\n _directive: SectionFooter<S>,\n context: unknown,\n ): context is SectionContext<S> {\n return true;\n }\n}\n\n/** `<ng-template sectionItem let-item let-index=\"index\">`: RN's `renderItem`. */\n@Directive({ selector: 'ng-template[sectionItem]' })\nexport class SectionItem<T = unknown, S = unknown> {\n readonly template = inject<TemplateRef<SectionItemContext<T, S>>>(TemplateRef);\n\n static ngTemplateContextGuard<T, S>(\n _directive: SectionItem<T, S>,\n context: unknown,\n ): context is SectionItemContext<T, S> {\n return true;\n }\n}\n\n/** `<ng-template sectionSeparator let-leading>`: RN's `ItemSeparatorComponent`. */\n@Directive({ selector: 'ng-template[sectionSeparator]' })\nexport class SectionSeparator<T = unknown, S = unknown> {\n readonly template = inject<TemplateRef<SectionSeparatorContext<T, S>>>(TemplateRef);\n\n static ngTemplateContextGuard<T, S>(\n _directive: SectionSeparator<T, S>,\n context: unknown,\n ): context is SectionSeparatorContext<T, S> {\n return true;\n }\n}\n\n/**\n * `<ng-template sectionEdgeSeparator let-section>`: RN's `SectionSeparatorComponent`, drawn at\n * both edges of a section that has items, between its header and first item and between its\n * last item and footer.\n */\n@Directive({ selector: 'ng-template[sectionEdgeSeparator]' })\nexport class SectionEdgeSeparator<T = unknown, S = unknown> {\n readonly template = inject<TemplateRef<SectionEdgeSeparatorContext<T, S>>>(TemplateRef);\n\n static ngTemplateContextGuard<T, S>(\n _directive: SectionEdgeSeparator<T, S>,\n context: unknown,\n ): context is SectionEdgeSeparatorContext<T, S> {\n return true;\n }\n}\n\n/** One row of the flattened list. */\nexport type SectionRow<T, S> =\n | {\n readonly kind: 'header' | 'footer';\n readonly context: SectionContext<S>;\n readonly separator: null;\n readonly leading: null;\n readonly trailing: null;\n }\n | {\n readonly kind: 'item';\n readonly context: SectionItemContext<T, S>;\n /** Null for a section's last item, where the section separator takes its place, as in RN. */\n readonly separator: SectionSeparatorContext<T, S> | null;\n /** The section separator before a section's first item, and null on every other. */\n readonly leading: SectionEdgeSeparatorContext<T, S> | null;\n /** The section separator after a section's last item, and null on every other. */\n readonly trailing: SectionEdgeSeparatorContext<T, S> | null;\n };\n\n/**\n * Flatten sections into the rows RN's `VirtualizedSectionList` gives its list: for each section a\n * header, its items, then a footer. The header and footer rows are there whether or not anything\n * is drawn in them, as in RN, which is what keeps `scrollToLocation`'s arithmetic the same.\n *\n * The section separators ride on the first and last item, as RN's do: `SectionSeparatorComponent`\n * is the first item's leading separator and the last item's trailing one, so an empty section has\n * none, and a last item has it in place of the item separator.\n */\nexport function flattenSections<T, S extends SectionListSection<T>>(\n sections: readonly S[],\n): SectionRow<T, S>[] {\n const rows: SectionRow<T, S>[] = [];\n const edges = { leading: null, trailing: null };\n sections.forEach((section, sectionIndex) => {\n const context = { $implicit: section, section, sectionIndex };\n rows.push({ kind: 'header', context, separator: null, ...edges });\n const data = section.data;\n const last = data.length - 1;\n const around = {\n $implicit: section,\n section,\n leadingSection: sections[sectionIndex - 1],\n trailingSection: sections[sectionIndex + 1],\n };\n data.forEach((item, index) => {\n const trailingItem = data[index + 1];\n rows.push({\n kind: 'item',\n context: { $implicit: item, item, index, section, sectionIndex },\n separator:\n index < last\n ? { $implicit: item, leadingItem: item, trailingItem: trailingItem!, section }\n : null,\n leading: index === 0 ? { ...around, leadingItem: undefined, trailingItem: item } : null,\n trailing: index === last ? { ...around, leadingItem: item, trailingItem: undefined } : null,\n });\n });\n rows.push({ kind: 'footer', context, separator: null, ...edges });\n });\n return rows;\n}\n\ntype Height<A extends unknown[]> = number | ((...args: A) => number);\n\nconst measure = <A extends unknown[]>(height: Height<A>, ...args: A): number =>\n typeof height === 'number' ? height : height(...args);\n\n/**\n * RN's SectionList: sections of items, each with a header and footer, over `<virtual-list>`.\n *\n * RN's own is a VirtualizedList over flattened rows, and so is this. Sections become one list of\n * header, item and footer rows, the rows are placed by `<virtual-list>`'s variable-height windowing,\n * and this component draws each from the caller's templates:\n *\n * ```html\n * <section-list [sections]=\"sections\" [itemHeight]=\"44\" [sectionHeaderHeight]=\"28\">\n * <ng-template sectionHeader let-section><text>{{ section.title }}</text></ng-template>\n * <ng-template sectionItem let-item><text>{{ item }}</text></ng-template>\n * <ng-template sectionSeparator><view [style]=\"line\"></view></ng-template>\n * <ng-template sectionEdgeSeparator><view [style]=\"rule\"></view></ng-template>\n * </section-list>\n * ```\n *\n * Rows are fixed height, as `getItemLayout` makes them in RN: `itemHeight`, `sectionHeaderHeight`\n * and `sectionFooterHeight` are numbers or functions. An item's height includes its separators,\n * which are drawn in the item's slot: the item separator at its bottom, only between items of the\n * same section, and the section separator at the top of a section's first item and the bottom of\n * its last, as RN's `SectionSeparatorComponent` is.\n *\n * `stickySectionHeadersEnabled` pins the current section's header until the next pushes it off.\n * It defaults to on for iOS and off for Android, as RN's does. `listHeader` and `listFooter`\n * content and a `<refresh-control>` pass through to the list.\n *\n * `contentPadding` and `keyboardShouldPersistTaps` pass through to the list as `<virtual-list>`\n * takes them.\n *\n * ponytail: no `horizontal` or `inverted`, no viewability events, and no `highlighted` on a\n * separator. No `keyExtractor` or `maintainVisibleContentPosition` either: rows are fixed height,\n * so there is no measured height for a key to carry across an insert. The host is a plain view\n * with the list filling it, where RN's host is the scroll view, so scroll-view props are not\n * taken.\n */\n@Component({\n selector: 'section-list',\n exportAs: 'sectionList',\n imports: [TemplateSlot, View, VirtualList],\n template: `\n <virtual-list\n #list\n [items]=\"rows()\"\n [itemHeight]=\"rowHeight\"\n [stickyIndices]=\"stickyRows()\"\n [overscan]=\"overscan()\"\n [endReachedThreshold]=\"endReachedThreshold()\"\n [contentPadding]=\"contentPadding()\"\n [keyboardShouldPersistTaps]=\"keyboardShouldPersistTaps()\"\n [style]=\"fill\"\n (endReached)=\"endReached.emit($event)\"\n >\n <ng-content select=\"refresh-control\" ngProjectAs=\"refresh-control\" />\n <view listHeader><ng-content select=\"[listHeader]\" /></view>\n <ng-content />\n @for (row of list.window(); track row.index) {\n <view [style]=\"row.style\">\n @switch (row.item.kind) {\n @case ('header') {\n @if (header(); as header) {\n <ng-container\n [templateSlot]=\"header.template\"\n [templateSlotContext]=\"row.item.context\"\n />\n }\n }\n @case ('footer') {\n @if (footer(); as footer) {\n <ng-container\n [templateSlot]=\"footer.template\"\n [templateSlotContext]=\"row.item.context\"\n />\n }\n }\n @case ('item') {\n @if (row.item.leading; as start) {\n @if (edge(); as edge) {\n <ng-container [templateSlot]=\"edge.template\" [templateSlotContext]=\"start\" />\n }\n }\n <view [style]=\"cell\">\n @if (item(); as item) {\n <ng-container\n [templateSlot]=\"item.template\"\n [templateSlotContext]=\"row.item.context\"\n />\n }\n </view>\n @if (separator(); as separator) {\n @if (row.item.separator; as gap) {\n <ng-container [templateSlot]=\"separator.template\" [templateSlotContext]=\"gap\" />\n }\n }\n @if (row.item.trailing; as end) {\n @if (edge(); as edge) {\n <ng-container [templateSlot]=\"edge.template\" [templateSlotContext]=\"end\" />\n }\n }\n }\n }\n </view>\n }\n <view listFooter><ng-content select=\"[listFooter]\" /></view>\n </virtual-list>\n `,\n})\nexport class SectionList<T, S extends SectionListSection<T> = SectionListSection<T>> {\n readonly sections = input.required<readonly S[]>();\n /** Each item's height, including every separator drawn in its slot. */\n readonly itemHeight = input.required<Height<[item: T, index: number, section: S]>>();\n readonly sectionHeaderHeight = input<Height<[section: S]>>(0);\n readonly sectionFooterHeight = input<Height<[section: S]>>(0);\n /** Pin the current section's header. Defaults to true on iOS and false on Android, as RN. */\n readonly stickySectionHeadersEnabled = input(undefined, { transform: optionalBoolean });\n readonly overscan = input(4, { transform: numberAttribute });\n readonly endReachedThreshold = input(2, { transform: numberAttribute });\n /** Space around the rows, inside the scrolling content. See `<virtual-list>`. */\n readonly contentPadding = input<VirtualListPadding>(0);\n /** What a tap in the list does while a text input has the keyboard up. See `ScrollView`. */\n readonly keyboardShouldPersistTaps = input<KeyboardShouldPersistTaps>('never');\n\n /** The end of the last section is within `endReachedThreshold`. See `<virtual-list>`. */\n readonly endReached = output<{ distanceFromEnd: number }>();\n\n protected readonly header = contentChild<SectionHeader<S>>(SectionHeader);\n protected readonly footer = contentChild<SectionFooter<S>>(SectionFooter);\n protected readonly item = contentChild<SectionItem<T, S>>(SectionItem);\n protected readonly separator = contentChild<SectionSeparator<T, S>>(SectionSeparator);\n protected readonly edge = contentChild<SectionEdgeSeparator<T, S>>(SectionEdgeSeparator);\n private readonly list = viewChild.required<VirtualList<SectionRow<T, S>>>('list');\n\n protected readonly fill = { flex: 1 };\n /** The item fills its slot between its separators, as RN's cell draws them around it. */\n protected readonly cell = { flex: 1 };\n\n protected readonly rows = computed(() => flattenSections<T, S>(this.sections()));\n\n private readonly sticky = computed(\n () => this.stickySectionHeadersEnabled() ?? nativePlatform() === 'ios',\n );\n\n /** The header rows, pinned in turn, when headers stick and there is a header to draw. */\n protected readonly stickyRows = computed(() => {\n if (!this.sticky() || !this.header()) return [];\n const indices: number[] = [];\n this.rows().forEach((row, index) => {\n if (row.kind === 'header') indices.push(index);\n });\n return indices;\n });\n\n protected readonly rowHeight = (row: SectionRow<T, S>): number => {\n const { section } = row.context;\n if (row.kind === 'item') {\n return measure(this.itemHeight(), row.context.item, row.context.index, section);\n }\n return row.kind === 'header'\n ? measure(this.sectionHeaderHeight(), section)\n : measure(this.sectionFooterHeight(), section);\n };\n\n /**\n * Scroll to an item, RN's way: `itemIndex` 0 is the section's header and 1 its first item, and\n * with sticky headers the header's height is allowed for so the item is not hidden under it.\n */\n scrollToLocation(options: {\n sectionIndex: number;\n itemIndex: number;\n viewOffset?: number;\n animated?: boolean;\n }): void {\n const sections = this.sections();\n let index = options.itemIndex;\n for (let i = 0; i < options.sectionIndex; i++) index += sections[i]!.data.length + 2;\n let viewOffset = options.viewOffset ?? 0;\n const section = sections[options.sectionIndex];\n if (options.itemIndex > 0 && this.sticky() && section) {\n viewOffset += measure(this.sectionHeaderHeight(), section);\n }\n this.list().scrollToIndex({ index, viewOffset, animated: options.animated });\n }\n}\n"]}
@@ -1,4 +1,10 @@
1
1
  import { type HostEngine, type HostNode } from '@ng-native/fabric';
2
+ /**
3
+ * How long scrolling has to pause before a natively moved header's translate is written, so the
4
+ * shadow tree, which a touch is hit-tested against, catches up with where native drew it. RN's
5
+ * `ScrollViewStickyHeader` debounces the same write, by 64 ms on iOS.
6
+ */
7
+ export declare const STICKY_SETTLE_MS = 64;
2
8
  /**
3
9
  * `stickyHeaderIndices` for `<scroll-view>`, done the way RN's `ScrollView.js` does it.
4
10
  *
@@ -1,4 +1,10 @@
1
1
  import { pinnedRange } from '@ng-native/fabric';
2
+ /**
3
+ * How long scrolling has to pause before a natively moved header's translate is written, so the
4
+ * shadow tree, which a touch is hit-tested against, catches up with where native drew it. RN's
5
+ * `ScrollViewStickyHeader` debounces the same write, by 64 ms on iOS.
6
+ */
7
+ export const STICKY_SETTLE_MS = 64;
2
8
  /**
3
9
  * `stickyHeaderIndices` for `<scroll-view>`, done the way RN's `ScrollView.js` does it.
4
10
  *
@@ -54,9 +60,8 @@ export class StickyHeaders {
54
60
  this.offset = offset;
55
61
  if (!this.engine.drivesScroll)
56
62
  return this.apply();
57
- // RN's sticky header commits its translate after 64 ms without a scroll event.
58
63
  clearTimeout(this.settleTimer);
59
- this.settleTimer = setTimeout(() => this.apply(), 64);
64
+ this.settleTimer = setTimeout(() => this.apply(), STICKY_SETTLE_MS);
60
65
  }
61
66
  destroy() {
62
67
  clearTimeout(this.settleTimer);
@@ -1 +1 @@
1
- {"version":3,"file":"sticky-headers.js","sourceRoot":"","sources":["../src/sticky-headers.ts"],"names":[],"mappings":"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","sourcesContent":["import { type HostEngine, type HostNode, type ScrollDrive, pinnedRange } from '@ng-native/fabric';\n\ninterface Extent {\n readonly start: number;\n readonly size: number;\n}\n\ntype LayoutEvent = {\n nativeEvent?: { layout?: { x?: number; y?: number; width?: number; height?: number } };\n};\n\n/**\n * `stickyHeaderIndices` for `<scroll-view>`, done the way RN's `ScrollView.js` does it.\n *\n * There is no native prop for it on either platform. RN wraps each sticky child in\n * `ScrollViewStickyHeader`, which translates it by the scroll offset past its own position and\n * stops where the next sticky child would push it off. The same sums run here, over the content\n * view's element children: control-flow anchors are not children in RN's sense and are skipped,\n * so an index counts what is committed.\n *\n * Each sticky child's position comes from its own layout event, and the translation is written\n * as its `transform` prop, with RN's `zIndex: 10` so it draws over the content scrolling under\n * it. A style of the caller's own that sets either wins, as it would in RN.\n *\n * Where the host can, native moves each one on every frame the scroll view moves, as RN's\n * animated interpolation does, and the translation is written only once scrolling pauses: a\n * translate written from each scroll event lands late, and on a fast fling the header shudders.\n */\nexport class StickyHeaders {\n private readonly engine: HostEngine;\n private readonly horizontal: () => boolean;\n private headers: HostNode[] = [];\n private readonly extents = new Map<HostNode, Extent>();\n private readonly listening = new Map<HostNode, () => void>();\n /** The translation last written per header, so an unchanged one is not written again. */\n private readonly written = new Map<HostNode, number>();\n private readonly drives = new Map<HostNode, ScrollDrive>();\n private settleTimer: ReturnType<typeof setTimeout> | undefined;\n private offset = 0;\n\n constructor(engine: HostEngine, scroll: HostNode, horizontal: () => boolean) {\n this.engine = engine;\n this.scroll = scroll;\n this.horizontal = horizontal;\n }\n\n private readonly scroll: HostNode;\n\n /** Find the sticky children again, after a pass that may have changed them. */\n track(content: HostNode | undefined, indices: readonly number[]): void {\n const children = (content?.children ?? []).filter((child) => child.kind === 'element');\n const headers = indices.flatMap((index) => children[index] ?? []);\n for (const node of this.headers) {\n if (!headers.includes(node)) this.release(node);\n }\n for (const node of headers) {\n if (this.listening.has(node)) continue;\n this.engine.setProp(node, 'zIndex', 10);\n this.listening.set(\n node,\n this.engine.setEventListener(node, 'topLayout', (event) => this.measured(node, event)),\n );\n }\n this.headers = headers;\n this.apply();\n }\n\n scrolled(offset: number): void {\n this.offset = offset;\n if (!this.engine.drivesScroll) return this.apply();\n // RN's sticky header commits its translate after 64 ms without a scroll event.\n clearTimeout(this.settleTimer);\n this.settleTimer = setTimeout(() => this.apply(), 64);\n }\n\n destroy(): void {\n clearTimeout(this.settleTimer);\n for (const node of this.headers) this.release(node);\n this.headers = [];\n }\n\n private measured(node: HostNode, event: unknown): void {\n const layout = (event as LayoutEvent).nativeEvent?.layout;\n if (!layout) return;\n const horizontal = this.horizontal();\n this.extents.set(node, {\n start: (horizontal ? layout.x : layout.y) ?? 0,\n size: (horizontal ? layout.width : layout.height) ?? 0,\n });\n this.apply();\n }\n\n private release(node: HostNode): void {\n this.listening.get(node)?.();\n this.listening.delete(node);\n this.extents.delete(node);\n this.written.delete(node);\n this.drives.get(node)?.stop();\n this.drives.delete(node);\n this.engine.setProp(node, 'zIndex', undefined);\n this.engine.setProp(node, 'transform', undefined);\n }\n\n private drive(node: HostNode, start: number, stop: number): void {\n if (!this.engine.drivesScroll) return;\n const range = pinnedRange(start, stop);\n const drive = this.drives.get(node);\n if (drive) return drive.update(range);\n const axis = this.horizontal() ? 'x' : 'y';\n const next = this.engine.driveByScroll(node, this.scroll, axis, range);\n if (next) this.drives.set(node, next);\n }\n\n /** `ScrollViewStickyHeader`'s interpolation, as plain arithmetic. */\n private apply(): void {\n const key = this.horizontal() ? 'translateX' : 'translateY';\n this.headers.forEach((node, at) => {\n const own = this.extents.get(node);\n if (!own) return;\n const next = this.extents.get(this.headers[at + 1]!);\n const stop = next ? next.start - own.size : Infinity;\n this.drive(node, own.start, stop);\n const shift = Math.min(Math.max(0, this.offset - own.start), Math.max(0, stop - own.start));\n if (this.written.get(node) === shift) return;\n this.written.set(node, shift);\n this.engine.setProp(node, 'transform', shift > 0 ? [{ [key]: shift }] : undefined);\n });\n }\n}\n"]}
1
+ {"version":3,"file":"sticky-headers.js","sourceRoot":"","sources":["../src/sticky-headers.ts"],"names":[],"mappings":"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","sourcesContent":["import { type HostEngine, type HostNode, type ScrollDrive, pinnedRange } from '@ng-native/fabric';\n\ninterface Extent {\n readonly start: number;\n readonly size: number;\n}\n\n/**\n * How long scrolling has to pause before a natively moved header's translate is written, so the\n * shadow tree, which a touch is hit-tested against, catches up with where native drew it. RN's\n * `ScrollViewStickyHeader` debounces the same write, by 64 ms on iOS.\n */\nexport const STICKY_SETTLE_MS = 64;\n\ntype LayoutEvent = {\n nativeEvent?: { layout?: { x?: number; y?: number; width?: number; height?: number } };\n};\n\n/**\n * `stickyHeaderIndices` for `<scroll-view>`, done the way RN's `ScrollView.js` does it.\n *\n * There is no native prop for it on either platform. RN wraps each sticky child in\n * `ScrollViewStickyHeader`, which translates it by the scroll offset past its own position and\n * stops where the next sticky child would push it off. The same sums run here, over the content\n * view's element children: control-flow anchors are not children in RN's sense and are skipped,\n * so an index counts what is committed.\n *\n * Each sticky child's position comes from its own layout event, and the translation is written\n * as its `transform` prop, with RN's `zIndex: 10` so it draws over the content scrolling under\n * it. A style of the caller's own that sets either wins, as it would in RN.\n *\n * Where the host can, native moves each one on every frame the scroll view moves, as RN's\n * animated interpolation does, and the translation is written only once scrolling pauses: a\n * translate written from each scroll event lands late, and on a fast fling the header shudders.\n */\nexport class StickyHeaders {\n private readonly engine: HostEngine;\n private readonly horizontal: () => boolean;\n private headers: HostNode[] = [];\n private readonly extents = new Map<HostNode, Extent>();\n private readonly listening = new Map<HostNode, () => void>();\n /** The translation last written per header, so an unchanged one is not written again. */\n private readonly written = new Map<HostNode, number>();\n private readonly drives = new Map<HostNode, ScrollDrive>();\n private settleTimer: ReturnType<typeof setTimeout> | undefined;\n private offset = 0;\n\n constructor(engine: HostEngine, scroll: HostNode, horizontal: () => boolean) {\n this.engine = engine;\n this.scroll = scroll;\n this.horizontal = horizontal;\n }\n\n private readonly scroll: HostNode;\n\n /** Find the sticky children again, after a pass that may have changed them. */\n track(content: HostNode | undefined, indices: readonly number[]): void {\n const children = (content?.children ?? []).filter((child) => child.kind === 'element');\n const headers = indices.flatMap((index) => children[index] ?? []);\n for (const node of this.headers) {\n if (!headers.includes(node)) this.release(node);\n }\n for (const node of headers) {\n if (this.listening.has(node)) continue;\n this.engine.setProp(node, 'zIndex', 10);\n this.listening.set(\n node,\n this.engine.setEventListener(node, 'topLayout', (event) => this.measured(node, event)),\n );\n }\n this.headers = headers;\n this.apply();\n }\n\n scrolled(offset: number): void {\n this.offset = offset;\n if (!this.engine.drivesScroll) return this.apply();\n clearTimeout(this.settleTimer);\n this.settleTimer = setTimeout(() => this.apply(), STICKY_SETTLE_MS);\n }\n\n destroy(): void {\n clearTimeout(this.settleTimer);\n for (const node of this.headers) this.release(node);\n this.headers = [];\n }\n\n private measured(node: HostNode, event: unknown): void {\n const layout = (event as LayoutEvent).nativeEvent?.layout;\n if (!layout) return;\n const horizontal = this.horizontal();\n this.extents.set(node, {\n start: (horizontal ? layout.x : layout.y) ?? 0,\n size: (horizontal ? layout.width : layout.height) ?? 0,\n });\n this.apply();\n }\n\n private release(node: HostNode): void {\n this.listening.get(node)?.();\n this.listening.delete(node);\n this.extents.delete(node);\n this.written.delete(node);\n this.drives.get(node)?.stop();\n this.drives.delete(node);\n this.engine.setProp(node, 'zIndex', undefined);\n this.engine.setProp(node, 'transform', undefined);\n }\n\n private drive(node: HostNode, start: number, stop: number): void {\n if (!this.engine.drivesScroll) return;\n const range = pinnedRange(start, stop);\n const drive = this.drives.get(node);\n if (drive) return drive.update(range);\n const axis = this.horizontal() ? 'x' : 'y';\n const next = this.engine.driveByScroll(node, this.scroll, axis, range);\n if (next) this.drives.set(node, next);\n }\n\n /** `ScrollViewStickyHeader`'s interpolation, as plain arithmetic. */\n private apply(): void {\n const key = this.horizontal() ? 'translateX' : 'translateY';\n this.headers.forEach((node, at) => {\n const own = this.extents.get(node);\n if (!own) return;\n const next = this.extents.get(this.headers[at + 1]!);\n const stop = next ? next.start - own.size : Infinity;\n this.drive(node, own.start, stop);\n const shift = Math.min(Math.max(0, this.offset - own.start), Math.max(0, stop - own.start));\n if (this.written.get(node) === shift) return;\n this.written.set(node, shift);\n this.engine.setProp(node, 'transform', shift > 0 ? [{ [key]: shift }] : undefined);\n });\n }\n}\n"]}
package/dist/switch.d.ts CHANGED
@@ -6,6 +6,10 @@ import * as i0 from "@angular/core";
6
6
  * A toggle, wired for signal forms as a `FormCheckboxControl` (`checked`, not `value`). Commits
7
7
  * as `Switch` on iOS and `AndroidSwitch` on Android.
8
8
  *
9
+ * `invalid` and `touched` come out as `data-invalid` and `data-touched` for a stylesheet, as on
10
+ * `<text-input>`, and `touch` fires when the user flips it: a switch has no blur, so the flip is
11
+ * the moment the user has dealt with it, which a form that shows errors once touched waits for.
12
+ *
9
13
  * The two platforms spell the same props differently, so both spellings are sent and each
10
14
  * native side reads its own; a prop it does not know is ignored.
11
15
  *
@@ -20,6 +24,12 @@ export declare class Switch extends ViewBase {
20
24
  readonly checked: import("@angular/core").ModelSignal<boolean>;
21
25
  /** Ignore touches and grey out. */
22
26
  readonly disabled: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
27
+ /** Published as `data-invalid`; nothing native reads it. */
28
+ readonly invalid: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
29
+ /** Published as `data-touched`; nothing native reads it. */
30
+ readonly touched: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
31
+ /** The user flipped it. What Signal Forms marks a field touched from. */
32
+ readonly touch: import("@angular/core").OutputEmitterRef<void>;
23
33
  /** `Switch.js`: `accessibilityRole={props.accessibilityRole ?? 'switch'}`. */
24
34
  protected roleByDefault(): 'switch';
25
35
  protected disabledForAccessibility(): boolean | undefined;
@@ -50,5 +60,5 @@ export declare class Switch extends ViewBase {
50
60
  */
51
61
  private reconcile;
52
62
  static ɵfac: i0.ɵɵFactoryDeclaration<Switch, never>;
53
- static ɵdir: i0.ɵɵDirectiveDeclaration<Switch, "switch", never, { "checked": { "alias": "checked"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "thumbColor": { "alias": "thumbColor"; "required": false; "isSignal": true; }; "trackColor": { "alias": "trackColor"; "required": false; "isSignal": true; }; "iosBackgroundColor": { "alias": "ios_backgroundColor"; "required": false; "isSignal": true; }; }, { "checked": "checkedChange"; }, never, never, true, never>;
63
+ static ɵdir: i0.ɵɵDirectiveDeclaration<Switch, "switch", never, { "checked": { "alias": "checked"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; "thumbColor": { "alias": "thumbColor"; "required": false; "isSignal": true; }; "trackColor": { "alias": "trackColor"; "required": false; "isSignal": true; }; "iosBackgroundColor": { "alias": "ios_backgroundColor"; "required": false; "isSignal": true; }; }, { "checked": "checkedChange"; "touch": "touch"; }, never, never, true, never>;
54
64
  }
package/dist/switch.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Directive, Injector, afterNextRender, computed, effect, inject, input, model, } from '@angular/core';
1
+ import { Directive, Injector, afterNextRender, booleanAttribute, computed, effect, inject, input, model, output, } from '@angular/core';
2
2
  import { nativePlatform } from '@ng-native/fabric';
3
3
  import { ControlledModel } from "./controlled-model.js";
4
4
  import { optionalBoolean } from "./transforms.js";
@@ -8,6 +8,10 @@ import * as i0 from "@angular/core";
8
8
  * A toggle, wired for signal forms as a `FormCheckboxControl` (`checked`, not `value`). Commits
9
9
  * as `Switch` on iOS and `AndroidSwitch` on Android.
10
10
  *
11
+ * `invalid` and `touched` come out as `data-invalid` and `data-touched` for a stylesheet, as on
12
+ * `<text-input>`, and `touch` fires when the user flips it: a switch has no blur, so the flip is
13
+ * the moment the user has dealt with it, which a form that shows errors once touched waits for.
14
+ *
11
15
  * The two platforms spell the same props differently, so both spellings are sent and each
12
16
  * native side reads its own; a prop it does not know is ignored.
13
17
  *
@@ -23,6 +27,12 @@ export class Switch extends ViewBase {
23
27
  ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
24
28
  /** Ignore touches and grey out. */
25
29
  disabled = input(undefined, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
30
+ /** Published as `data-invalid`; nothing native reads it. */
31
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
32
+ /** Published as `data-touched`; nothing native reads it. */
33
+ touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
34
+ /** The user flipped it. What Signal Forms marks a field touched from. */
35
+ touch = output();
26
36
  /** `Switch.js`: `accessibilityRole={props.accessibilityRole ?? 'switch'}`. */
27
37
  roleByDefault() {
28
38
  return 'switch';
@@ -66,6 +76,7 @@ export class Switch extends ViewBase {
66
76
  onNativeChange(event) {
67
77
  this.nativeValue = event.nativeEvent?.value ?? false;
68
78
  this.control.propose(this.nativeValue);
79
+ this.touch.emit();
69
80
  afterNextRender({ write: () => this.reconcile() }, { injector: this.injector });
70
81
  }
71
82
  /**
@@ -83,7 +94,7 @@ export class Switch extends ViewBase {
83
94
  this.engine.dispatchCommand(this.node, command, [desired]);
84
95
  }
85
96
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Switch, deps: [], target: i0.ɵɵFactoryTarget.Directive });
86
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: Switch, isStandalone: true, selector: "switch", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, thumbColor: { classPropertyName: "thumbColor", publicName: "thumbColor", isSignal: true, isRequired: false, transformFunction: null }, trackColor: { classPropertyName: "trackColor", publicName: "trackColor", isSignal: true, isRequired: false, transformFunction: null }, iosBackgroundColor: { classPropertyName: "iosBackgroundColor", publicName: "ios_backgroundColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange" }, host: { listeners: { "change": "onNativeChange($event)" }, properties: { "accessibilityState": "accessibilityStateProp()", "value": "checked()", "disabled": "disabled()", "attr.data-disabled": "disabled() ? '' : null", "enabled": "enabled()", "thumbTintColor": "thumbColor()", "tintColor": "trackColor()?.false", "trackColorForTrue": "trackColor()?.true", "trackColorForFalse": "trackColor()?.false", "trackTintColor": "checked() ? trackColor()?.true : trackColor()?.false", "style.backgroundColor": "iosBackgroundColor()", "style.borderRadius": "iosBackgroundColor() ? 16 : undefined" } }, usesInheritance: true, usesOnChanges: true, ngImport: i0 });
97
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: Switch, isStandalone: true, selector: "switch", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, thumbColor: { classPropertyName: "thumbColor", publicName: "thumbColor", isSignal: true, isRequired: false, transformFunction: null }, trackColor: { classPropertyName: "trackColor", publicName: "trackColor", isSignal: true, isRequired: false, transformFunction: null }, iosBackgroundColor: { classPropertyName: "iosBackgroundColor", publicName: "ios_backgroundColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touch: "touch" }, host: { listeners: { "change": "onNativeChange($event)" }, properties: { "accessibilityState": "accessibilityStateProp()", "value": "checked()", "disabled": "disabled()", "attr.data-disabled": "disabled() ? '' : null", "attr.data-invalid": "invalid() ? '' : null", "attr.data-touched": "touched() ? '' : null", "enabled": "enabled()", "thumbTintColor": "thumbColor()", "tintColor": "trackColor()?.false", "trackColorForTrue": "trackColor()?.true", "trackColorForFalse": "trackColor()?.false", "trackTintColor": "checked() ? trackColor()?.true : trackColor()?.false", "style.backgroundColor": "iosBackgroundColor()", "style.borderRadius": "iosBackgroundColor() ? 16 : undefined" } }, usesInheritance: true, usesOnChanges: true, ngImport: i0 });
87
98
  }
88
99
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Switch, decorators: [{
89
100
  type: Directive,
@@ -96,6 +107,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
96
107
  '[value]': 'checked()',
97
108
  '[disabled]': 'disabled()',
98
109
  '[attr.data-disabled]': "disabled() ? '' : null",
110
+ '[attr.data-invalid]': "invalid() ? '' : null",
111
+ '[attr.data-touched]': "touched() ? '' : null",
99
112
  '[enabled]': 'enabled()',
100
113
  '[thumbTintColor]': 'thumbColor()',
101
114
  '[tintColor]': 'trackColor()?.false',
@@ -107,5 +120,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
107
120
  '(change)': 'onNativeChange($event)',
108
121
  },
109
122
  }]
110
- }], ctorParameters: () => [], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], thumbColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbColor", required: false }] }], trackColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackColor", required: false }] }], iosBackgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "ios_backgroundColor", required: false }] }] } });
123
+ }], ctorParameters: () => [], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], thumbColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbColor", required: false }] }], trackColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackColor", required: false }] }], iosBackgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "ios_backgroundColor", required: false }] }] } });
111
124
  //# sourceMappingURL=switch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","sourceRoot":"","sources":["../src/switch.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n Injector,\n type SimpleChanges,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n model,\n} from '@angular/core';\nimport { type NativeSyntheticEvent, nativePlatform } from '@ng-native/fabric';\nimport { ControlledModel } from './controlled-model.ts';\nimport { optionalBoolean } from './transforms.ts';\nimport { ViewBase } from './view-base.ts';\n\n/**\n * A toggle, wired for signal forms as a `FormCheckboxControl` (`checked`, not `value`). Commits\n * as `Switch` on iOS and `AndroidSwitch` on Android.\n *\n * The two platforms spell the same props differently, so both spellings are sent and each\n * native side reads its own; a prop it does not know is ignored.\n *\n * Controlled as React Native's `Switch` is: bind `checked` and the app's value is what the switch\n * shows. The user flips it, the app keeps the old value, and a prop diff has nothing to send,\n * because the prop never changed; native is the only side that moved. So once the pass the flip\n * started has rendered, the switch compares the model with what native reported and, if they\n * differ, sends the command RN's wrapper sends - `setValue` on iOS, `setNativeValue` on Android.\n * See `ControlledModel` for how the model comes to say what the app says.\n */\n@Directive({\n selector: 'switch',\n host: {\n // `disabled` is an input of ours, so it cannot reach `ngOnChanges` on the base; this is\n // the one binding that keeps the announced state in step with it.\n '[accessibilityState]': 'accessibilityStateProp()',\n '[value]': 'checked()',\n '[disabled]': 'disabled()',\n '[attr.data-disabled]': \"disabled() ? '' : null\",\n '[enabled]': 'enabled()',\n '[thumbTintColor]': 'thumbColor()',\n '[tintColor]': 'trackColor()?.false',\n '[trackColorForTrue]': 'trackColor()?.true',\n '[trackColorForFalse]': 'trackColor()?.false',\n '[trackTintColor]': 'checked() ? trackColor()?.true : trackColor()?.false',\n '[style.backgroundColor]': 'iosBackgroundColor()',\n '[style.borderRadius]': 'iosBackgroundColor() ? 16 : undefined',\n '(change)': 'onNativeChange($event)',\n },\n})\nexport class Switch extends ViewBase {\n readonly checked = model<boolean>(false);\n /** Ignore touches and grey out. */\n readonly disabled = input(undefined, { transform: optionalBoolean });\n\n /** `Switch.js`: `accessibilityRole={props.accessibilityRole ?? 'switch'}`. */\n protected override roleByDefault(): 'switch' {\n return 'switch';\n }\n\n protected override disabledForAccessibility(): boolean | undefined {\n return this.disabled();\n }\n\n protected override checkedForAccessibility(): boolean {\n return this.checked();\n }\n /** The colour of the knob. */\n readonly thumbColor = input<string>();\n /** The track colour for each state. */\n readonly trackColor = input<{ readonly false?: string; readonly true?: string }>();\n /** iOS: the colour behind the track when off, seen where the track is transparent. */\n readonly iosBackgroundColor = input<string>(undefined, { alias: 'ios_backgroundColor' });\n\n protected readonly enabled = computed(() => {\n const disabled = this.disabled();\n return disabled === undefined ? undefined : !disabled;\n });\n\n private readonly injector = inject(Injector);\n private readonly control = new ControlledModel(this.checked, 'checked');\n /** What native reported last, until the pass it started has rendered and been compared. */\n private nativeValue: boolean | null = null;\n\n constructor() {\n super();\n effect(() => {\n // Android spells the value `on` and iOS the on-track colour `onTintColor`. Angular refuses\n // both as host bindings because they read as event handlers, so they go to the node.\n this.engine.setProp(this.node, 'on', this.checked());\n this.engine.setProp(this.node, 'onTintColor', this.trackColor()?.true);\n });\n }\n\n override ngOnChanges(changes: SimpleChanges): void {\n super.ngOnChanges(changes);\n this.control.noteChanges(changes);\n }\n\n protected onNativeChange(event: NativeSyntheticEvent<{ value?: boolean }>): void {\n this.nativeValue = event.nativeEvent?.value ?? false;\n this.control.propose(this.nativeValue);\n afterNextRender({ write: () => this.reconcile() }, { injector: this.injector });\n }\n\n /**\n * After the app's bindings have settled, not before: a flip the app accepts reaches the model\n * during change detection, and comparing any earlier would send native back the value it is\n * about to be given. A value set from code later needs none of this, as the prop changes.\n */\n private reconcile(): void {\n const native = this.nativeValue;\n this.nativeValue = null;\n const desired = this.checked();\n if (native === null || native === desired) return;\n const command = nativePlatform() === 'android' ? 'setNativeValue' : 'setValue';\n this.engine.dispatchCommand(this.node, command, [desired]);\n }\n}\n"]}
1
+ {"version":3,"file":"switch.js","sourceRoot":"","sources":["../src/switch.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n Injector,\n type SimpleChanges,\n afterNextRender,\n booleanAttribute,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport { type NativeSyntheticEvent, nativePlatform } from '@ng-native/fabric';\nimport { ControlledModel } from './controlled-model.ts';\nimport { optionalBoolean } from './transforms.ts';\nimport { ViewBase } from './view-base.ts';\n\n/**\n * A toggle, wired for signal forms as a `FormCheckboxControl` (`checked`, not `value`). Commits\n * as `Switch` on iOS and `AndroidSwitch` on Android.\n *\n * `invalid` and `touched` come out as `data-invalid` and `data-touched` for a stylesheet, as on\n * `<text-input>`, and `touch` fires when the user flips it: a switch has no blur, so the flip is\n * the moment the user has dealt with it, which a form that shows errors once touched waits for.\n *\n * The two platforms spell the same props differently, so both spellings are sent and each\n * native side reads its own; a prop it does not know is ignored.\n *\n * Controlled as React Native's `Switch` is: bind `checked` and the app's value is what the switch\n * shows. The user flips it, the app keeps the old value, and a prop diff has nothing to send,\n * because the prop never changed; native is the only side that moved. So once the pass the flip\n * started has rendered, the switch compares the model with what native reported and, if they\n * differ, sends the command RN's wrapper sends - `setValue` on iOS, `setNativeValue` on Android.\n * See `ControlledModel` for how the model comes to say what the app says.\n */\n@Directive({\n selector: 'switch',\n host: {\n // `disabled` is an input of ours, so it cannot reach `ngOnChanges` on the base; this is\n // the one binding that keeps the announced state in step with it.\n '[accessibilityState]': 'accessibilityStateProp()',\n '[value]': 'checked()',\n '[disabled]': 'disabled()',\n '[attr.data-disabled]': \"disabled() ? '' : null\",\n '[attr.data-invalid]': \"invalid() ? '' : null\",\n '[attr.data-touched]': \"touched() ? '' : null\",\n '[enabled]': 'enabled()',\n '[thumbTintColor]': 'thumbColor()',\n '[tintColor]': 'trackColor()?.false',\n '[trackColorForTrue]': 'trackColor()?.true',\n '[trackColorForFalse]': 'trackColor()?.false',\n '[trackTintColor]': 'checked() ? trackColor()?.true : trackColor()?.false',\n '[style.backgroundColor]': 'iosBackgroundColor()',\n '[style.borderRadius]': 'iosBackgroundColor() ? 16 : undefined',\n '(change)': 'onNativeChange($event)',\n },\n})\nexport class Switch extends ViewBase {\n readonly checked = model<boolean>(false);\n /** Ignore touches and grey out. */\n readonly disabled = input(undefined, { transform: optionalBoolean });\n /** Published as `data-invalid`; nothing native reads it. */\n readonly invalid = input(false, { transform: booleanAttribute });\n /** Published as `data-touched`; nothing native reads it. */\n readonly touched = input(false, { transform: booleanAttribute });\n /** The user flipped it. What Signal Forms marks a field touched from. */\n readonly touch = output<void>();\n\n /** `Switch.js`: `accessibilityRole={props.accessibilityRole ?? 'switch'}`. */\n protected override roleByDefault(): 'switch' {\n return 'switch';\n }\n\n protected override disabledForAccessibility(): boolean | undefined {\n return this.disabled();\n }\n\n protected override checkedForAccessibility(): boolean {\n return this.checked();\n }\n /** The colour of the knob. */\n readonly thumbColor = input<string>();\n /** The track colour for each state. */\n readonly trackColor = input<{ readonly false?: string; readonly true?: string }>();\n /** iOS: the colour behind the track when off, seen where the track is transparent. */\n readonly iosBackgroundColor = input<string>(undefined, { alias: 'ios_backgroundColor' });\n\n protected readonly enabled = computed(() => {\n const disabled = this.disabled();\n return disabled === undefined ? undefined : !disabled;\n });\n\n private readonly injector = inject(Injector);\n private readonly control = new ControlledModel(this.checked, 'checked');\n /** What native reported last, until the pass it started has rendered and been compared. */\n private nativeValue: boolean | null = null;\n\n constructor() {\n super();\n effect(() => {\n // Android spells the value `on` and iOS the on-track colour `onTintColor`. Angular refuses\n // both as host bindings because they read as event handlers, so they go to the node.\n this.engine.setProp(this.node, 'on', this.checked());\n this.engine.setProp(this.node, 'onTintColor', this.trackColor()?.true);\n });\n }\n\n override ngOnChanges(changes: SimpleChanges): void {\n super.ngOnChanges(changes);\n this.control.noteChanges(changes);\n }\n\n protected onNativeChange(event: NativeSyntheticEvent<{ value?: boolean }>): void {\n this.nativeValue = event.nativeEvent?.value ?? false;\n this.control.propose(this.nativeValue);\n this.touch.emit();\n afterNextRender({ write: () => this.reconcile() }, { injector: this.injector });\n }\n\n /**\n * After the app's bindings have settled, not before: a flip the app accepts reaches the model\n * during change detection, and comparing any earlier would send native back the value it is\n * about to be given. A value set from code later needs none of this, as the prop changes.\n */\n private reconcile(): void {\n const native = this.nativeValue;\n this.nativeValue = null;\n const desired = this.checked();\n if (native === null || native === desired) return;\n const command = nativePlatform() === 'android' ? 'setNativeValue' : 'setValue';\n this.engine.dispatchCommand(this.node, command, [desired]);\n }\n}\n"]}
package/dist/text.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { TouchableBase } from './pressable.ts';
2
+ import type { AccessibilityRole } from './view-base.ts';
2
3
  import * as i0 from "@angular/core";
3
4
  export type EllipsizeMode = 'head' | 'middle' | 'tail' | 'clip';
4
5
  export type DynamicTypeRamp = 'caption2' | 'caption1' | 'footnote' | 'subheadline' | 'callout' | 'body' | 'headline' | 'title3' | 'title2' | 'title1' | 'largeTitle';
@@ -27,7 +28,14 @@ export declare class Text extends TouchableBase {
27
28
  * `ngOnChanges` like the rest of the state, since `disabled` is an input of this directive: no
28
29
  * host binding, which `ControlBase` needs and a text cannot afford.
29
30
  */
30
- protected disabledForAccessibility(): boolean;
31
+ protected disabledForAccessibility(): boolean | undefined;
32
+ /**
33
+ * `Text.js`: a pressable text is a `link` unless a role is set or it is disabled, as the
34
+ * merged state says: `disabled` wins, and `aria-disabled`, `accessibilityState` or a
35
+ * contributing directive stand in when it is unset. Nested or not. Its inputs are this
36
+ * directive's, so `ngOnChanges` keeps it current without a host binding.
37
+ */
38
+ protected roleByDefault(): AccessibilityRole | undefined;
31
39
  /** Truncate after this many lines, with `ellipsizeMode` deciding where the ellipsis goes. */
32
40
  readonly numberOfLines: import("@angular/core").InputSignalWithTransform<number | undefined, unknown>;
33
41
  /**
@@ -70,6 +78,7 @@ export declare class Text extends TouchableBase {
70
78
  /** The pass-through props, written when one of them changes. See `ViewBase.ngOnChanges`. */
71
79
  ngOnChanges(): void;
72
80
  protected claims(): boolean;
81
+ protected pressDisabled(): boolean;
73
82
  protected measures(): boolean;
74
83
  static ɵfac: i0.ɵɵFactoryDeclaration<Text, never>;
75
84
  static ɵdir: i0.ɵɵDirectiveDeclaration<Text, "text", ["text"], { "numberOfLines": { "alias": "numberOfLines"; "required": false; "isSignal": true; }; "ellipsizeMode": { "alias": "ellipsizeMode"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "selectionColor": { "alias": "selectionColor"; "required": false; "isSignal": true; }; "allowFontScaling": { "alias": "allowFontScaling"; "required": false; "isSignal": true; }; "maxFontSizeMultiplier": { "alias": "maxFontSizeMultiplier"; "required": false; "isSignal": true; }; "dynamicTypeRamp": { "alias": "dynamicTypeRamp"; "required": false; "isSignal": true; }; "adjustsFontSizeToFit": { "alias": "adjustsFontSizeToFit"; "required": false; "isSignal": true; }; "minimumFontScale": { "alias": "minimumFontScale"; "required": false; "isSignal": true; }; "lineBreakStrategyIOS": { "alias": "lineBreakStrategyIOS"; "required": false; "isSignal": true; }; "textBreakStrategy": { "alias": "textBreakStrategy"; "required": false; "isSignal": true; }; "dataDetectorType": { "alias": "dataDetectorType"; "required": false; "isSignal": true; }; "hyphenationFrequency": { "alias": "android_hyphenationFrequency"; "required": false; "isSignal": true; }; "pressable": { "alias": "pressable"; "required": false; "isSignal": true; }; "suppressHighlighting": { "alias": "suppressHighlighting"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
package/dist/text.js CHANGED
@@ -33,6 +33,17 @@ export class Text extends TouchableBase {
33
33
  disabledForAccessibility() {
34
34
  return this.disabled();
35
35
  }
36
+ /**
37
+ * `Text.js`: a pressable text is a `link` unless a role is set or it is disabled, as the
38
+ * merged state says: `disabled` wins, and `aria-disabled`, `accessibilityState` or a
39
+ * contributing directive stand in when it is unset. Nested or not. Its inputs are this
40
+ * directive's, so `ngOnChanges` keeps it current without a host binding.
41
+ */
42
+ roleByDefault() {
43
+ if (!this.pressable())
44
+ return undefined;
45
+ return this.accessibilityStateProp()?.disabled === true ? undefined : 'link';
46
+ }
36
47
  /** Truncate after this many lines, with `ellipsizeMode` deciding where the ellipsis goes. */
37
48
  numberOfLines = input(undefined, { ...(ngDevMode ? { debugName: "numberOfLines" } : /* istanbul ignore next */ {}), transform: optionalNumber });
38
49
  /**
@@ -104,7 +115,10 @@ export class Text extends TouchableBase {
104
115
  write('data-disabled', this.disabled() ? '' : undefined);
105
116
  }
106
117
  claims() {
107
- return this.pressable() && !this.disabled();
118
+ return this.pressable() && !this.pressDisabled();
119
+ }
120
+ pressDisabled() {
121
+ return this.disabledByAnyState();
108
122
  }
109
123
  measures() {
110
124
  return this.pressable();
package/dist/text.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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","sourcesContent":["import { Directive, booleanAttribute, input } from '@angular/core';\nimport { nativePlatform } from '@ng-native/fabric';\nimport { TouchableBase } from './pressable.ts';\nimport { optionalBoolean, optionalNumber } from './transforms.ts';\n\nexport type EllipsizeMode = 'head' | 'middle' | 'tail' | 'clip';\nexport type DynamicTypeRamp =\n | 'caption2'\n | 'caption1'\n | 'footnote'\n | 'subheadline'\n | 'callout'\n | 'body'\n | 'headline'\n | 'title3'\n | 'title2'\n | 'title1'\n | 'largeTitle';\n\n/**\n * A run of text. Commits as `Paragraph`, or as a span (`VirtualText`) when nested in another\n * `<text>`, which the engine decides from the tree so a nested run inherits its parent's font\n * and sits on its baseline.\n *\n * Text inherits `color`, `fontSize` and the other text properties from CSS as it does on the\n * web; from an inline `[style]` on an ancestor view it does not, exactly as in RN.\n *\n * Presses: RN makes a text pressable when a press handler is present. Angular cannot see\n * whether `(press)` is bound, so set `pressable` to opt in; the handlers, delays and long press\n * are then the same as `<pressable>`'s. iOS highlights a pressed run unless\n * `suppressHighlighting` is set.\n */\n@Directive({\n selector: 'text',\n exportAs: 'text',\n})\nexport class Text extends TouchableBase {\n /**\n * `Text.js` splits by platform: on iOS every text is an accessibility element unless refused,\n * on Android only one that can be pressed. Nothing native can infer which, so the host's\n * `Platform.OS` decides, exactly as React Native's own wrapper does.\n */\n protected override accessibleByDefault(): boolean {\n return nativePlatform() === 'android' ? this.pressable() : true;\n }\n\n /**\n * `Text.js` announces `disabled` on every text, pressable or not, nested or not. Written from\n * `ngOnChanges` like the rest of the state, since `disabled` is an input of this directive: no\n * host binding, which `ControlBase` needs and a text cannot afford.\n */\n protected override disabledForAccessibility(): boolean {\n return this.disabled();\n }\n\n /** Truncate after this many lines, with `ellipsizeMode` deciding where the ellipsis goes. */\n readonly numberOfLines = input<number>(undefined, { transform: optionalNumber });\n /**\n * Where truncated text is cut. `clip` is iOS only. Defaults to `tail`, as React Native's `Text`\n * does in JavaScript: native's own default is to clip, with no ellipsis. The default is the\n * engine's, below `text-overflow` in a stylesheet, which in turn is below this input.\n */\n readonly ellipsizeMode = input<EllipsizeMode>();\n /** Let the user select and copy the text. */\n readonly selectable = input<boolean>(undefined, { transform: optionalBoolean });\n /** The highlight colour of a selection. */\n readonly selectionColor = input<string>();\n /** Scale with the system Text Size setting. Defaults to true. */\n readonly allowFontScaling = input<boolean>(undefined, { transform: optionalBoolean });\n /** The most `allowFontScaling` may scale by; 0 means no limit. */\n readonly maxFontSizeMultiplier = input<number>(undefined, { transform: optionalNumber });\n /** iOS: the Dynamic Type style this text follows. */\n readonly dynamicTypeRamp = input<DynamicTypeRamp>();\n /** iOS: shrink the font to fit `numberOfLines`. */\n readonly adjustsFontSizeToFit = input<boolean>(undefined, { transform: optionalBoolean });\n /** iOS: the smallest scale `adjustsFontSizeToFit` may shrink to, from 0.01 to 1. */\n readonly minimumFontScale = input<number>(undefined, { transform: optionalNumber });\n /** iOS: the line-breaking strategy. */\n readonly lineBreakStrategyIOS = input<'none' | 'standard' | 'hangul-word' | 'push-out'>();\n /** Android: the line-breaking strategy. */\n readonly textBreakStrategy = input<'simple' | 'highQuality' | 'balanced'>();\n /** Android: turn phone numbers, links and addresses into tappable text. */\n readonly dataDetectorType = input<'phoneNumber' | 'link' | 'email' | 'none' | 'all'>();\n /** Android: how often to hyphenate. */\n readonly hyphenationFrequency = input<'normal' | 'none' | 'full'>(undefined, {\n alias: 'android_hyphenationFrequency',\n });\n /** Respond to presses. See the class note. */\n readonly pressable = input(false, { transform: booleanAttribute });\n /** iOS: do not grey the text while it is pressed. */\n readonly suppressHighlighting = input(false, { transform: booleanAttribute });\n\n /**\n * Written when the press starts and ends rather than bound from the host: a host binding would\n * run for every text on screen on every pass, to follow a press that almost none of them get.\n */\n protected override pressedChanged(pressed: boolean): void {\n const highlight = pressed && !this.suppressHighlighting();\n this.engine.setProp(this.node, 'isHighlighted', highlight ? true : undefined);\n }\n\n /** The pass-through props, written when one of them changes. See `ViewBase.ngOnChanges`. */\n override ngOnChanges(): void {\n super.ngOnChanges();\n const write = (key: string, value: unknown) => this.engine.setProp(this.node, key, value);\n write('numberOfLines', this.numberOfLines());\n write('ellipsizeMode', this.ellipsizeMode());\n write('selectable', this.selectable());\n write('selectionColor', this.selectionColor());\n write('allowFontScaling', this.allowFontScaling());\n write('maxFontSizeMultiplier', this.maxFontSizeMultiplier());\n write('dynamicTypeRamp', this.dynamicTypeRamp());\n write('adjustsFontSizeToFit', this.adjustsFontSizeToFit());\n write('minimumFontScale', this.minimumFontScale());\n write('lineBreakStrategyIOS', this.lineBreakStrategyIOS());\n write('textBreakStrategy', this.textBreakStrategy());\n write('dataDetectorType', this.dataDetectorType());\n write('android_hyphenationFrequency', this.hyphenationFrequency());\n write('isPressable', this.pressable() || undefined);\n // `disabled` is consumed as an input, so this is what `[data-disabled]` and `disabled:` match,\n // as on a control. Written here rather than bound from the host; see `pressedChanged`.\n write('data-disabled', this.disabled() ? '' : undefined);\n }\n\n protected override claims(): boolean {\n return this.pressable() && !this.disabled();\n }\n\n protected override measures(): boolean {\n return this.pressable();\n }\n}\n"]}
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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","sourcesContent":["import { Directive, booleanAttribute, input } from '@angular/core';\nimport { nativePlatform } from '@ng-native/fabric';\nimport { TouchableBase } from './pressable.ts';\nimport { optionalBoolean, optionalNumber } from './transforms.ts';\nimport type { AccessibilityRole } from './view-base.ts';\n\nexport type EllipsizeMode = 'head' | 'middle' | 'tail' | 'clip';\nexport type DynamicTypeRamp =\n | 'caption2'\n | 'caption1'\n | 'footnote'\n | 'subheadline'\n | 'callout'\n | 'body'\n | 'headline'\n | 'title3'\n | 'title2'\n | 'title1'\n | 'largeTitle';\n\n/**\n * A run of text. Commits as `Paragraph`, or as a span (`VirtualText`) when nested in another\n * `<text>`, which the engine decides from the tree so a nested run inherits its parent's font\n * and sits on its baseline.\n *\n * Text inherits `color`, `fontSize` and the other text properties from CSS as it does on the\n * web; from an inline `[style]` on an ancestor view it does not, exactly as in RN.\n *\n * Presses: RN makes a text pressable when a press handler is present. Angular cannot see\n * whether `(press)` is bound, so set `pressable` to opt in; the handlers, delays and long press\n * are then the same as `<pressable>`'s. iOS highlights a pressed run unless\n * `suppressHighlighting` is set.\n */\n@Directive({\n selector: 'text',\n exportAs: 'text',\n})\nexport class Text extends TouchableBase {\n /**\n * `Text.js` splits by platform: on iOS every text is an accessibility element unless refused,\n * on Android only one that can be pressed. Nothing native can infer which, so the host's\n * `Platform.OS` decides, exactly as React Native's own wrapper does.\n */\n protected override accessibleByDefault(): boolean {\n return nativePlatform() === 'android' ? this.pressable() : true;\n }\n\n /**\n * `Text.js` announces `disabled` on every text, pressable or not, nested or not. Written from\n * `ngOnChanges` like the rest of the state, since `disabled` is an input of this directive: no\n * host binding, which `ControlBase` needs and a text cannot afford.\n */\n protected override disabledForAccessibility(): boolean | undefined {\n return this.disabled();\n }\n\n /**\n * `Text.js`: a pressable text is a `link` unless a role is set or it is disabled, as the\n * merged state says: `disabled` wins, and `aria-disabled`, `accessibilityState` or a\n * contributing directive stand in when it is unset. Nested or not. Its inputs are this\n * directive's, so `ngOnChanges` keeps it current without a host binding.\n */\n protected override roleByDefault(): AccessibilityRole | undefined {\n if (!this.pressable()) return undefined;\n return this.accessibilityStateProp()?.disabled === true ? undefined : 'link';\n }\n\n /** Truncate after this many lines, with `ellipsizeMode` deciding where the ellipsis goes. */\n readonly numberOfLines = input<number>(undefined, { transform: optionalNumber });\n /**\n * Where truncated text is cut. `clip` is iOS only. Defaults to `tail`, as React Native's `Text`\n * does in JavaScript: native's own default is to clip, with no ellipsis. The default is the\n * engine's, below `text-overflow` in a stylesheet, which in turn is below this input.\n */\n readonly ellipsizeMode = input<EllipsizeMode>();\n /** Let the user select and copy the text. */\n readonly selectable = input<boolean>(undefined, { transform: optionalBoolean });\n /** The highlight colour of a selection. */\n readonly selectionColor = input<string>();\n /** Scale with the system Text Size setting. Defaults to true. */\n readonly allowFontScaling = input<boolean>(undefined, { transform: optionalBoolean });\n /** The most `allowFontScaling` may scale by; 0 means no limit. */\n readonly maxFontSizeMultiplier = input<number>(undefined, { transform: optionalNumber });\n /** iOS: the Dynamic Type style this text follows. */\n readonly dynamicTypeRamp = input<DynamicTypeRamp>();\n /** iOS: shrink the font to fit `numberOfLines`. */\n readonly adjustsFontSizeToFit = input<boolean>(undefined, { transform: optionalBoolean });\n /** iOS: the smallest scale `adjustsFontSizeToFit` may shrink to, from 0.01 to 1. */\n readonly minimumFontScale = input<number>(undefined, { transform: optionalNumber });\n /** iOS: the line-breaking strategy. */\n readonly lineBreakStrategyIOS = input<'none' | 'standard' | 'hangul-word' | 'push-out'>();\n /** Android: the line-breaking strategy. */\n readonly textBreakStrategy = input<'simple' | 'highQuality' | 'balanced'>();\n /** Android: turn phone numbers, links and addresses into tappable text. */\n readonly dataDetectorType = input<'phoneNumber' | 'link' | 'email' | 'none' | 'all'>();\n /** Android: how often to hyphenate. */\n readonly hyphenationFrequency = input<'normal' | 'none' | 'full'>(undefined, {\n alias: 'android_hyphenationFrequency',\n });\n /** Respond to presses. See the class note. */\n readonly pressable = input(false, { transform: booleanAttribute });\n /** iOS: do not grey the text while it is pressed. */\n readonly suppressHighlighting = input(false, { transform: booleanAttribute });\n\n /**\n * Written when the press starts and ends rather than bound from the host: a host binding would\n * run for every text on screen on every pass, to follow a press that almost none of them get.\n */\n protected override pressedChanged(pressed: boolean): void {\n const highlight = pressed && !this.suppressHighlighting();\n this.engine.setProp(this.node, 'isHighlighted', highlight ? true : undefined);\n }\n\n /** The pass-through props, written when one of them changes. See `ViewBase.ngOnChanges`. */\n override ngOnChanges(): void {\n super.ngOnChanges();\n const write = (key: string, value: unknown) => this.engine.setProp(this.node, key, value);\n write('numberOfLines', this.numberOfLines());\n write('ellipsizeMode', this.ellipsizeMode());\n write('selectable', this.selectable());\n write('selectionColor', this.selectionColor());\n write('allowFontScaling', this.allowFontScaling());\n write('maxFontSizeMultiplier', this.maxFontSizeMultiplier());\n write('dynamicTypeRamp', this.dynamicTypeRamp());\n write('adjustsFontSizeToFit', this.adjustsFontSizeToFit());\n write('minimumFontScale', this.minimumFontScale());\n write('lineBreakStrategyIOS', this.lineBreakStrategyIOS());\n write('textBreakStrategy', this.textBreakStrategy());\n write('dataDetectorType', this.dataDetectorType());\n write('android_hyphenationFrequency', this.hyphenationFrequency());\n write('isPressable', this.pressable() || undefined);\n // `disabled` is consumed as an input, so this is what `[data-disabled]` and `disabled:` match,\n // as on a control. Written here rather than bound from the host; see `pressedChanged`.\n write('data-disabled', this.disabled() ? '' : undefined);\n }\n\n protected override claims(): boolean {\n return this.pressable() && !this.pressDisabled();\n }\n\n protected override pressDisabled(): boolean {\n return this.disabledByAnyState();\n }\n\n protected override measures(): boolean {\n return this.pressable();\n }\n}\n"]}